1use alloc::borrow::ToOwned;
16use alloc::collections::BTreeMap;
17use alloc::format;
18use alloc::string::String;
19use alloc::vec::Vec;
20use core::cell::Cell;
21use core::future::{Future, ready};
22use core::pin::pin;
23use core::task::{Context, Poll, Waker};
24
25use shuyi_assets::audio::{
26 AudioManifest, BAKED_MUSIC_DIR, BAKED_SOUND_DIR, Files, without_extension,
27};
28use shuyi_platform::audio::player::{Backend, Player, RATE_ONE, Unsupported};
29use shuyi_ui::audio::SharedSamples;
30
31use crate::sdk::Sdk;
32
33pub const SAMPLE_PLAYERS: usize = 8;
36
37const VOICE_DIR: &str = "voice";
39
40fn rate_factor(rate: u32) -> f32 {
42 #[allow(clippy::cast_precision_loss)]
44 let rate = rate as f32;
45 #[allow(clippy::cast_precision_loss)]
46 let one = RATE_ONE as f32;
47 rate / one
48}
49
50#[derive(Debug)]
52struct Slot<S: Sdk> {
53 player: S::SamplePlayer,
54 sample: Option<S::Sample>,
56 serial: u32,
58 paused: bool,
60}
61
62#[derive(Debug)]
64pub struct Speaker<S: Sdk> {
65 sdk: S,
66 slots: Vec<Slot<S>>,
67 loaded: BTreeMap<String, S::Sample>,
69 made: Vec<S::Sample>,
71 serial: u32,
73 oldest: usize,
75}
76
77#[derive(Debug)]
79pub enum Clip<S: Sdk> {
80 Sample {
82 sample: S::Sample,
84 rate: Cell<u32>,
86 },
87 File(FileClip<S>),
89}
90
91#[derive(Debug)]
94pub struct FileClip<S: Sdk> {
95 sdk: S,
96 player: S::FilePlayer,
97 played: Cell<bool>,
99 rate: Cell<u32>,
101}
102
103impl<S: Sdk> Drop for FileClip<S> {
104 fn drop(&mut self) {
105 self.sdk.file_close(self.player);
106 }
107}
108
109#[derive(Debug)]
111pub enum Playing<S: Sdk> {
112 Nothing,
114 Sample {
116 slot: usize,
118 serial: u32,
120 },
121 File {
123 player: S::FilePlayer,
125 looped: bool,
127 paused: Cell<bool>,
129 },
130}
131
132impl<S: Sdk> Speaker<S> {
133 pub fn new(sdk: S) -> Self {
135 let slots = (0..SAMPLE_PLAYERS)
136 .filter_map(|_| sdk.sample_player_new())
137 .map(|player| Slot {
138 player,
139 sample: None,
140 serial: 0,
141 paused: false,
142 })
143 .collect();
144 Self {
145 sdk,
146 slots,
147 loaded: BTreeMap::new(),
148 made: Vec::new(),
149 serial: 0,
150 oldest: 0,
151 }
152 }
153
154 fn free_slot(&mut self) -> Option<usize> {
157 let free = self
158 .slots
159 .iter()
160 .position(|slot| !slot.paused && !self.sdk.sample_player_is_playing(slot.player));
161 if free.is_some() {
162 return free;
163 }
164 let count = self.slots.len();
165 let taken = (0..count)
166 .map(|turn| (self.oldest + turn) % count)
167 .find(|&at| !self.slots[at].paused)?;
168 self.oldest = taken + 1;
169 Some(taken)
170 }
171
172 fn slot_of(&mut self, playing: &Playing<S>) -> Option<&mut Slot<S>> {
174 match playing {
175 Playing::Sample { slot, serial } => {
176 self.slots.get_mut(*slot).filter(|at| at.serial == *serial)
177 }
178 _ => None,
179 }
180 }
181
182 fn slots_on(&self, sample: S::Sample) -> impl Iterator<Item = &Slot<S>> {
184 self.slots
185 .iter()
186 .filter(move |slot| slot.sample == Some(sample))
187 }
188}
189
190impl<S: Sdk> Backend for Speaker<S> {
191 type Sound = Clip<S>;
192 type Loading = Option<Result<Clip<S>, String>>;
193 type Playing = Playing<S>;
194
195 fn load_sound(&mut self, _bytes: &[u8]) -> impl Future<Output = Result<Clip<S>, String>> {
198 ready(Err("the Playdate loads a sound from its path".to_owned()))
200 }
201
202 fn load_sound_file(
203 &mut self,
204 file: &str,
205 _files: &Files<'_>,
206 ) -> impl Future<Output = Result<Clip<S>, String>> {
207 let path = format!("{BAKED_SOUND_DIR}/{}", without_extension(file));
208 let sample = match self.loaded.get(&path) {
209 Some(sample) => Some(*sample),
210 None => self.sdk.sample_load(&path),
211 };
212 ready(match sample {
213 Some(sample) => {
214 self.loaded.insert(path, sample);
215 Ok(Clip::Sample {
216 sample,
217 rate: Cell::new(RATE_ONE),
218 })
219 }
220 None => Err("not in the .pdx".to_owned()),
222 })
223 }
224
225 fn load_music(&mut self, path: &str) -> Self::Loading {
226 let opened = self.sdk.file_open(without_extension(path));
227 Some(match opened {
228 Some(player) => Ok(Clip::File(FileClip {
229 sdk: self.sdk.clone(),
230 player,
231 played: Cell::new(false),
232 rate: Cell::new(RATE_ONE),
233 })),
234 None => Err("not in the .pdx".to_owned()),
236 })
237 }
238
239 fn poll_music(&mut self, loading: &mut Self::Loading) -> Option<Result<Clip<S>, String>> {
240 loading.take()
241 }
242
243 fn length(&self, sound: &Clip<S>) -> Option<f64> {
244 let seconds = match sound {
245 Clip::Sample { sample, .. } => self.sdk.sample_length(*sample),
246 Clip::File(file) => self.sdk.file_length(file.player),
247 };
248 (seconds > 0.0).then(|| f64::from(seconds))
249 }
250
251 fn play(&mut self, sound: &Clip<S>, volume: f32, looped: bool) -> Playing<S> {
252 match sound {
253 Clip::Sample { sample, rate } => {
254 let Some(slot) = self.free_slot() else {
255 return Playing::Nothing;
256 };
257 self.serial = self.serial.wrapping_add(1);
258 let at = &mut self.slots[slot];
259 at.sample = Some(*sample);
260 at.serial = self.serial;
261 let factor = rate_factor(rate.get());
262 self.sdk
263 .sample_player_play(at.player, *sample, volume, looped, factor);
264 Playing::Sample {
265 slot,
266 serial: self.serial,
267 }
268 }
269 Clip::File(file) => {
270 if file.played.replace(true) {
271 self.sdk.file_stop(file.player);
272 self.sdk.file_rewind(file.player);
273 }
274 self.sdk
275 .file_set_rate(file.player, rate_factor(file.rate.get()));
276 self.sdk.file_set_volume(file.player, volume);
277 if !self.sdk.file_play(file.player, looped) {
278 self.sdk.log("audio: the SDK could not start a track");
280 }
281 Playing::File {
282 player: file.player,
283 looped,
284 paused: Cell::new(false),
285 }
286 }
287 }
288 }
289
290 fn set_volume(&mut self, sound: &Clip<S>, volume: f32) {
291 match sound {
292 Clip::Sample { sample, .. } => {
293 for slot in self.slots_on(*sample) {
294 self.sdk.sample_player_set_volume(slot.player, volume);
295 }
296 }
297 Clip::File(file) => self.sdk.file_set_volume(file.player, volume),
298 }
299 }
300
301 fn stop(&mut self, sound: &Clip<S>) {
302 match sound {
303 Clip::Sample { sample, .. } => {
304 for slot in self.slots_on(*sample) {
305 self.sdk.sample_player_stop(slot.player);
306 }
307 }
308 Clip::File(file) => self.sdk.file_stop(file.player),
309 }
310 }
311
312 fn set_playing_volume(&mut self, playing: &Playing<S>, volume: f32) {
313 if let Playing::File { player, .. } = playing {
314 self.sdk.file_set_volume(*player, volume);
315 } else if let Some(player) = self.slot_of(playing).map(|slot| slot.player) {
316 self.sdk.sample_player_set_volume(player, volume);
317 }
318 }
319
320 fn stop_playing(&mut self, playing: Playing<S>) {
321 if let Playing::File { player, .. } = playing {
322 self.sdk.file_stop(player);
323 } else if let Some(slot) = self.slot_of(&playing) {
324 slot.paused = false;
325 let player = slot.player;
326 self.sdk.sample_player_stop(player);
327 }
328 }
329
330 fn pause(&mut self, playing: &Playing<S>) -> Result<(), Unsupported> {
331 if let Playing::File { player, paused, .. } = playing {
332 if self.sdk.file_is_playing(*player) {
333 self.sdk.file_pause(*player);
334 paused.set(true);
335 }
336 } else if let Some(player) = self.slot_of(playing).map(|slot| slot.player)
337 && self.sdk.sample_player_is_playing(player)
338 {
339 self.sdk.sample_player_set_paused(player, true);
340 if let Some(slot) = self.slot_of(playing) {
341 slot.paused = true;
342 }
343 }
344 Ok(())
345 }
346
347 fn resume(&mut self, playing: &Playing<S>) -> Result<(), Unsupported> {
348 if let Playing::File {
349 player,
350 looped,
351 paused,
352 } = playing
353 {
354 if paused.replace(false) && !self.sdk.file_play(*player, *looped) {
356 self.sdk.log("audio: the SDK could not resume a track");
358 }
359 } else if let Some(slot) = self.slot_of(playing).filter(|slot| slot.paused) {
360 slot.paused = false;
361 let player = slot.player;
362 self.sdk.sample_player_set_paused(player, false);
363 }
364 Ok(())
365 }
366
367 fn set_rate(&mut self, sound: &Clip<S>, rate: u32) -> Result<(), Unsupported> {
368 match sound {
369 Clip::Sample { rate: at, .. } => at.set(rate),
370 Clip::File(file) => file.rate.set(rate),
371 }
372 Ok(())
373 }
374
375 fn add_sound(&mut self, samples: &SharedSamples) -> Result<Clip<S>, String> {
377 let stereo = samples.channels() == 2;
378 let Some(sample) = self
379 .sdk
380 .sample_from_data(samples.samples(), samples.rate(), stereo)
381 else {
382 return Err("the SDK made no sample of it: no memory, or no samples".to_owned());
384 };
385 self.made.push(sample);
386 Ok(Clip::Sample {
387 sample,
388 rate: Cell::new(RATE_ONE),
389 })
390 }
391
392 fn remove_sound(&mut self, sound: Clip<S>) {
394 self.stop(&sound);
395 let Clip::Sample { sample, .. } = sound else {
396 return;
397 };
398 let Some(at) = self.made.iter().position(|made| *made == sample) else {
399 return;
400 };
401 self.made.swap_remove(at);
402 for slot in &mut self.slots {
403 if slot.sample == Some(sample) {
404 slot.sample = None;
405 slot.paused = false;
406 }
407 }
408 self.sdk.sample_free(sample);
409 }
410
411 fn warn(&mut self, line: &str) {
412 self.sdk.log(&format!("audio: {line}"));
414 }
415}
416
417fn shown(file: &str) -> String {
420 format!("{BAKED_SOUND_DIR}/{}", without_extension(file))
421}
422
423pub fn player<S: Sdk>(
425 speaker: &mut Speaker<S>,
426 manifest: AudioManifest,
427 seed: u64,
428) -> Player<Speaker<S>> {
429 let files = Files::new(&|_| None, &shown, &|_| false, BAKED_MUSIC_DIR);
432 let music = BAKED_MUSIC_DIR.to_owned();
433 let voice = VOICE_DIR.to_owned();
434 ready_now(Player::load(speaker, manifest, &files, music, voice, seed))
435}
436
437fn ready_now<F: Future>(future: F) -> F::Output {
440 let mut future = pin!(future);
441 let mut cx = Context::from_waker(Waker::noop());
442 loop {
443 if let Poll::Ready(value) = future.as_mut().poll(&mut cx) {
444 return value;
445 }
446 }
447}
448
449#[cfg(test)]
450mod tests;