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 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#[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 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}