Skip to main content

shuyi_sound/
mix.rs

1//! The mixer: everything playing, added up into one block of samples, in whole
2//! numbers, so the same script gives the same samples on every target.
3//!
4//! - Volumes and balance are `u16`s in 1/4096 ([`UNITY`] is full), the sum is in
5//!   `i32`, and the result clamps to `i16`.
6//! - A rate is 16.16 ([`RATE_ONE`] is as recorded). A sound's own rate and this rate
7//!   make one exact step, and frames between are linearly interpolated.
8//! - [`Mixer::render`] takes no lock, allocates nothing, frees nothing, and does no
9//!   decoder work or I/O, so a system's audio callback can call it. All the memory
10//!   is taken in [`Mixer::new`].
11//! - A voice is never stopped to make room for another: with every voice busy,
12//!   [`Mixer::play`] says [`Full`]. Which sound loses is the caller's to decide,
13//!   before it gets here.
14//!
15//! A sound is decoded whole and held by the bank ([`Mixer::add_sound`]), and voices
16//! name it by [`SoundId`]. A stream is a [`Source`] that has its frames ready; a
17//! worker decodes ahead into a [`stream::feed`], whose module says what the two
18//! ends promise each other. [`queue`] is how another thread commands a mixer.
19
20use alloc::boxed::Box;
21use alloc::vec::Vec;
22
23use crate::pcm::{Channels, Pcm};
24use crate::source::Source;
25
26pub mod queue;
27mod resample;
28pub mod stream;
29mod voice;
30
31#[cfg(test)]
32mod tests;
33
34// What two owners both hold. A target without shared atomic counts has one thread.
35#[cfg(not(target_has_atomic = "ptr"))]
36use alloc::rc::Rc as Shared;
37#[cfg(target_has_atomic = "ptr")]
38use alloc::sync::Arc as Shared;
39
40use self::queue::{Command, Inbox, Remote};
41use self::stream::Slot;
42use self::voice::{Origin, Sources, Voice};
43
44/// A sound many voices can hold.
45pub type SharedPcm = Shared<Pcm>;
46
47/// Full volume, and an even balance, in 1/4096.
48pub const UNITY: u16 = 4096;
49/// As recorded, in 16.16.
50pub const RATE_ONE: u32 = 1 << 16;
51/// The slowest rate: a voice always ends, or comes round, in a bounded time.
52pub const RATE_MIN: u32 = RATE_ONE >> 8;
53/// The fastest rate: what a block costs stays bounded.
54pub const RATE_MAX: u32 = RATE_ONE << 8;
55
56/// How many frames the mixer adds up at a time.
57const BLOCK_FRAMES: usize = 256;
58
59/// Every voice is busy, or the bank or the queue has no room. Nothing was stopped
60/// to make any.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
62#[error("no room: every voice is busy")]
63pub struct Full;
64
65/// A place in a fixed table, and which of its tenants.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67struct Handle {
68    index: u32,
69    generation: u32,
70}
71
72/// A playing sound. Once it has ended the id means nothing: an operation on it
73/// does nothing, even when its voice is playing something else by then.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub struct VoiceId(Handle);
76
77/// A sound of a mixer's bank.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub struct SoundId(Handle);
80
81/// A stream waiting in a mixer to be played.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub struct StreamId(Handle);
84
85/// Part of a source, in frames: `start` up to, not including, `end`. It plays
86/// what comes before it once, then goes round it with no gap.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct Region {
89    start: u64,
90    end: u64,
91}
92
93impl Region {
94    /// From `start` up to `end`, if that is any frames at all.
95    pub const fn new(start: u64, end: u64) -> Option<Self> {
96        if start < end {
97            Some(Self { start, end })
98        } else {
99            None
100        }
101    }
102
103    /// Its first frame.
104    pub const fn start(self) -> u64 {
105        self.start
106    }
107
108    /// The frame after its last.
109    pub const fn end(self) -> u64 {
110        self.end
111    }
112}
113
114/// How a voice starts.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116#[non_exhaustive]
117pub struct Params {
118    /// In 1/4096. Over [`UNITY`] it amplifies.
119    pub volume: u16,
120    /// Starts over at its end. A region loops whether or not this is set.
121    pub looped: bool,
122    /// In 16.16, held between [`RATE_MIN`] and [`RATE_MAX`]: it moves the pitch
123    /// and the length together.
124    pub rate: u32,
125    /// The left and right gains, in 1/4096. A mono output plays their mean.
126    pub balance: (u16, u16),
127    /// What it loops, after playing what comes before once.
128    pub region: Option<Region>,
129}
130
131impl Params {
132    /// Full volume, as recorded, centred, once through.
133    pub const fn new() -> Self {
134        Self {
135            volume: UNITY,
136            looped: false,
137            rate: RATE_ONE,
138            balance: (UNITY, UNITY),
139            region: None,
140        }
141    }
142
143    /// At `volume`, in 1/4096.
144    #[must_use]
145    pub const fn with_volume(mut self, volume: u16) -> Self {
146        self.volume = volume;
147        self
148    }
149
150    /// Starting over at its end.
151    #[must_use]
152    pub const fn looped(mut self) -> Self {
153        self.looped = true;
154        self
155    }
156
157    /// At `rate`, in 16.16.
158    #[must_use]
159    pub const fn with_rate(mut self, rate: u32) -> Self {
160        self.rate = rate;
161        self
162    }
163
164    /// With these gains, in 1/4096.
165    #[must_use]
166    pub const fn with_balance(mut self, left: u16, right: u16) -> Self {
167        self.balance = (left, right);
168        self
169    }
170
171    /// Looping `region`.
172    #[must_use]
173    pub const fn with_region(mut self, region: Region) -> Self {
174        self.region = Some(region);
175        self
176    }
177}
178
179impl Default for Params {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185/// What a mixer is made with. All its memory comes from these.
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187#[non_exhaustive]
188pub struct Config {
189    /// The output's frames a second.
190    pub rate: u32,
191    /// The output's channels.
192    pub channels: Channels,
193    /// The most voices sounding at once.
194    pub voices: usize,
195    /// The most sounds in the bank at once.
196    pub sounds: usize,
197    /// The most streams held at once.
198    pub streams: usize,
199}
200
201impl Config {
202    /// [`Self::DEFAULT_SOUNDS`] sounds and [`Self::DEFAULT_STREAMS`] streams.
203    pub const fn new(rate: u32, channels: Channels, voices: usize) -> Self {
204        Self {
205            rate,
206            channels,
207            voices,
208            sounds: Self::DEFAULT_SOUNDS,
209            streams: Self::DEFAULT_STREAMS,
210        }
211    }
212
213    /// A bank's size by default.
214    pub const DEFAULT_SOUNDS: usize = 256;
215    /// Two, for a crossfade.
216    pub const DEFAULT_STREAMS: usize = 2;
217
218    /// With room for `sounds` sounds.
219    #[must_use]
220    pub const fn with_sounds(mut self, sounds: usize) -> Self {
221        self.sounds = sounds;
222        self
223    }
224
225    /// With room for `streams` streams.
226    #[must_use]
227    pub const fn with_streams(mut self, streams: usize) -> Self {
228        self.streams = streams;
229        self
230    }
231}
232
233/// The mix ([module docs](self)).
234pub struct Mixer {
235    config: Config,
236    channels: usize,
237    voices: Vec<Voice>,
238    sounds: Vec<Option<SharedPcm>>,
239    /// Each of `sounds`' slots' tenants so far.
240    generations: Vec<u32>,
241    streams: Vec<Slot>,
242    /// One block, added up.
243    block: Box<[i32]>,
244    underrun_frames: u64,
245}
246
247impl core::fmt::Debug for Mixer {
248    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249        f.debug_struct("Mixer")
250            .field("config", &self.config)
251            .field("underrun_frames", &self.underrun_frames)
252            .finish_non_exhaustive()
253    }
254}
255
256impl Mixer {
257    /// A mixer for `config`, with all the memory it will ever use.
258    pub fn new(config: Config) -> Self {
259        let channels = usize::from(config.channels.count());
260        Self {
261            config,
262            channels,
263            voices: alloc::vec![Voice::new(); config.voices],
264            sounds: alloc::vec![None; config.sounds],
265            generations: alloc::vec![0; config.sounds],
266            streams: (0..config.streams).map(|_| Slot::new()).collect(),
267            block: alloc::vec![0; BLOCK_FRAMES * channels].into_boxed_slice(),
268            underrun_frames: 0,
269        }
270    }
271
272    /// Its output's format.
273    pub const fn config(&self) -> Config {
274        self.config
275    }
276
277    /// The two ends for commanding this mixer from another thread, with room for
278    /// `commands` commands waiting ([`queue`]).
279    pub fn remote(&self, commands: usize) -> (Remote, Inbox) {
280        let sounds = self
281            .generations
282            .iter()
283            .zip(&self.sounds)
284            .map(|(generation, sound)| (*generation, sound.is_some()))
285            .collect();
286        let streams = self
287            .streams
288            .iter()
289            .map(|slot| (slot.generation, slot.source.is_some()))
290            .collect();
291        queue::ends(self.voices.len(), sounds, streams, commands)
292    }
293
294    /// Puts `pcm` in the place of the bank [`Remote::reserve_sound`] named, and gives
295    /// back what was there for the caller to free. It neither allocates nor frees, so
296    /// a backend's hand-over can call it on the audio thread.
297    pub fn put_sound(&mut self, sound: SoundId, pcm: SharedPcm) -> Option<SharedPcm> {
298        let Some(index) = usize::try_from(sound.0.index)
299            .ok()
300            .filter(|index| *index < self.sounds.len())
301        else {
302            return Some(pcm);
303        };
304        self.generations[index] = sound.0.generation;
305        self.sounds[index].replace(pcm)
306    }
307
308    /// Holds `source` in the stream slot [`Remote::reserve_stream`] named, and gives
309    /// back what was there for the caller to free. It neither allocates nor frees.
310    pub fn put_stream(
311        &mut self,
312        stream: StreamId,
313        source: Box<dyn Source + Send>,
314    ) -> Option<Box<dyn Source + Send>> {
315        let Some(slot) = usize::try_from(stream.0.index)
316            .ok()
317            .and_then(|index| self.streams.get_mut(index))
318        else {
319            return Some(source);
320        };
321        let old = slot.source.take();
322        slot.hold(source);
323        slot.generation = stream.0.generation;
324        old
325    }
326
327    /// Takes `stream` out of its slot, ending what is playing it, for the caller to
328    /// free where it may. A mix never frees one.
329    pub fn take_stream(&mut self, stream: StreamId) -> Option<Box<dyn Source + Send>> {
330        let index = usize::try_from(stream.0.index).ok()?;
331        if self.streams.get(index)?.generation != stream.0.generation {
332            return None;
333        }
334        for voice in &mut self.voices {
335            if voice.active && voice.origin == Origin::Stream(index) {
336                voice.finish(&mut self.streams);
337            }
338        }
339        let slot = &mut self.streams[index];
340        slot.retired = false;
341        slot.source.take()
342    }
343
344    /// Puts `pcm` in the bank.
345    pub fn add_sound(&mut self, pcm: SharedPcm) -> Result<SoundId, Full> {
346        let index = self.sounds.iter().position(Option::is_none).ok_or(Full)?;
347        self.sounds[index] = Some(pcm);
348        self.generations[index] = self.generations[index].wrapping_add(1);
349        Ok(SoundId(Handle {
350            index: u32::try_from(index).map_err(|_| Full)?,
351            generation: self.generations[index],
352        }))
353    }
354
355    /// Takes `sound` out of the bank and stops what is playing it. The caller
356    /// frees it, on a thread that may.
357    pub fn remove_sound(&mut self, sound: SoundId) -> Option<SharedPcm> {
358        let index = self.sound_index(sound)?;
359        for voice in &mut self.voices {
360            if voice.active && voice.origin == Origin::Sound(index) {
361                voice.finish(&mut self.streams);
362            }
363        }
364        self.sounds[index].take()
365    }
366
367    fn sound_index(&self, sound: SoundId) -> Option<usize> {
368        let index = usize::try_from(sound.0.index).ok()?;
369        let live = self.generations.get(index) == Some(&sound.0.generation);
370        (live && self.sounds[index].is_some()).then_some(index)
371    }
372
373    /// Holds `source` ready to play. A slot that a finished stream still holds is
374    /// freed for it; a backend with its mixer on the audio thread takes those out
375    /// first ([`take_finished_stream`](Self::take_finished_stream)).
376    pub fn add_stream(&mut self, source: Box<dyn Source + Send>) -> Result<StreamId, Full> {
377        let index = self
378            .streams
379            .iter()
380            .position(|slot| slot.source.is_none())
381            .or_else(|| self.streams.iter().position(|slot| slot.retired))
382            .ok_or(Full)?;
383        let slot = &mut self.streams[index];
384        slot.hold(source);
385        Ok(StreamId(Handle {
386            index: u32::try_from(index).map_err(|_| Full)?,
387            generation: slot.generation,
388        }))
389    }
390
391    /// A source whose voice has finished with it, for the caller to free where it
392    /// may. A mix never frees one.
393    pub fn take_finished_stream(&mut self) -> Option<Box<dyn Source + Send>> {
394        let slot = self.streams.iter_mut().find(|slot| slot.retired)?;
395        slot.retired = false;
396        slot.source.take()
397    }
398
399    /// A free voice's next id.
400    fn free_voice(&self) -> Result<VoiceId, Full> {
401        let index = self
402            .voices
403            .iter()
404            .position(|voice| !voice.active)
405            .ok_or(Full)?;
406        Ok(VoiceId(Handle {
407            index: u32::try_from(index).map_err(|_| Full)?,
408            generation: self.voices[index].generation.wrapping_add(1),
409        }))
410    }
411
412    /// Plays `sound`, or says [`Full`]. A sound no longer in the bank plays as one
413    /// with no frames.
414    pub fn play(&mut self, sound: SoundId, params: Params) -> Result<VoiceId, Full> {
415        let voice = self.free_voice()?;
416        self.start_sound(voice, sound, params);
417        Ok(voice)
418    }
419
420    fn start_sound(&mut self, voice: VoiceId, sound: SoundId, params: Params) {
421        let (origin, rate) = match self.sound_index(sound) {
422            Some(index) => {
423                let rate = self.sounds[index].as_ref().map_or(1, |p| p.format().rate);
424                (Origin::Sound(index), rate)
425            }
426            None => (Origin::Nothing, 1),
427        };
428        self.start_voice(voice, origin, rate, params);
429    }
430
431    fn start_voice(&mut self, voice: VoiceId, origin: Origin, rate: u32, params: Params) {
432        let output_rate = self.config.rate;
433        if let Some(slot) = usize::try_from(voice.0.index)
434            .ok()
435            .and_then(|index| self.voices.get_mut(index))
436        {
437            slot.start(voice.0.generation, origin, rate, output_rate, params);
438        }
439    }
440
441    /// Plays `source`, a stream that has its frames ready, or says [`Full`].
442    pub fn stream(
443        &mut self,
444        source: impl Source + Send + 'static,
445        params: Params,
446    ) -> Result<VoiceId, Full> {
447        self.free_voice()?;
448        let stream = self.add_stream(Box::new(source))?;
449        self.play_stream(stream, params)
450    }
451
452    /// Plays a stream [`add_stream`](Self::add_stream) holds, or says [`Full`]. A
453    /// stream plays once: one that was played, or is gone, plays as one with no
454    /// frames.
455    pub fn play_stream(&mut self, stream: StreamId, params: Params) -> Result<VoiceId, Full> {
456        let voice = self.free_voice()?;
457        self.start_stream(voice, stream, params);
458        Ok(voice)
459    }
460
461    fn start_stream(&mut self, voice: VoiceId, stream: StreamId, params: Params) {
462        let slot = usize::try_from(stream.0.index)
463            .ok()
464            .and_then(|index| self.streams.get_mut(index).map(|slot| (index, slot)))
465            .filter(|(_, slot)| slot.generation == stream.0.generation && !slot.used);
466        let (origin, rate) = match slot {
467            Some((index, slot)) => match slot.source.as_ref() {
468                Some(source) => {
469                    let rate = source.format().rate;
470                    slot.used = true;
471                    (Origin::Stream(index), rate)
472                }
473                None => (Origin::Nothing, 1),
474            },
475            None => (Origin::Nothing, 1),
476        };
477        self.start_voice(voice, origin, rate, params);
478    }
479
480    /// The voice `id` names, if it is still playing.
481    fn voice_mut(&mut self, id: VoiceId) -> Option<&mut Voice> {
482        let voice = self.voices.get_mut(usize::try_from(id.0.index).ok()?)?;
483        (voice.active && voice.generation == id.0.generation).then_some(voice)
484    }
485
486    fn voice(&self, id: VoiceId) -> Option<&Voice> {
487        let voice = self.voices.get(usize::try_from(id.0.index).ok()?)?;
488        (voice.active && voice.generation == id.0.generation).then_some(voice)
489    }
490
491    /// Jumps to `volume`, in 1/4096, ending any fade.
492    pub fn set_volume(&mut self, voice: VoiceId, volume: u16) {
493        if let Some(voice) = self.voice_mut(voice) {
494            voice.set_volume(volume);
495        }
496    }
497
498    /// Goes to `volume`, in 1/4096, in a straight line, over `millis`. A fade to
499    /// nothing leaves the voice playing silently, for the caller to stop.
500    pub fn fade_to(&mut self, voice: VoiceId, volume: u16, millis: u64) {
501        let frames = frames_of_millis(self.config.rate, millis);
502        if let Some(voice) = self.voice_mut(voice) {
503            voice.fade_to(volume, frames);
504        }
505    }
506
507    /// Sets the left and right gains, in 1/4096.
508    pub fn set_balance(&mut self, voice: VoiceId, left: u16, right: u16) {
509        if let Some(voice) = self.voice_mut(voice) {
510            voice.set_balance(left, right);
511        }
512    }
513
514    /// Ends `voice` now.
515    pub fn stop(&mut self, voice: VoiceId) {
516        let index = self.voice(voice).map(|_| voice.0.index);
517        if let Some(voice) = index
518            .and_then(|index| usize::try_from(index).ok())
519            .and_then(|index| self.voices.get_mut(index))
520        {
521            voice.finish(&mut self.streams);
522        }
523    }
524
525    /// Holds `voice` where it is, its fade too: it sounds nothing until
526    /// [`resume`](Self::resume).
527    pub fn pause(&mut self, voice: VoiceId) {
528        if let Some(voice) = self.voice_mut(voice) {
529            voice.paused = true;
530        }
531    }
532
533    /// Goes on from where [`pause`](Self::pause) held it.
534    pub fn resume(&mut self, voice: VoiceId) {
535        if let Some(voice) = self.voice_mut(voice) {
536            voice.paused = false;
537        }
538    }
539
540    /// Whether `voice` hasn't ended. A paused voice is playing.
541    pub fn is_playing(&self, voice: VoiceId) -> bool {
542        self.voice(voice).is_some()
543    }
544
545    /// Whether `voice` is held by [`pause`](Self::pause).
546    pub fn is_paused(&self, voice: VoiceId) -> bool {
547        self.voice(voice).is_some_and(|voice| voice.paused)
548    }
549
550    /// `voice`'s volume now, in 1/4096, if it is playing.
551    pub fn volume(&self, voice: VoiceId) -> Option<u16> {
552        self.voice(voice).map(Voice::volume)
553    }
554
555    /// How many voices are playing.
556    pub fn playing(&self) -> usize {
557        self.voices.iter().filter(|voice| voice.active).count()
558    }
559
560    /// How many frames, added over every stream so far, had nothing to play yet
561    /// because a worker hadn't got there. A voice stops where it is for those: a
562    /// stream is delayed, never skipped.
563    pub const fn underrun_frames(&self) -> u64 {
564        self.underrun_frames
565    }
566
567    /// A command from the queue. It can't be refused: the [`Remote`] chose a free
568    /// voice.
569    fn apply(&mut self, command: Command) {
570        match command {
571            Command::Play(voice, sound, params) => self.start_sound(voice, sound, params),
572            Command::Stream(voice, stream, params) => self.start_stream(voice, stream, params),
573            Command::SetVolume(voice, volume) => self.set_volume(voice, volume),
574            Command::FadeTo(voice, volume, millis) => self.fade_to(voice, volume, millis),
575            Command::SetBalance(voice, left, right) => self.set_balance(voice, left, right),
576            Command::Stop(voice) => self.stop(voice),
577            Command::Pause(voice) => self.pause(voice),
578            Command::Resume(voice) => self.resume(voice),
579        }
580    }
581
582    /// Mixes every voice into `out`, whole frames of interleaved samples (a part of
583    /// a frame at its end is silence), then removes those that finished. No lock, no
584    /// allocation, nothing freed.
585    pub fn render(&mut self, out: &mut [i16]) {
586        for chunk in out.chunks_mut(self.block.len()) {
587            let whole = chunk.len() - chunk.len() % self.channels;
588            let block = &mut self.block[..whole];
589            block.fill(0);
590            let mut sources = Sources {
591                sounds: &self.sounds,
592                streams: &mut self.streams,
593            };
594            for voice in &mut self.voices {
595                if voice.active && !voice.paused {
596                    self.underrun_frames += voice.mix(&mut sources, block, self.channels);
597                }
598            }
599            for (sample, mixed) in chunk.iter_mut().zip(block.iter()) {
600                *sample = clamp(*mixed);
601            }
602            chunk[whole..].fill(0);
603        }
604    }
605}
606
607/// How many whole frames play in `millis` at `rate`, at most `u64::MAX`.
608fn frames_of_millis(rate: u32, millis: u64) -> u64 {
609    let rate = u64::from(rate);
610    (millis / 1000)
611        .saturating_mul(rate)
612        .saturating_add(millis % 1000 * rate / 1000)
613}
614
615/// `value` as a sample: past the ends it stays at the end, it never wraps.
616fn clamp(value: i32) -> i16 {
617    i16::try_from(value.clamp(i32::from(i16::MIN), i32::from(i16::MAX))).unwrap_or(0)
618}