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, Sink, SpatialSink};
23#[cfg(feature = "playback")]
24use rodio::{OutputStream, OutputStreamBuilder};
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: OutputStream,
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 output_stream = OutputStreamBuilder::open_default_stream()?;
81
82        Ok(Self {
83            #[cfg(feature = "fs")]
84            cache_path,
85            map_store: HashMap::new(),
86
87            #[cfg(feature = "playback")]
88            max_sinks: 16,
89            #[cfg(feature = "playback")]
90            volume: 0.1,
91
92            #[cfg(feature = "playback")]
93            output_stream,
94            #[cfg(feature = "playback")]
95            sinks: VecDeque::new(),
96
97            #[cfg(feature = "memory")]
98            fs_memory: Default::default(),
99        })
100    }
101
102    #[must_use]
103    pub fn get(&self, sentence: &str) -> Option<&Vec<Chatsound>> {
104        self.map_store.get(sentence)
105    }
106
107    #[must_use]
108    pub fn search(&self, search: &str) -> Vec<(usize, &String)> {
109        #[cfg(feature = "rayon")]
110        use rayon::prelude::*;
111
112        #[cfg(feature = "rayon")]
113        let iter = HashMap::par_iter;
114        #[cfg(not(feature = "rayon"))]
115        let iter = HashMap::iter;
116
117        #[cfg(feature = "rayon")]
118        let sort_by = <[(usize, &String)]>::par_sort_unstable_by;
119        #[cfg(not(feature = "rayon"))]
120        let sort_by = <[(usize, &String)]>::sort_unstable_by;
121
122        let mut positions: Vec<_> = iter(&self.map_store)
123            .map(|(key, _value)| key)
124            .filter_map(|sentence| twoway::find_str(sentence, search).map(|pos| (pos, sentence)))
125            .collect();
126
127        sort_by(
128            positions.as_mut_slice(),
129            |(pos1, str1): &(usize, &String), (pos2, str2): &(usize, &String)| {
130                pos1.partial_cmp(pos2)
131                    .unwrap()
132                    .then_with(|| str1.len().partial_cmp(&str2.len()).unwrap())
133            },
134        );
135
136        positions
137    }
138
139    #[cfg(feature = "playback")]
140    pub fn stop_all(&mut self) {
141        for sink in self.sinks.drain(..) {
142            sink.stop();
143        }
144    }
145
146    #[cfg(feature = "playback")]
147    pub fn set_volume(&mut self, volume: f32) {
148        let volume = volume.max(0.0);
149
150        self.volume = volume;
151
152        for sink in &mut self.sinks {
153            sink.set_volume(volume);
154        }
155    }
156
157    #[cfg(feature = "playback")]
158    #[must_use]
159    pub const fn volume(&self) -> f32 {
160        self.volume
161    }
162
163    #[cfg(feature = "playback")]
164    pub async fn play<R: RngCore>(
165        &mut self,
166        text: &str,
167        rng: R,
168    ) -> Result<(Arc<Sink>, Vec<Chatsound>)> {
169        let sink = Arc::new(Sink::connect_new(self.output_stream.mixer()));
170
171        sink.set_volume(self.volume);
172
173        let (sources, chatsounds): (Vec<_>, Vec<_>) =
174            self.get_sources(text, rng).await?.into_iter().unzip();
175        for source in sources {
176            sink.append(source);
177        }
178
179        self.sinks.push_back(Box::new(sink.clone()));
180        if self.sinks.len() == self.max_sinks {
181            self.sinks.pop_front();
182        }
183
184        Ok((sink, chatsounds))
185    }
186
187    #[cfg(feature = "playback")]
188    pub async fn play_spatial<R: RngCore>(
189        &mut self,
190        text: &str,
191        rng: R,
192        emitter_pos: [f32; 3],
193        left_ear_pos: [f32; 3],
194        right_ear_pos: [f32; 3],
195    ) -> Result<(Arc<SpatialSink>, Vec<Chatsound>)> {
196        let sink = Arc::new(SpatialSink::connect_new(
197            self.output_stream.mixer(),
198            emitter_pos,
199            left_ear_pos,
200            right_ear_pos,
201        ));
202
203        sink.set_volume(self.volume);
204
205        let (sources, chatsounds): (Vec<_>, Vec<_>) =
206            self.get_sources(text, rng).await?.into_iter().unzip();
207        for source in sources {
208            sink.append(source);
209        }
210
211        self.sinks.push_back(Box::new(sink.clone()));
212        if self.sinks.len() == self.max_sinks {
213            self.sinks.pop_front();
214        }
215
216        Ok((sink, chatsounds))
217    }
218
219    #[cfg(feature = "playback")]
220    pub async fn play_channel_volume<R: RngCore>(
221        &mut self,
222        text: &str,
223        rng: R,
224        channel_volumes: Vec<f32>,
225    ) -> Result<(Arc<ChannelVolumeSink>, Vec<Chatsound>)> {
226        let sink = Arc::new(ChannelVolumeSink::connect_new(
227            self.output_stream.mixer(),
228            channel_volumes,
229        )?);
230
231        sink.sink.set_volume(self.volume);
232
233        let (sources, chatsounds): (Vec<_>, Vec<_>) =
234            self.get_sources(text, rng).await?.into_iter().unzip();
235        for source in sources {
236            sink.append(source);
237        }
238
239        self.sinks.push_back(Box::new(sink.clone()));
240        if self.sinks.len() == self.max_sinks {
241            self.sinks.pop_front();
242        }
243
244        Ok((sink, chatsounds))
245    }
246
247    pub async fn get_sources<R: RngCore>(
248        &mut self,
249        text: &str,
250        mut rng: R,
251    ) -> Result<Vec<(BoxSource, Chatsound)>> {
252        let mut sources = Vec::new();
253
254        let parsed_chatsounds = parse(text)?;
255        for parsed_chatsound in parsed_chatsounds {
256            let chatsound = if let Some(chatsounds) = self.get(&parsed_chatsound.sentence) {
257                // TODO random hashed number passed in?
258                parsed_chatsound
259                    .choose(chatsounds, &mut rng)
260                    .ok_or_else(|| Error::EmptyChoose { text: text.into() })?
261                    .to_owned()
262            } else {
263                continue;
264            };
265
266            let mut source: BoxSource = {
267                #[cfg(feature = "fs")]
268                let cache = &self.cache_path;
269                #[cfg(feature = "memory")]
270                let cache = self.fs_memory.clone();
271
272                let bytes = chatsound.load(cache).await?;
273
274                let reader = BufReader::new(Cursor::new(bytes));
275                let sound_path = chatsound.sound_path.clone();
276                Box::new(
277                    Decoder::new(reader).map_err(|err| Error::RodioDecoder { err, sound_path })?,
278                )
279            };
280            for modifier in parsed_chatsound.modifiers {
281                source = modifier.modify(source);
282            }
283
284            sources.push((source, chatsound));
285        }
286
287        Ok(sources)
288    }
289
290    #[cfg(feature = "playback")]
291    pub fn sleep_until_end(&mut self) {
292        for sink in self.sinks.drain(..) {
293            sink.sleep_until_end();
294        }
295    }
296}