Skip to main content

chatsounds/
lib.rs

1#![warn(clippy::nursery, clippy::pedantic)]
2#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
3
4mod cache;
5#[cfg(feature = "playback")]
6mod channel_volume;
7mod error;
8mod fetching;
9mod parsing;
10mod types;
11
12use std::{
13    collections::{HashMap, VecDeque},
14    io::{BufReader, Cursor},
15    path::{Path, PathBuf},
16    sync::Arc,
17};
18
19pub use bytes::Bytes;
20use rand::prelude::*;
21pub use rodio;
22use rodio::{Decoder, Player, SpatialPlayer};
23#[cfg(feature = "playback")]
24use rodio::{DeviceSinkBuilder, MixerDeviceSink};
25
26#[cfg(feature = "playback")]
27pub use self::channel_volume::ChannelVolumeSink;
28#[cfg(feature = "playback")]
29use self::types::ChatsoundsSink;
30pub use self::{
31    error::Error,
32    fetching::{GitHubApiFileEntry, GitHubApiTrees, GitHubMsgpackEntries},
33    types::Chatsound,
34};
35use self::{
36    error::Result,
37    parsing::{parse, ModifierTrait},
38    types::BoxSource,
39};
40
41#[cfg(feature = "memory")]
42type FsMemory = Arc<async_lock::RwLock<HashMap<String, Bytes>>>;
43
44pub struct Chatsounds {
45    #[cfg(feature = "fs")]
46    cache_path: PathBuf,
47    // [sentence]: Chatsound[]
48    map_store: HashMap<String, Vec<Chatsound>>,
49
50    #[cfg(feature = "playback")]
51    max_sinks: usize,
52    #[cfg(feature = "playback")]
53    volume: f32,
54
55    #[cfg(feature = "playback")]
56    output_stream: MixerDeviceSink,
57    #[cfg(feature = "playback")]
58    sinks: VecDeque<Box<dyn ChatsoundsSink>>,
59
60    #[cfg(feature = "memory")]
61    fs_memory: FsMemory,
62}
63
64// TODO ???
65#[allow(clippy::non_send_fields_in_send_ty)]
66unsafe impl Send for Chatsounds {}
67unsafe impl Sync for Chatsounds {}
68
69impl Chatsounds {
70    pub fn new(#[cfg(feature = "fs")] cache_path: &Path) -> Result<Self> {
71        #[cfg(feature = "fs")]
72        let cache_path = cache_path.canonicalize().map_err(|err| Error::Io {
73            err,
74            path: cache_path.into(),
75        })?;
76        #[cfg(feature = "fs")]
77        ensure!(cache_path.is_dir(), Error::DirMissing { path: cache_path });
78
79        #[cfg(feature = "playback")]
80        let mut output_stream = DeviceSinkBuilder::open_default_sink()?;
81        #[cfg(feature = "playback")]
82        output_stream.log_on_drop(false);
83
84        Ok(Self {
85            #[cfg(feature = "fs")]
86            cache_path,
87            map_store: HashMap::new(),
88
89            #[cfg(feature = "playback")]
90            max_sinks: 64,
91            #[cfg(feature = "playback")]
92            volume: 0.1,
93
94            #[cfg(feature = "playback")]
95            output_stream,
96            #[cfg(feature = "playback")]
97            sinks: VecDeque::new(),
98
99            #[cfg(feature = "memory")]
100            fs_memory: Default::default(),
101        })
102    }
103
104    #[must_use]
105    pub fn get(&self, sentence: &str) -> Option<&Vec<Chatsound>> {
106        self.map_store.get(sentence)
107    }
108
109    #[must_use]
110    pub fn search(&self, search: &str) -> Vec<(usize, &String)> {
111        #[cfg(feature = "rayon")]
112        use rayon::prelude::*;
113
114        #[cfg(feature = "rayon")]
115        let iter = HashMap::par_iter;
116        #[cfg(not(feature = "rayon"))]
117        let iter = HashMap::iter;
118
119        #[cfg(feature = "rayon")]
120        let sort_by = <[(usize, &String)]>::par_sort_unstable_by;
121        #[cfg(not(feature = "rayon"))]
122        let sort_by = <[(usize, &String)]>::sort_unstable_by;
123
124        let mut positions: Vec<_> = iter(&self.map_store)
125            .map(|(key, _value)| key)
126            .filter_map(|sentence| twoway::find_str(sentence, search).map(|pos| (pos, sentence)))
127            .collect();
128
129        sort_by(
130            positions.as_mut_slice(),
131            |(pos1, str1): &(usize, &String), (pos2, str2): &(usize, &String)| {
132                pos1.partial_cmp(pos2)
133                    .unwrap()
134                    .then_with(|| str1.len().partial_cmp(&str2.len()).unwrap())
135            },
136        );
137
138        positions
139    }
140
141    #[cfg(feature = "playback")]
142    pub fn stop_all(&mut self) {
143        for sink in self.sinks.drain(..) {
144            sink.stop();
145        }
146    }
147
148    #[cfg(feature = "playback")]
149    pub fn set_volume(&mut self, volume: f32) {
150        let volume = volume.max(0.0);
151
152        self.volume = volume;
153
154        for sink in &mut self.sinks {
155            sink.set_volume(volume);
156        }
157    }
158
159    #[cfg(feature = "playback")]
160    #[must_use]
161    pub const fn volume(&self) -> f32 {
162        self.volume
163    }
164
165    #[cfg(feature = "playback")]
166    pub async fn play<R: Rng>(
167        &mut self,
168        text: &str,
169        rng: R,
170    ) -> Result<(Arc<Player>, Vec<Chatsound>)> {
171        let sink = Arc::new(Player::connect_new(self.output_stream.mixer()));
172
173        sink.set_volume(self.volume);
174
175        let (sources, chatsounds): (Vec<_>, Vec<_>) =
176            self.get_sources(text, rng).await?.into_iter().unzip();
177        for source in sources {
178            sink.append(source);
179        }
180
181        self.sinks.push_back(Box::new(sink.clone()));
182        if self.sinks.len() == self.max_sinks {
183            self.sinks.pop_front();
184        }
185
186        Ok((sink, chatsounds))
187    }
188
189    #[cfg(feature = "playback")]
190    pub async fn play_spatial<R: Rng>(
191        &mut self,
192        text: &str,
193        rng: R,
194        emitter_pos: [f32; 3],
195        left_ear_pos: [f32; 3],
196        right_ear_pos: [f32; 3],
197    ) -> Result<(Arc<SpatialPlayer>, Vec<Chatsound>)> {
198        let sink = Arc::new(SpatialPlayer::connect_new(
199            self.output_stream.mixer(),
200            emitter_pos,
201            left_ear_pos,
202            right_ear_pos,
203        ));
204
205        sink.set_volume(self.volume);
206
207        let (sources, chatsounds): (Vec<_>, Vec<_>) =
208            self.get_sources(text, rng).await?.into_iter().unzip();
209        for source in sources {
210            sink.append(source);
211        }
212
213        self.sinks.push_back(Box::new(sink.clone()));
214        if self.sinks.len() == self.max_sinks {
215            self.sinks.pop_front();
216        }
217
218        Ok((sink, chatsounds))
219    }
220
221    #[cfg(feature = "playback")]
222    pub async fn play_channel_volume<R: Rng>(
223        &mut self,
224        text: &str,
225        rng: R,
226        channel_volumes: Vec<f32>,
227    ) -> Result<(Arc<ChannelVolumeSink>, Vec<Chatsound>)> {
228        let sink = Arc::new(ChannelVolumeSink::connect_new(
229            self.output_stream.mixer(),
230            channel_volumes,
231        )?);
232
233        sink.sink.set_volume(self.volume);
234
235        let (sources, chatsounds): (Vec<_>, Vec<_>) =
236            self.get_sources(text, rng).await?.into_iter().unzip();
237        for source in sources {
238            sink.append(source);
239        }
240
241        self.sinks.push_back(Box::new(sink.clone()));
242        if self.sinks.len() == self.max_sinks {
243            self.sinks.pop_front();
244        }
245
246        Ok((sink, chatsounds))
247    }
248
249    pub async fn get_sources<R: Rng>(
250        &mut self,
251        text: &str,
252        mut rng: R,
253    ) -> Result<Vec<(BoxSource, Chatsound)>> {
254        let mut sources = Vec::new();
255
256        let parsed_chatsounds = parse(text)?;
257        for parsed_chatsound in parsed_chatsounds {
258            let chatsound = if let Some(chatsounds) = self.get(&parsed_chatsound.sentence) {
259                // TODO random hashed number passed in?
260                parsed_chatsound
261                    .choose(chatsounds, &mut rng)
262                    .ok_or_else(|| Error::EmptyChoose { text: text.into() })?
263                    .to_owned()
264            } else {
265                continue;
266            };
267
268            let mut source: BoxSource = {
269                #[cfg(feature = "fs")]
270                let cache = &self.cache_path;
271                #[cfg(feature = "memory")]
272                let cache = self.fs_memory.clone();
273
274                let bytes = chatsound.load(cache).await?;
275
276                let reader = BufReader::new(Cursor::new(bytes));
277                let sound_path = chatsound.sound_path.clone();
278                Box::new(
279                    Decoder::new(reader).map_err(|err| Error::RodioDecoder { err, sound_path })?,
280                )
281            };
282            for modifier in parsed_chatsound.modifiers {
283                source = modifier.modify(source);
284            }
285
286            sources.push((source, chatsound));
287        }
288
289        Ok(sources)
290    }
291
292    #[cfg(feature = "playback")]
293    pub fn sleep_until_end(&mut self) {
294        for sink in self.sinks.drain(..) {
295            sink.sleep_until_end();
296        }
297    }
298}