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