Skip to main content

audiobox/mixer/
mod.rs

1//! A real-time mixer for games. Requires the `mixer` feature.
2//!
3//! [`Mixer::new`] returns two halves. The [`Mixer`] goes to the audio callback, which
4//! calls [`Mixer::render`] for every buffer. The [`MixerHandle`] stays with the game,
5//! which starts voices, changes gains and pans, and stops them.
6//!
7//! ```
8//! use audiobox::mixer::{Fade, Loop, Mixer, MixerConfig, Play};
9//! use audiobox::Audio;
10//! use std::sync::Arc;
11//!
12//! let (mut mixer, mut handle) = Mixer::new(MixerConfig {
13//!     buses: vec!["music".into(), "effects".into()],
14//!     ..MixerConfig::default()
15//! })?;
16//! let music = handle.bus("music").unwrap();
17//! let effects = handle.bus("effects").unwrap();
18//!
19//! let beep = Arc::new(Audio::new(vec![vec![0.25; 4800]], 48_000)?);
20//! handle.play(beep.clone(), &Play { bus: effects, pan: -0.5, ..Play::default() })?;
21//! handle.play(beep, &Play { bus: music, looping: Loop::Whole, fade_in: Fade::linear(0.5), ..Play::default() })?;
22//! handle.set_bus_gain(music, 0.5, Fade::linear(1.0))?;
23//!
24//! // In the audio callback: interleaved stereo at 48 kHz.
25//! let mut buffer = [0.0f32; 2 * 512];
26//! mixer.render(&mut buffer);
27//! # Ok::<(), audiobox::Error>(())
28//! ```
29//!
30//! # Sounds
31//!
32//! A voice plays a [`Sound`]: decoded [`Audio`] shared through an [`Arc`], so that
33//! many voices play one sound without copying it, or a [`Stream`] that produces
34//! frames as it plays, such as a [`Decoder`](crate::stream::Decoder) of compressed
35//! music or a [`Synth`](crate::synth::Synth) rendering a score. Sounds are mono or stereo.
36//!
37//! Each voice has a gain, a pan, a bus, a priority, an optional fade-in, and a loop:
38//! off, the whole sound, or a region between two frames. Gain, pan and bus gain
39//! changes, and stops, can fade over a duration with a [`FadeCurve`].
40//!
41//! A voice also has a playback rate, which changes speed and pitch together, and a
42//! pitch offset in semitones on top of it. Both can change while the voice plays, with
43//! a ramp. [`Variation`] draws a random pitch and gain for each start of a repeated
44//! sound, such as a footstep, from a generator seeded by [`MixerConfig::seed`].
45//!
46//! A voice can start partway through its sound ([`Play::start`]), and be paused and
47//! resumed, optionally with a fade; it keeps its position, its fades and its loop.
48//! Pausing a bus pauses every voice routed through it and no others, so a pause menu
49//! can stop the game's sounds and keep its own. [`MixerHandle::voice_status`] reads a
50//! voice's state and position as of the last render, without blocking the audio side. A loop of a
51//! stream seeks it back, so a stream that loops must support seeking to the loop start.
52//!
53//! # Voices and buses
54//!
55//! By default at most [`MixerConfig::max_voices`] voices play at once. When a voice starts while
56//! that many play, it takes the place of the playing voice with the lowest priority,
57//! the oldest among equals, if that priority is not higher than its own. The voice it
58//! replaces fades out over 5 ms and reports [`MixerEvent::Stolen`]. Otherwise the new
59//! voice does not play and reports [`MixerEvent::Rejected`]. Give music a high
60//! priority so that effects never take its place.
61//!
62//! Opt into [`MixerConfig::virtual_voices`] to retain up to 4096 logical sources
63//! while selecting only `max_voices` physical voices. Quiet, distant and lower-ranked
64//! voices advance their clocks as [`VoiceState::Virtual`]. Memory sources skip PCM
65//! and rebuild history on promotion; stateful streams still consume/discard PCM.
66//! Selection uses priority, estimated source-peak level and start order at fixed
67//! 32-frame boundaries and command frames. Virtual/paused voices count against
68//! logical and instance capacity. Physical stop fades have eight extra budget slots.
69//!
70//! A sound can also limit its own voices with [`Instances`]: at most so many at once,
71//! with a policy for one more, and a minimum gap between starts. A refused start
72//! reports [`MixerEvent::Limited`].
73//!
74//! A voice can have a 2D position relative to the listener ([`Play::position`],
75//! [`MixerHandle::set_position`]), which sets its pan and a distance gain through the
76//! mixer's [`Space2d`].
77//!
78//! Every voice plays through a bus: the master bus, or one of the named buses of
79//! [`MixerConfig::buses`], which by default play through the master. [`BusRouting`]
80//! and [`MixerHandle::set_routing`] opt into nested groups and shared pre/post-fader
81//! [`BusSend`] routes, with stereo bus balance and explicit mute/solo path rules.
82//! A voice's samples are scaled
83//! by its gain, its fades, its pan, its bus's gain and the master gain.
84//! [`effects`] adds ordered bus chains, ramped filters, compression/ducking, delay
85//! and reverb. Sidechain dependencies order named buses before master processing.
86//!
87//! Many voices together can go beyond full scale. [`MixerConfig::limiter`] enables a
88//! [`MasterLimiter`] on the master bus, off by default, which keeps the output at or
89//! below its ceiling and passes quieter signals unchanged. Its look-ahead, 0 by
90//! default, delays the output by [`MixerHandle::latency`] frames.
91//!
92//! # Rendering
93//!
94//! [`Mixer::render`] fills an interleaved buffer of the output's channels (1/2/4/6/8) at
95//! the output's sample rate. It writes `out.len() / channels` whole frames and sets any
96//! remaining samples to 0; a buffer shorter than a frame renders nothing, but still
97//! applies the changes that are due.
98//!
99//! Sources at another sample rate, and voices whose rate or pitch differs from 1, are
100//! resampled with a 16-tap windowed-sinc filter
101//! (Blackman window, 256 interpolated phases, cutoff at 0.9 of the lower Nyquist
102//! frequency). It is designed for ratios up to 2:1, such as 44.1 kHz to 48 kHz;
103//! larger downsampling ratios alias more. A source at the output rate is copied
104//! sample for sample.
105//!
106//! [`Play::resampling`] can explicitly select [`ResamplingQuality::Linear`], cheaper
107//! two-point interpolation without an anti-alias filter. Sinc remains the default.
108//!
109//! Panning a mono source follows a constant-power law: `cos θ` on the left and
110//! `sin θ` on the right, with `θ = (pan + 1)·π/4`, so the centre is -3 dB on each side.
111//! A stereo source is balanced: the centre leaves it unchanged and the far side
112//! follows `√2·cos θ` or `√2·sin θ`, limited to 1. A mono output sums mono sources and
113//! averages the two channels of stereo ones.
114//!
115//! # Real-time safety
116//!
117//! [`Mixer::render`] allocates nothing, takes no lock that can wait and does not block.
118//! Changes reach it through a bounded single-producer, single-consumer queue of the
119//! crate's own, allocated when the mixer is created. The handle and the mixer each own
120//! one end and one counter, so a slot is only ever used by one side at a time; each slot
121//! is taken with `try_lock`, which never blocks and, under this protocol, is never
122//! contended. A full queue is reported to the handle; neither side ever waits. Voices
123//! and their buffers are built by the handle, on the game's thread. Finished voices go
124//! back to the handle through a second such queue and are freed by
125//! [`MixerHandle::poll_event`], so the audio thread frees no memory either. Poll events
126//! regularly, such as once per game frame.
127//!
128//! # Timing and determinism
129//!
130//! Every change applies at an output frame. [`MixerHandle::at`] schedules changes at an
131//! exact frame, counted from the first rendered frame; they apply there whatever the
132//! buffer sizes, splitting a buffer if needed. Changes sent without a frame apply at the
133//! start of the next render. The same changes at the same frames give the same samples
134//! on every platform: resampling, panning and fades use only basic IEEE arithmetic and
135//! the crate's own sine series.
136pub mod effects;
137#[cfg(feature = "events")]
138#[cfg_attr(docsrs, doc(cfg(feature = "events")))]
139pub mod events;
140mod limiter;
141#[cfg(feature = "music")]
142#[cfg_attr(docsrs, doc(cfg(feature = "music")))]
143pub mod music;
144mod queue;
145mod routing;
146pub use routing::{BusRouting, BusSend, MAX_BUS_ROUTES, RoutingId, SendPosition};
147pub mod spatial;
148mod status;
149mod virtualization;
150mod voice;
151pub use virtualization::{MAX_LOGICAL_VOICES, VIRTUAL_VOICE_QUANTUM, VirtualVoices};
152
153/// Per-voice interpolation quality. The default retains the original PCM/filter.
154#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
155pub enum ResamplingQuality {
156    #[default]
157    Sinc,
158    /// Two-point interpolation, without an anti-alias low-pass. Cheaper, with
159    /// greater high-frequency error/aliasing, especially when downsampling.
160    Linear,
161}
162pub use spatial::{
163    Attenuation, DistanceModel, Emitter, Listener, OutputLayout, Placement, SpatialConfig,
164};
165
166use std::sync::Arc;
167use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
168
169pub use limiter::MasterLimiter;
170use queue::{Consumer, Producer, PushError};
171pub use status::{VoiceState, VoiceStatus};
172use voice::{Feed, ONE, Ramp, Resampling, SincBank, SincTable, State, Voice};
173
174use crate::{Audio, Error, ErrorCode, FadeCurve, Limits, Result, TimePosition};
175
176/// Most frames rendered between changes of the voices' gains.
177const BLOCK: usize = 256;
178/// Extra voice slots for voices that fade out after a stop or a steal.
179const TAILS: usize = 8;
180const STEAL_FADE: f64 = 0.005;
181
182/// A source of interleaved frames that a voice plays as it goes.
183///
184/// [`read`](Stream::read) runs on the audio thread, so it must not allocate or block.
185pub trait Stream: Send {
186    fn sample_rate(&self) -> u32;
187    /// 1 or 2.
188    fn channels(&self) -> usize;
189    /// The stream's length in frames, if known.
190    fn frames(&self) -> Option<u64> {
191        None
192    }
193    /// Fills `out` with interleaved frames and returns how many; 0 at the end.
194    /// `ErrorCode::Underrun` is temporary: the mixer emits silence and retries
195    /// next render segment, preserving source position. Other errors fail the voice.
196    fn read(&mut self, out: &mut [f32]) -> Result<usize>;
197    /// Moves to `frame`, for loops.
198    fn seek(&mut self, frame: u64) -> Result<()>;
199}
200
201impl<B: AsRef<[u8]> + Send + 'static> Stream for crate::stream::Decoder<B> {
202    fn sample_rate(&self) -> u32 {
203        crate::stream::Decoder::sample_rate(self)
204    }
205    fn channels(&self) -> usize {
206        crate::stream::Decoder::channels(self)
207    }
208    fn frames(&self) -> Option<u64> {
209        crate::stream::Decoder::frames(self)
210    }
211    fn read(&mut self, out: &mut [f32]) -> Result<usize> {
212        crate::stream::Decoder::read(self, out)
213    }
214    fn seek(&mut self, frame: u64) -> Result<()> {
215        crate::stream::Decoder::seek(self, frame)
216    }
217}
218
219/// A synthesizer plays as a mono or stereo stream. It loops by itself when its
220/// [`RenderOptions::loops`](crate::synth::RenderOptions::loops) is `None`; as a voice
221/// loop it can only return to its start.
222#[cfg(feature = "synth")]
223#[cfg_attr(docsrs, doc(cfg(feature = "synth")))]
224impl Stream for crate::synth::Synth {
225    fn sample_rate(&self) -> u32 {
226        crate::synth::Synth::sample_rate(self)
227    }
228    fn channels(&self) -> usize {
229        crate::synth::Synth::channels(self)
230    }
231    fn read(&mut self, out: &mut [f32]) -> Result<usize> {
232        Ok(self.fill(out))
233    }
234    fn seek(&mut self, frame: u64) -> Result<()> {
235        if frame != 0 {
236            return Err(Error::invalid("A synthesizer can only seek to its start"));
237        }
238        self.reset();
239        Ok(())
240    }
241}
242
243/// What a voice plays.
244pub enum Sound {
245    Memory(Arc<Audio>),
246    Stream(Box<dyn Stream>),
247}
248impl Sound {
249    pub fn stream(stream: impl Stream + 'static) -> Self {
250        Self::Stream(Box::new(stream))
251    }
252}
253impl From<Arc<Audio>> for Sound {
254    fn from(audio: Arc<Audio>) -> Self {
255        Self::Memory(audio)
256    }
257}
258impl From<Audio> for Sound {
259    fn from(audio: Audio) -> Self {
260        Self::Memory(Arc::new(audio))
261    }
262}
263impl From<Box<dyn Stream>> for Sound {
264    fn from(stream: Box<dyn Stream>) -> Self {
265        Self::Stream(stream)
266    }
267}
268impl<B: AsRef<[u8]> + Send + 'static> From<crate::stream::Decoder<B>> for Sound {
269    fn from(decoder: crate::stream::Decoder<B>) -> Self {
270        Self::stream(decoder)
271    }
272}
273#[cfg(feature = "synth")]
274#[cfg_attr(docsrs, doc(cfg(feature = "synth")))]
275impl From<crate::synth::Synth> for Sound {
276    fn from(synth: crate::synth::Synth) -> Self {
277        Self::stream(synth)
278    }
279}
280
281/// Identifies a voice in commands and events. Unique for the life of a mixer.
282#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
283pub struct VoiceId(u64);
284
285/// A bus: [`BusId::MASTER`] or a named bus from [`MixerHandle::bus`].
286#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
287pub struct BusId(usize);
288impl BusId {
289    pub const MASTER: BusId = BusId(0);
290}
291
292/// Which part of a sound repeats.
293#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
294pub enum Loop {
295    #[default]
296    Off,
297    /// The whole sound, from its end back to its start.
298    Whole,
299    /// Plays from the start, then repeats the source frames `start..end`.
300    Region { start: u64, end: u64 },
301}
302
303/// A change over a duration, with a curve. Zero seconds changes at once.
304#[derive(Clone, Copy, Debug)]
305pub struct Fade {
306    pub seconds: f64,
307    pub curve: FadeCurve,
308}
309impl Fade {
310    pub const NONE: Fade = Fade {
311        seconds: 0.0,
312        curve: FadeCurve::Linear,
313    };
314    pub const fn linear(seconds: f64) -> Self {
315        Self {
316            seconds,
317            curve: FadeCurve::Linear,
318        }
319    }
320    pub const fn new(seconds: f64, curve: FadeCurve) -> Self {
321        Self { seconds, curve }
322    }
323}
324impl Default for Fade {
325    fn default() -> Self {
326        Self::NONE
327    }
328}
329
330/// How a voice starts playing.
331#[derive(Clone, Copy, Debug)]
332pub struct Play {
333    /// Interpolation quality fixed at voice creation. Default sinc; Linear has no
334    /// anti-alias filter and is cheaper. Integral unit-speed samples stay exact.
335    pub resampling: ResamplingQuality,
336    pub bus: BusId,
337    /// Linear gain, at least 0. Default 1.
338    pub gain: f32,
339    /// -1 (left) to 1 (right). Default 0.
340    pub pan: f32,
341    pub looping: Loop,
342    pub fade_in: Fade,
343    /// Decides which voice gives way when too many play. Default 128.
344    pub priority: u8,
345    /// Playback rate, which changes speed and pitch together: 2 plays twice as fast
346    /// and an octave higher. 1/16..=8. Default 1.
347    pub rate: f32,
348    /// Pitch offset in semitones on top of the rate; fractions are cents, so 0.01 is
349    /// one cent. -48..=48. Rate, pitch and [`Variation::pitch`] together span
350    /// 1/1024..=512 times the speed. Default 0.
351    pub pitch: f32,
352    /// Random variation of pitch and gain, drawn from the mixer's seed.
353    pub variation: Variation,
354    /// Where in the sound to start, in seconds or source frames; a negative position
355    /// counts from the end of a sound of known length. A stream seeks there before the
356    /// voice is sent. Default the start.
357    pub start: TimePosition,
358    /// Limits on how many voices of this sound play at once and how often it starts.
359    pub instances: Option<Instances>,
360    /// The sound's position relative to the listener, such as the camera's centre, in
361    /// the units of [`MixerConfig::space`]. It sets the voice's pan, replacing
362    /// [`Play::pan`], and a distance gain on top of [`Play::gain`]. Default none.
363    pub position: Option<[f32; 2]>,
364    /// Optional 3D point emitter. Takes precedence over pan; cannot combine with position.
365    pub emitter: Option<Emitter>,
366}
367
368/// How a 2D position relative to the listener becomes a pan and a gain.
369///
370/// The horizontal offset `x` pans the sound with the mixer's constant-power law:
371/// `pan = x / width`, limited to -1..=1, so a sound `width` units to the right is fully
372/// right. The distance `d = √(x² + y²)` sets a gain of 1 up to `min_distance` and 0 from
373/// `max_distance`; in between, [`Distance::Linear`] falls in a straight line and
374/// [`Distance::Inverse`] follows `min/d`, shifted and scaled so it reaches 0 at the
375/// maximum instead of jumping there.
376#[derive(Clone, Copy, Debug, PartialEq)]
377pub struct Space2d {
378    /// Horizontal offset at which a sound is fully to one side. Default 1.
379    pub width: f32,
380    /// Distance up to which a sound plays at full gain. Default 1.
381    pub min_distance: f32,
382    /// Distance from which a sound is silent. Default 10.
383    pub max_distance: f32,
384    pub model: Distance,
385}
386impl Default for Space2d {
387    fn default() -> Self {
388        Self {
389            width: 1.0,
390            min_distance: 1.0,
391            max_distance: 10.0,
392            model: Distance::Linear,
393        }
394    }
395}
396impl Space2d {
397    /// The pan and the distance gain of a sound at `[x, y]` from the listener.
398    pub fn place(&self, [x, y]: [f32; 2]) -> (f32, f32) {
399        let (x, y) = (x as f64, y as f64);
400        let pan = (x / self.width as f64).clamp(-1.0, 1.0);
401        let d = (x * x + y * y).sqrt();
402        let (min, max) = (self.min_distance as f64, self.max_distance as f64);
403        let gain = if d <= min {
404            1.0
405        } else if d >= max {
406            0.0
407        } else {
408            match self.model {
409                Distance::Linear => 1.0 - (d - min) / (max - min),
410                Distance::Inverse => {
411                    let floor = min / max;
412                    (min / d - floor) / (1.0 - floor)
413                }
414            }
415        };
416        (pan as f32, gain as f32)
417    }
418    fn validate(&self) -> Result<()> {
419        let ok = self.width.is_finite()
420            && self.width > 0.0
421            && self.min_distance.is_finite()
422            && self.min_distance >= 0.0
423            && self.max_distance.is_finite()
424            && self.max_distance > self.min_distance;
425        if ok {
426            Ok(())
427        } else {
428            Err(Error::invalid(
429                "A 2D space needs a positive width and 0 <= min_distance < max_distance, all finite",
430            ))
431        }
432    }
433}
434
435/// How gain falls with distance in a [`Space2d`].
436#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
437pub enum Distance {
438    #[default]
439    Linear,
440    Inverse,
441}
442
443/// Limits for one sound: at most `max` voices with the same key play at once, and a
444/// new one starts no sooner than `min_gap` seconds after the last, so that ten coins
445/// collected together do not sum into distortion.
446///
447/// When `max` voices already play, `policy` decides: refuse the new voice, or stop
448/// the oldest or the quietest one with a 5 ms fade. A refused start reports
449/// [`MixerEvent::Limited`]; a start within the gap is always refused. Limits are
450/// judged with the mixer's bound on voices and its priorities when the start is
451/// applied, so scheduled starts are judged at their own frame, and a start refused for
452/// any reason stops no voice and does not begin a gap. Only instances of no higher
453/// priority than the new voice give way to it. Each start is checked against
454/// its own gap, from the sound's last start. The mixer remembers the last starts of up
455/// to 1024 limited sounds within any 60 seconds; a limited start beyond that is refused.
456#[derive(Clone, Copy, Debug, PartialEq)]
457pub struct Instances {
458    /// Voices with the same key share the limits. [`Instances::of`] keys a sound by
459    /// its identity, so every voice of that audio counts, also through clones and
460    /// `Arc`s. Keys you pick yourself should be below 2^63; internal audio and event
461    /// identities occupy the upper half of the key space.
462    pub key: u64,
463    /// Most voices playing at once; 0 for no limit.
464    pub max: u16,
465    /// Seconds from one start to the next, 0..=60.
466    pub min_gap: f64,
467    pub policy: OverLimit,
468}
469impl Instances {
470    /// Limits keyed by `key`, with no limit set yet.
471    pub const fn new(key: u64) -> Self {
472        Self {
473            key,
474            max: 0,
475            min_gap: 0.0,
476            policy: OverLimit::Refuse,
477        }
478    }
479    /// Limits keyed by a decoded sound's identity, which no other audio value shares,
480    /// even after this one is freed.
481    pub fn of(sound: &Audio) -> Self {
482        Self::new(sound.id())
483    }
484    pub const fn max(self, max: u16) -> Self {
485        Self { max, ..self }
486    }
487    pub const fn min_gap(self, seconds: f64) -> Self {
488        Self {
489            min_gap: seconds,
490            ..self
491        }
492    }
493    pub const fn policy(self, policy: OverLimit) -> Self {
494        Self { policy, ..self }
495    }
496}
497
498/// What happens when a sound already plays its [`Instances::max`] voices.
499#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
500pub enum OverLimit {
501    /// The new voice does not start.
502    #[default]
503    Refuse,
504    /// The voice that started first fades out and the new one starts.
505    StopOldest,
506    /// The voice with the lowest gain fades out and the new one starts.
507    StopQuietest,
508}
509
510/// Sounds with instance limits whose last start the mixer remembers at once. An
511/// entry is kept for [`MAX_GAP`] seconds, the longest gap a start can ask for; a
512/// limited start that finds the table full is refused.
513const STARTS: usize = 1024;
514/// The longest minimum gap between starts, in seconds.
515const MAX_GAP: f64 = 60.0;
516impl Default for Play {
517    fn default() -> Self {
518        Self {
519            resampling: ResamplingQuality::Sinc,
520            bus: BusId::MASTER,
521            gain: 1.0,
522            pan: 0.0,
523            looping: Loop::Off,
524            fade_in: Fade::NONE,
525            priority: 128,
526            rate: 1.0,
527            pitch: 0.0,
528            variation: Variation::NONE,
529            start: TimePosition::Frames(0),
530            instances: None,
531            position: None,
532            emitter: None,
533        }
534    }
535}
536
537/// Random variation for sounds that repeat, such as footsteps, coins and impacts.
538///
539/// Each start draws a pitch offset uniformly within `±pitch` semitones and a gain
540/// within `±gain_db` decibels from the mixer's generator, seeded by
541/// [`MixerConfig::seed`] or [`MixerHandle::reseed`]. The same seed and the same
542/// sequence of starts give the same variations on every platform.
543#[derive(Clone, Copy, Debug, Default, PartialEq)]
544pub struct Variation {
545    /// 0..=24 semitones.
546    pub pitch: f32,
547    /// 0..=24 decibels.
548    pub gain_db: f32,
549}
550impl Variation {
551    pub const NONE: Variation = Variation {
552        pitch: 0.0,
553        gain_db: 0.0,
554    };
555}
556
557#[derive(Clone, Debug)]
558pub struct MixerConfig {
559    /// Opt into a bounded logical pool and physical selection. None preserves
560    /// legacy admission/stealing and all default PCM.
561    pub virtual_voices: Option<VirtualVoices>,
562    /// The output's sample rate. Default 48000.
563    pub sample_rate: u32,
564    /// Output layout: 1 mono, 2 stereo, 4 quad, 6 5.1, 8 7.1. Default 2.
565    pub channels: usize,
566    /// Most live physical voices, 1..=1024. Default 32. With virtualization disabled
567    /// this is also the live admission limit. Stop fades can use eight extra slots.
568    pub max_voices: usize,
569    /// Named buses besides the master, such as `music` and `effects`.
570    pub buses: Vec<String>,
571    /// Commands that can wait for the mixer, 1..=65536. Default 1024.
572    pub queue: usize,
573    /// Seed of the generator that draws each voice's [`Variation`]. Default 0.
574    pub seed: u64,
575    /// How positions of voices become pans and gains.
576    pub space: Space2d,
577    pub listener: Listener,
578    pub spatial: SpatialConfig,
579    /// A peak limiter on the master bus, off by default. Its look-ahead delays the
580    /// output, which [`MixerHandle::latency`] reports.
581    pub limiter: Option<MasterLimiter>,
582}
583impl Default for MixerConfig {
584    fn default() -> Self {
585        Self {
586            virtual_voices: None,
587            sample_rate: 48_000,
588            channels: 2,
589            max_voices: 32,
590            buses: Vec::new(),
591            queue: 1024,
592            seed: 0,
593            space: Space2d::default(),
594            listener: Listener::default(),
595            spatial: SpatialConfig::default(),
596            limiter: None,
597        }
598    }
599}
600
601/// Something that happened to a voice, reported by [`MixerHandle::poll_event`].
602#[derive(Clone, Copy, Debug, Eq, PartialEq)]
603#[non_exhaustive]
604pub enum MixerEvent {
605    /// The voice finished, was stopped, or finished fading out after being stolen.
606    Ended(VoiceId),
607    /// Another voice took this voice's place; it fades out, then ends.
608    Stolen(VoiceId),
609    /// The voice did not start because too many voices of higher priority play.
610    Rejected(VoiceId),
611    /// The voice did not start because of its [`Instances`] limit.
612    Limited(VoiceId),
613    /// The voice's stream failed and the voice stopped.
614    Failed(VoiceId, ErrorCode),
615    /// A bus effect chain was installed at this output frame.
616    EffectsApplied(BusId, u64),
617    /// An effect edit was refused; the existing chain and settings are unchanged.
618    EffectsRejected(BusId, ErrorCode),
619    /// A prepared audio graph/settings edit was installed at this output frame.
620    RoutingApplied(RoutingId, u64),
621    /// A graph conflicted with actual sidechain dependencies; prior state survives.
622    RoutingRejected(RoutingId, ErrorCode),
623    /// A whole music operation took effect at this output frame; a fade may continue.
624    #[cfg(feature = "music")]
625    MusicApplied(music::MusicId, u64),
626    /// A music operation was refused as a whole; the previous track is unchanged.
627    #[cfg(feature = "music")]
628    MusicRejected(music::MusicId, ErrorCode),
629}
630
631enum Action {
632    Routing(Box<routing::Edit>),
633    Effects(Box<effects::Edit>),
634    Effect(usize, usize, effects::Settings, u64, FadeCurve),
635    #[cfg(feature = "music")]
636    Music(Box<music::Request>),
637    Play(Box<Voice>),
638    Stop(VoiceId, u64, FadeCurve),
639    Gain(VoiceId, f64, u64, FadeCurve),
640    Pan(VoiceId, f64, u64, FadeCurve),
641    Rate(VoiceId, f64, u64, FadeCurve),
642    Pitch(VoiceId, f64, u64, FadeCurve),
643    Position(VoiceId, f64, f64, u64, FadeCurve),
644    Emitter(VoiceId, Emitter, u64, FadeCurve),
645    Listener(Listener, u64, FadeCurve),
646    Pause(VoiceId, u64, FadeCurve),
647    Resume(VoiceId, u64, FadeCurve),
648    PauseBus(usize, u64, FadeCurve),
649    ResumeBus(usize, u64, FadeCurve),
650    BusGain(usize, f64, u64, FadeCurve),
651    StopAll(u64, FadeCurve),
652}
653struct Command {
654    frame: Option<u64>,
655    sequence: u64,
656    action: Action,
657}
658struct Returned {
659    event: Option<MixerEvent>,
660    voice: Option<Box<Voice>>,
661    #[cfg(feature = "music")]
662    music: Option<Box<music::Request>>,
663}
664
665struct Bus {
666    gain: Ramp,
667    /// Gain of pause and resume fades.
668    pause: Ramp,
669    pausing: bool,
670    paused: bool,
671    buffer: Vec<f32>,
672    effects: Option<Box<effects::Chain>>,
673    effect_clock: u64,
674}
675
676/// The audio thread's half of the mixer. See the [module documentation](self).
677pub struct Mixer {
678    virtualization: Option<virtualization::Scheduler>,
679    voice_limit: usize,
680    routing: routing::AudioRouting,
681    effects: effects::AudioEffects,
682    rate: u32,
683    channels: usize,
684    listener: Listener,
685    spatial: SpatialConfig,
686    max_voices: usize,
687    frame: u64,
688    order: u64,
689    voices: Vec<Option<Box<Voice>>>,
690    buses: Vec<Bus>,
691    pending: Vec<Command>,
692    commands: Consumer<Command>,
693    returns: Producer<Returned>,
694    retired: Vec<Returned>,
695    clock: Arc<AtomicU64>,
696    status: Arc<status::Table>,
697    steal_fade: u64,
698    /// Keys of sounds with instance limits and the frame of each one's last start
699    /// (`None` for a free entry).
700    starts: [(u64, Option<u64>); STARTS],
701    /// Frames after which a start can no longer be within any gap.
702    max_gap: u64,
703    limiter: Option<limiter::Limiter>,
704    limiter_gain: Arc<AtomicU32>,
705}
706
707/// The game's half of the mixer: starts, changes and stops voices.
708pub struct MixerHandle {
709    /// Bounded source-peak metadata only; does not retain Audio allocations.
710    peaks: Vec<(u64, f64)>,
711    peak_cursor: usize,
712    peak_limit: usize,
713    routing: routing::ControlRouting,
714    effects: effects::ControlEffects,
715    rate: u32,
716    channels: usize,
717    buses: Vec<String>,
718    commands: Producer<Command>,
719    returns: Consumer<Returned>,
720    clock: Arc<AtomicU64>,
721    status: Arc<status::Table>,
722    next_voice: u64,
723    next_sequence: u64,
724    tables: Vec<(u32, Arc<SincTable>)>,
725    bank: Arc<SincBank>,
726    random: SplitMix,
727    space: Space2d,
728    latency: u64,
729    limited: bool,
730    limiter_gain: Arc<AtomicU32>,
731}
732
733/// SplitMix64: a small, fixed generator, so variations are the same on every platform.
734struct SplitMix(u64);
735impl SplitMix {
736    fn next(&mut self) -> u64 {
737        self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15);
738        let mut z = self.0;
739        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
740        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
741        z ^ (z >> 31)
742    }
743    /// Uniform in -1..1.
744    fn symmetric(&mut self) -> f64 {
745        (self.next() >> 11) as f64 / (1u64 << 52) as f64 - 1.0
746    }
747}
748
749impl Mixer {
750    pub fn new(config: MixerConfig) -> Result<(Mixer, MixerHandle)> {
751        let MixerConfig {
752            virtual_voices,
753            sample_rate,
754            channels,
755            max_voices,
756            buses,
757            queue,
758            seed,
759            space,
760            listener,
761            spatial,
762            limiter,
763        } = config;
764        space.validate()?;
765        listener.validate()?;
766        spatial.validate()?;
767        Limits::default().check(0, channels, sample_rate)?;
768        OutputLayout::from_channels(channels)?;
769        if !(1..=1024).contains(&max_voices) || !(1..=65536).contains(&queue) {
770            return Err(Error::invalid(
771                "max_voices must be 1..=1024 and queue 1..=65536",
772            ));
773        }
774        for (i, name) in buses.iter().enumerate() {
775            if name.is_empty() || buses[..i].contains(name) {
776                return Err(Error::invalid(format!(
777                    "Bus names must be unique and not empty: {name:?}"
778                )));
779            }
780        }
781        if buses.len() > 255 {
782            return Err(Error::invalid("A mixer has at most 255 named buses"));
783        }
784        // The limiter's buffers depend on the validated rate and channels.
785        let limiter = limiter
786            .map(|settings| limiter::Limiter::new(settings, sample_rate, channels))
787            .transpose()?;
788        let latency = limiter.as_ref().map_or(0, |l| l.latency() as u64);
789        let has_limiter = limiter.is_some();
790        let limiter_gain = Arc::new(AtomicU32::new(1f32.to_bits()));
791        let voice_limit = if let Some(settings) = virtual_voices {
792            if settings.capacity < max_voices
793                || settings.capacity > MAX_LOGICAL_VOICES
794                || !(0.0..=1.0).contains(&settings.threshold)
795            {
796                return Err(Error::invalid(
797                    "Virtual capacity must be max_voices..=4096 and threshold 0..=1",
798                ));
799            }
800            settings.capacity
801        } else {
802            max_voices
803        };
804        let slots = voice_limit + TAILS;
805        let (command_tx, command_rx) = queue::queue(queue);
806        // Per render, at most `slots + queue` voices end or are refused, and as many
807        // are stolen, so the way back holds every event and voice of a render.
808        #[cfg(not(feature = "music"))]
809        let returns_per_command = 1;
810        #[cfg(feature = "music")]
811        let returns_per_command = music::MAX_LAYERS + 1;
812        // A musical command may start MAX_LAYERS voices, plus return its container.
813        // Reserve their worst-case returns too; all storage is made off audio.
814        let returns = queue
815            .checked_mul(returns_per_command)
816            .and_then(|n| n.checked_add(slots))
817            .and_then(|n| n.checked_mul(2))
818            .ok_or_else(|| Error::limit("Mixer return capacity overflow"))?;
819        let (return_tx, return_rx) = queue::queue(returns);
820        let clock = Arc::new(AtomicU64::new(0));
821        let status = Arc::new(status::Table::new(slots));
822        let (audio_effects, control_effects) = effects::create(queue, buses.len() + 1);
823        let (audio_routing, control_routing) = routing::create(queue);
824        let mixer = Mixer {
825            virtualization: virtual_voices.map(|v| virtualization::Scheduler::new(v, slots)),
826            voice_limit,
827            routing: audio_routing,
828            effects: audio_effects,
829            rate: sample_rate,
830            channels,
831            listener,
832            spatial,
833            max_voices,
834            frame: 0,
835            order: 0,
836            voices: (0..slots).map(|_| None).collect(),
837            buses: (0..=buses.len())
838                .map(|_| Bus {
839                    gain: Ramp::constant(1.0),
840                    pause: Ramp::constant(1.0),
841                    pausing: false,
842                    paused: false,
843                    buffer: vec![0.0; BLOCK * channels],
844                    effects: None,
845                    effect_clock: 0,
846                })
847                .collect(),
848            pending: Vec::with_capacity(queue),
849            commands: command_rx,
850            returns: return_tx,
851            retired: Vec::with_capacity(returns),
852            clock: clock.clone(),
853            status: status.clone(),
854            steal_fade: (STEAL_FADE * sample_rate as f64).round() as u64,
855            starts: [(0, None); STARTS],
856            max_gap: (MAX_GAP * sample_rate as f64).round() as u64,
857            limiter,
858            limiter_gain: limiter_gain.clone(),
859        };
860        let handle = MixerHandle {
861            peaks: Vec::with_capacity(virtual_voices.map_or(0, |v| v.capacity)),
862            peak_cursor: 0,
863            peak_limit: virtual_voices.map_or(0, |v| v.capacity),
864            routing: control_routing,
865            effects: control_effects,
866            rate: sample_rate,
867            channels,
868            buses,
869            commands: command_tx,
870            returns: return_rx,
871            clock,
872            status,
873            next_voice: 0,
874            next_sequence: 0,
875            tables: Vec::new(),
876            bank: Arc::new(SincBank::new()),
877            random: SplitMix(seed),
878            space,
879            latency,
880            limited: has_limiter,
881            limiter_gain,
882        };
883        Ok((mixer, handle))
884    }
885    pub fn sample_rate(&self) -> u32 {
886        self.rate
887    }
888    pub fn channels(&self) -> usize {
889        self.channels
890    }
891    /// Frames rendered so far.
892    pub fn frame(&self) -> u64 {
893        self.frame
894    }
895    /// All retained voices, including virtual, paused and fading-out voices.
896    pub fn active_voices(&self) -> usize {
897        self.voices.iter().flatten().count()
898    }
899
900    /// Renders the next `out.len() / channels` frames into the interleaved `out`.
901    /// Allocates nothing, takes no lock and does not block.
902    pub fn render(&mut self, out: &mut [f32]) {
903        let ch = self.channels;
904        let frames = out.len() / ch;
905        out.fill(0.0);
906        self.receive();
907        let start = self.frame;
908        let end = start + frames as u64;
909        let mut cursor = start;
910        loop {
911            self.apply_due(cursor);
912            if cursor >= end {
913                break;
914            }
915            // Segments also end where a bus's pause fade does, so its voices freeze
916            // on that frame.
917            let pause_ends = self
918                .buses
919                .iter()
920                .filter(|b| b.pausing)
921                .map(|b| b.pause.end());
922            let next = self
923                .pending
924                .iter()
925                .filter_map(|c| c.frame)
926                .chain(pause_ends)
927                .filter(|&f| f > cursor)
928                .min()
929                .unwrap_or(end);
930            let mut stop = next.min(end).min(cursor + BLOCK as u64);
931            if self.virtualization.is_some() {
932                stop = stop.min(cursor - cursor % VIRTUAL_VOICE_QUANTUM + VIRTUAL_VOICE_QUANTUM);
933            }
934            let from = (cursor - start) as usize * ch;
935            let to = (stop - start) as usize * ch;
936            self.mix(cursor, &mut out[from..to]);
937            cursor = stop;
938        }
939        if let Some(limiter) = &mut self.limiter {
940            let lowest = limiter.process(&mut out[..frames * ch]);
941            self.limiter_gain
942                .store((lowest as f32).to_bits(), Ordering::Relaxed);
943        }
944        self.frame = end;
945        // Read each voice up to its position before publishing it, and retire any
946        // stream that fails doing so, also on renders of no frames.
947        for v in self.voices.iter_mut().flatten() {
948            v.settle();
949        }
950        self.retire_done();
951        self.select_physical(end);
952        self.publish();
953        self.send_returns();
954        self.effects.send_returns();
955        self.routing.send_returns();
956        self.clock.store(end, Ordering::Release);
957    }
958
959    fn receive(&mut self) {
960        // Refused music commands do not enter pending. They still consume this
961        // render's receive budget, even if a producer keeps refilling the queue.
962        let available = self.pending.capacity() - self.pending.len();
963        for _ in 0..available {
964            match self.commands.pop() {
965                Some(command) => {
966                    #[cfg(feature = "music")]
967                    let Some(command) = self.quantize_music(command) else {
968                        continue;
969                    };
970                    self.pending.push(command);
971                }
972                None => break,
973            }
974        }
975    }
976
977    /// Applies the pending commands due at `frame`, in order of frame and sending.
978    fn apply_due(&mut self, frame: u64) {
979        loop {
980            let due = self
981                .pending
982                .iter()
983                .enumerate()
984                .filter(|(_, c)| c.frame.unwrap_or(0) <= frame)
985                .min_by_key(|(_, c)| (c.frame.unwrap_or(0), c.sequence))
986                .map(|(i, _)| i);
987            let Some(index) = due else {
988                return;
989            };
990            let command = self.pending.swap_remove(index);
991            self.apply(frame, command.action);
992        }
993    }
994
995    fn voice_mut(&mut self, id: VoiceId) -> Option<&mut Voice> {
996        self.voices
997            .iter_mut()
998            .flatten()
999            .find(|v| v.id == id)
1000            .map(|v| &mut **v)
1001    }
1002
1003    fn slot_of(&self, id: VoiceId) -> Option<usize> {
1004        self.voices
1005            .iter()
1006            .position(|v| v.as_ref().is_some_and(|v| v.id == id))
1007    }
1008
1009    fn apply(&mut self, frame: u64, action: Action) {
1010        if let Some(scheduler) = &mut self.virtualization {
1011            scheduler.dirty = true;
1012        }
1013        match action {
1014            Action::Routing(edit) => self.apply_routing(frame, edit),
1015            Action::Effects(edit) => self.apply_effects(frame, edit),
1016            Action::Effect(bus, slot, settings, len, curve) => {
1017                self.apply_effect(bus, slot, settings, len, curve)
1018            }
1019            #[cfg(feature = "music")]
1020            Action::Music(request) => self.apply_music(frame, request),
1021            Action::Play(voice) => self.start(frame, voice),
1022            Action::Stop(id, len, curve) => {
1023                if let Some(slot) = self.slot_of(id) {
1024                    self.stop_slot(slot, frame, len, curve);
1025                }
1026                self.retire_done();
1027            }
1028            Action::Gain(id, gain, len, curve) => {
1029                if let Some(v) = self.voice_mut(id) {
1030                    v.gain.retarget(v.time(frame), gain, len, curve);
1031                }
1032            }
1033            Action::Pan(id, pan, len, curve) => {
1034                if let Some(v) = self.voice_mut(id) {
1035                    v.pan.retarget(v.time(frame), pan, len, curve);
1036                }
1037            }
1038            Action::Rate(id, rate, len, curve) => {
1039                if let Some(v) = self.voice_mut(id) {
1040                    v.rate.retarget(v.time(frame), rate, len, curve);
1041                }
1042            }
1043            Action::Pitch(id, pitch, len, curve) => {
1044                if let Some(v) = self.voice_mut(id) {
1045                    v.pitch.retarget(v.time(frame), pitch, len, curve);
1046                }
1047            }
1048            Action::Position(id, pan, gain, len, curve) => {
1049                if let Some(v) = self.voice_mut(id) {
1050                    if v.emitter.is_none() {
1051                        let time = v.time(frame);
1052                        v.pan.retarget(time, pan, len, curve);
1053                        v.distance.retarget(time, gain, len, curve);
1054                    }
1055                }
1056            }
1057            Action::Emitter(id, emitter, len, curve) => {
1058                let channels = self.channels;
1059                let placement = virtualization::placement(
1060                    &self.spatial,
1061                    &self.listener,
1062                    &emitter,
1063                    OutputLayout::from_channels(self.channels).unwrap(),
1064                    self.virtualization.is_some(),
1065                );
1066                if let Some(v) = self.voice_mut(id) {
1067                    v.emitter = Some(emitter);
1068                    v.update_spatial(placement, frame, len, curve, channels);
1069                }
1070            }
1071            Action::Listener(listener, len, curve) => {
1072                self.listener = listener;
1073                let layout = OutputLayout::from_channels(self.channels).unwrap();
1074                for v in self.voices.iter_mut().flatten() {
1075                    if let Some(emitter) = v.emitter {
1076                        let placement = virtualization::placement(
1077                            &self.spatial,
1078                            &listener,
1079                            &emitter,
1080                            layout,
1081                            self.virtualization.is_some(),
1082                        );
1083                        v.update_spatial(placement, frame, len, curve, layout.channels());
1084                    }
1085                }
1086            }
1087            Action::Pause(id, len, curve) => {
1088                if let Some(v) = self.voice_mut(id) {
1089                    v.pause(v.time(frame), len, curve);
1090                }
1091            }
1092            Action::Resume(id, len, curve) => {
1093                if let Some(v) = self.voice_mut(id) {
1094                    v.resume(v.time(frame), len, curve);
1095                }
1096            }
1097            Action::PauseBus(b, len, curve) => {
1098                let bus = &mut self.buses[b];
1099                if !bus.paused {
1100                    bus.pause.retarget(frame, 0.0, len, curve);
1101                    bus.pausing = len > 0;
1102                    bus.paused = len == 0;
1103                }
1104            }
1105            Action::ResumeBus(b, len, curve) => {
1106                let bus = &mut self.buses[b];
1107                if bus.paused || bus.pausing {
1108                    bus.paused = false;
1109                    bus.pausing = false;
1110                    bus.pause.retarget(frame, 1.0, len, curve);
1111                }
1112            }
1113            Action::BusGain(bus, gain, len, curve) => {
1114                self.buses[bus].gain.retarget(frame, gain, len, curve);
1115            }
1116            Action::StopAll(len, curve) => {
1117                for slot in 0..self.voices.len() {
1118                    self.stop_slot(slot, frame, len, curve);
1119                }
1120                self.retire_done();
1121            }
1122        }
1123    }
1124
1125    /// Refuses a voice before it starts.
1126    fn refuse(&mut self, voice: Box<Voice>, event: fn(VoiceId) -> MixerEvent) {
1127        let id = voice.id;
1128        self.retire(voice, Some(event(id)));
1129    }
1130
1131    /// Stops the voice in `slot`. A voice frozen by its own pause, its bus or the
1132    /// master bus cannot play out a fade, so it stops at once.
1133    fn stop_slot(&mut self, slot: usize, frame: u64, len: u64, curve: FadeCurve) {
1134        let master_paused = self.buses[0].paused;
1135        let buses = &self.buses;
1136        if let Some(v) = self.voices[slot].as_mut() {
1137            let frozen = buses[v.bus].paused || master_paused;
1138            v.stop(v.time(frame), len, curve, frozen);
1139        }
1140    }
1141
1142    fn start(&mut self, frame: u64, mut voice: Box<Voice>) {
1143        if let Some(emitter) = voice.emitter {
1144            let placement = virtualization::placement(
1145                &self.spatial,
1146                &self.listener,
1147                &emitter,
1148                OutputLayout::from_channels(self.channels).unwrap(),
1149                self.virtualization.is_some(),
1150            );
1151            voice.update_spatial(placement, frame, 0, FadeCurve::Linear, self.channels);
1152        }
1153        // Voices that played all their source no longer count against any limit. At
1154        // high speeds a voice can pass its source's end before reading it: read up to
1155        // the position, as its next frame would, to find out.
1156        for v in self.voices.iter_mut().flatten() {
1157            v.settle();
1158        }
1159        // Decide everything first, so that a refused voice changes nothing.
1160        let mut start_entry = None;
1161        if let Some(limit) = voice.instances {
1162            // The incoming start's own gap, measured from the sound's last start.
1163            let own = self
1164                .starts
1165                .iter()
1166                .position(|&(key, last)| key == limit.key && last.is_some());
1167            if let Some(last) = own.and_then(|i| self.starts[i].1) {
1168                if limit.min_gap > 0 && frame < last + limit.min_gap {
1169                    return self.refuse(voice, MixerEvent::Limited);
1170                }
1171            }
1172            // Its entry, a free one, or one too old to refuse any start. When every
1173            // entry is still needed, the start is refused rather than forgetting one.
1174            let max_gap = self.max_gap;
1175            let entry = own.or_else(|| {
1176                self.starts
1177                    .iter()
1178                    .position(|&(_, last)| last.is_none_or(|last| frame >= last + max_gap))
1179            });
1180            let Some(entry) = entry else {
1181                return self.refuse(voice, MixerEvent::Limited);
1182            };
1183            start_entry = Some(entry);
1184            if limit.max > 0 {
1185                let same = |v: &Voice| v.live() && v.instances.is_some_and(|l| l.key == limit.key);
1186                let candidates = || {
1187                    self.voices
1188                        .iter()
1189                        .enumerate()
1190                        .filter_map(|(i, v)| v.as_deref().filter(|v| same(v)).map(|v| (i, v)))
1191                };
1192                // Each start carries its own maximum, which may be lower than the
1193                // voices already playing: mark enough of them to fit.
1194                let playing = candidates().count();
1195                if playing >= limit.max as usize {
1196                    if limit.policy == OverLimit::Refuse {
1197                        return self.refuse(voice, MixerEvent::Limited);
1198                    }
1199                    // Only instances of no higher priority may give way, as with the
1200                    // mixer's bound; without enough of them, the start is refused.
1201                    let priority = voice.priority;
1202                    for _ in 0..playing + 1 - limit.max as usize {
1203                        let voices = &self.voices;
1204                        let unmarked = || {
1205                            voices.iter().enumerate().filter_map(|(i, v)| {
1206                                v.as_deref()
1207                                    .filter(|v| same(v) && !v.marked && v.priority <= priority)
1208                                    .map(|v| (i, v))
1209                            })
1210                        };
1211                        let pick = match limit.policy {
1212                            OverLimit::StopQuietest => unmarked().min_by(|(_, a), (_, b)| {
1213                                a.level(frame)
1214                                    .total_cmp(&b.level(frame))
1215                                    .then(a.order.cmp(&b.order))
1216                            }),
1217                            _ => unmarked().min_by_key(|(_, v)| v.order),
1218                        }
1219                        .map(|(i, _)| i);
1220                        match pick.and_then(|i| self.voices[i].as_mut()) {
1221                            Some(v) => v.marked = true,
1222                            None => {
1223                                for v in self.voices.iter_mut().flatten() {
1224                                    v.marked = false;
1225                                }
1226                                return self.refuse(voice, MixerEvent::Limited);
1227                            }
1228                        }
1229                    }
1230                }
1231            }
1232        }
1233        // The mixer's bound on voices, counting the marked voices as already gone.
1234        let others = || {
1235            self.voices
1236                .iter()
1237                .enumerate()
1238                .filter(|(_, v)| v.as_ref().is_some_and(|v| v.live() && !v.marked))
1239        };
1240        let mut global_victim = None;
1241        if others().count() >= self.voice_limit {
1242            let victim = others()
1243                .filter_map(|(i, v)| v.as_deref().map(|v| (i, v)))
1244                .min_by_key(|(_, v)| (v.priority, v.order));
1245            match victim {
1246                Some((i, v)) if v.priority <= voice.priority => global_victim = Some(i),
1247                _ => {
1248                    for v in self.voices.iter_mut().flatten() {
1249                        v.marked = false;
1250                    }
1251                    return self.refuse(voice, MixerEvent::Rejected);
1252                }
1253            }
1254        }
1255        if let Some(v) = global_victim.and_then(|i| self.voices[i].as_mut()) {
1256            v.marked = true;
1257        }
1258        // Admitted: stop the voices it replaces and record its start.
1259        for slot in 0..self.voices.len() {
1260            if self.voices[slot].as_ref().is_some_and(|v| v.marked) {
1261                self.stop_slot(slot, frame, self.steal_fade, FadeCurve::Linear);
1262                if let Some(v) = self.voices[slot].as_mut() {
1263                    v.marked = false;
1264                    let id = v.id;
1265                    self.event(MixerEvent::Stolen(id));
1266                }
1267            }
1268        }
1269        if let (Some(entry), Some(limit)) = (start_entry, voice.instances) {
1270            self.starts[entry] = (limit.key, Some(frame));
1271        }
1272        voice.order = self.order;
1273        self.order += 1;
1274        voice.start(frame);
1275        self.retire_done();
1276        let slot = match self.voices.iter().position(Option::is_none) {
1277            Some(slot) => slot,
1278            None => {
1279                // Every slot holds a live or fading voice: cut the oldest fading one.
1280                let oldest = self
1281                    .voices
1282                    .iter()
1283                    .enumerate()
1284                    .filter(|(_, v)| v.as_ref().is_some_and(|v| !v.live()))
1285                    .min_by_key(|(_, v)| v.as_ref().map(|v| v.order))
1286                    .map(|(i, _)| i);
1287                let Some(slot) = oldest else {
1288                    let id = voice.id;
1289                    self.retire(voice, Some(MixerEvent::Rejected(id)));
1290                    return;
1291                };
1292                let old = self.voices[slot].take().expect("slot is occupied");
1293                let id = old.id;
1294                let position = old.source_position();
1295                self.status.publish(
1296                    slot,
1297                    id,
1298                    VoiceStatus {
1299                        state: VoiceState::Finished,
1300                        position,
1301                    },
1302                );
1303                self.retire(old, Some(MixerEvent::Ended(id)));
1304                slot
1305            }
1306        };
1307        self.voices[slot] = Some(voice);
1308    }
1309
1310    fn mix(&mut self, first: u64, out: &mut [f32]) {
1311        self.select_physical(first);
1312        let ch = self.channels;
1313        let frames = out.len() / ch;
1314        for bus in &mut self.buses {
1315            bus.buffer[..frames * ch].fill(0.0);
1316        }
1317        let master_paused = self.buses[0].paused;
1318        for v in self.voices.iter_mut().flatten() {
1319            // A voice paused by itself, by its bus or by the master bus keeps still.
1320            if v.paused() || self.buses[v.bus].paused || master_paused {
1321                // A stopping voice that becomes frozen cannot finish its fade: end it,
1322                // as a stop that arrives during a pause does.
1323                if v.state == State::Stopping {
1324                    v.state = State::Finished;
1325                } else {
1326                    v.freeze(frames as u64);
1327                }
1328                continue;
1329            }
1330            let buffer = &mut self.buses[v.bus].buffer[..frames * ch];
1331            if v.virtualized {
1332                v.advance(first, frames);
1333            } else {
1334                v.render(first, frames, buffer, ch);
1335            }
1336        }
1337        self.retire_done();
1338        let end = first + frames as u64;
1339        if self.routing.graph.is_some() {
1340            self.mix_routing(first, out);
1341        } else if self.buses.iter().any(|bus| bus.effects.is_some()) {
1342            self.mix_effects(first, out);
1343        } else {
1344            let (master, master_pause) = (self.buses[0].gain, self.buses[0].pause);
1345            for (b, bus) in self.buses.iter().enumerate() {
1346                let constant = !bus.gain.changing(first)
1347                    && !bus.pause.changing(first)
1348                    && !master.changing(first)
1349                    && !master_pause.changing(first);
1350                let gain_at = |frame| {
1351                    let pause = if b == 0 {
1352                        master_pause.value(frame)
1353                    } else {
1354                        bus.pause.value(frame) * master_pause.value(frame)
1355                    };
1356                    bus_gain(b, &bus.gain, &master, frame) * pause
1357                };
1358                let fixed = gain_at(first);
1359                for j in 0..frames {
1360                    let g = if constant {
1361                        fixed
1362                    } else {
1363                        gain_at(first + j as u64)
1364                    };
1365                    for c in 0..ch {
1366                        out[j * ch + c] += (bus.buffer[j * ch + c] as f64 * g) as f32;
1367                    }
1368                }
1369            }
1370        }
1371        // Segments end where pause fades do, so a bus's voices freeze from here.
1372        for bus in &mut self.buses {
1373            if bus.pausing && bus.pause.end() <= end {
1374                bus.pausing = false;
1375                bus.paused = true;
1376            }
1377        }
1378    }
1379
1380    /// Publishes every voice's state for [`MixerHandle::voice_status`].
1381    fn publish(&self) {
1382        let master_paused = self.buses[0].paused;
1383        for (slot, voice) in self.voices.iter().enumerate() {
1384            if let Some(v) = voice {
1385                let state = if v.state == State::Stopping {
1386                    VoiceState::Stopping
1387                } else if v.paused() || self.buses[v.bus].paused || master_paused {
1388                    VoiceState::Paused
1389                } else if v.virtualized {
1390                    VoiceState::Virtual
1391                } else {
1392                    VoiceState::Playing
1393                };
1394                let position = v.source_position();
1395                self.status
1396                    .publish(slot, v.id, VoiceStatus { state, position });
1397            }
1398        }
1399    }
1400
1401    fn retire_done(&mut self) {
1402        for slot in 0..self.voices.len() {
1403            if self.voices[slot].as_ref().is_some_and(|v| v.done()) {
1404                let voice = self.voices[slot].take().expect("slot is occupied");
1405                let position = voice.source_position();
1406                self.status.publish(
1407                    slot,
1408                    voice.id,
1409                    VoiceStatus {
1410                        state: VoiceState::Finished,
1411                        position,
1412                    },
1413                );
1414                let event = match voice.state {
1415                    State::Failed(code) => MixerEvent::Failed(voice.id, code),
1416                    _ => MixerEvent::Ended(voice.id),
1417                };
1418                self.retire(voice, Some(event));
1419            }
1420        }
1421    }
1422
1423    fn event(&mut self, event: MixerEvent) {
1424        if self.retired.len() < self.retired.capacity() {
1425            self.retired.push(Returned {
1426                event: Some(event),
1427                voice: None,
1428                #[cfg(feature = "music")]
1429                music: None,
1430            });
1431        }
1432    }
1433
1434    /// Queues a finished voice to be freed by the handle. If the queue back to the
1435    /// handle is full because events are not polled, the voice is freed here.
1436    fn retire(&mut self, voice: Box<Voice>, event: Option<MixerEvent>) {
1437        if self.retired.len() < self.retired.capacity() {
1438            self.retired.push(Returned {
1439                event,
1440                voice: Some(voice),
1441                #[cfg(feature = "music")]
1442                music: None,
1443            });
1444        }
1445    }
1446
1447    fn send_returns(&mut self) {
1448        let mut sent = 0;
1449        while sent < self.retired.len() {
1450            let item = std::mem::replace(
1451                &mut self.retired[sent],
1452                Returned {
1453                    event: None,
1454                    voice: None,
1455                    #[cfg(feature = "music")]
1456                    music: None,
1457                },
1458            );
1459            match self.returns.push(item) {
1460                Ok(()) => sent += 1,
1461                Err(PushError::Full(item) | PushError::Disconnected(item)) => {
1462                    self.retired[sent] = item;
1463                    break;
1464                }
1465            }
1466        }
1467        self.retired.drain(..sent);
1468    }
1469}
1470
1471fn bus_gain(bus: usize, gain: &Ramp, master: &Ramp, frame: u64) -> f64 {
1472    if bus == 0 {
1473        master.value(frame)
1474    } else {
1475        gain.value(frame) * master.value(frame)
1476    }
1477}
1478
1479impl MixerHandle {
1480    fn source_peak(&mut self, audio: &Audio) -> f64 {
1481        if self.peak_limit == 0 {
1482            return 1.0;
1483        }
1484        if let Some((_, peak)) = self.peaks.iter().find(|(id, _)| *id == audio.id()) {
1485            return *peak;
1486        }
1487        let entry = (audio.id(), audio.peak());
1488        if self.peaks.len() < self.peak_limit {
1489            self.peaks.push(entry);
1490        } else {
1491            self.peaks[self.peak_cursor] = entry;
1492            self.peak_cursor = (self.peak_cursor + 1) % self.peak_limit;
1493        }
1494        entry.1
1495    }
1496    pub fn sample_rate(&self) -> u32 {
1497        self.rate
1498    }
1499    pub fn channels(&self) -> usize {
1500        self.channels
1501    }
1502    /// The named bus, if the mixer has it.
1503    pub fn bus(&self, name: &str) -> Option<BusId> {
1504        self.buses
1505            .iter()
1506            .position(|b| b == name)
1507            .map(|i| BusId(i + 1))
1508    }
1509    /// Frames the mixer had rendered when it last finished a render.
1510    pub fn frame(&self) -> u64 {
1511        self.clock.load(Ordering::Acquire)
1512    }
1513    /// A voice's state and position as of the mixer's last render, read without
1514    /// blocking. `None` if the mixer has not started the voice yet, or if it ended and
1515    /// its slot went to another voice; [`MixerHandle::poll_event`] reports endings.
1516    pub fn voice_status(&self, voice: VoiceId) -> Option<VoiceStatus> {
1517        self.status.read(voice)
1518    }
1519    /// Pauses a voice, fading it out first. It keeps its position, fades and loop.
1520    pub fn pause(&mut self, voice: VoiceId, fade: Fade) -> Result<()> {
1521        self.now().pause(voice, fade)
1522    }
1523    /// Resumes a paused voice, fading it in.
1524    pub fn resume(&mut self, voice: VoiceId, fade: Fade) -> Result<()> {
1525        self.now().resume(voice, fade)
1526    }
1527    /// Pauses voices directly assigned to a bus; [`BusId::MASTER`] pauses all.
1528    /// A paused group also gates routed input/freezes its inserts, but does not
1529    /// recursively freeze child sources. Use mute for advancing silent playback.
1530    /// Voices started directly on a paused bus wait for it to resume.
1531    pub fn pause_bus(&mut self, bus: BusId, fade: Fade) -> Result<()> {
1532        self.now().pause_bus(bus, fade)
1533    }
1534    pub fn resume_bus(&mut self, bus: BusId, fade: Fade) -> Result<()> {
1535        self.now().resume_bus(bus, fade)
1536    }
1537    /// Commands that apply at the start of the next render.
1538    pub fn now(&mut self) -> Commands<'_> {
1539        Commands {
1540            handle: self,
1541            frame: None,
1542        }
1543    }
1544    /// Commands that apply exactly at output frame `frame`, or at the next render if
1545    /// it has passed.
1546    pub fn at(&mut self, frame: u64) -> Commands<'_> {
1547        Commands {
1548            handle: self,
1549            frame: Some(frame),
1550        }
1551    }
1552    pub fn play(&mut self, sound: impl Into<Sound>, play: &Play) -> Result<VoiceId> {
1553        self.now().play(sound, play)
1554    }
1555    pub fn stop(&mut self, voice: VoiceId, fade: Fade) -> Result<()> {
1556        self.now().stop(voice, fade)
1557    }
1558    pub fn stop_all(&mut self, fade: Fade) -> Result<()> {
1559        self.now().stop_all(fade)
1560    }
1561    pub fn set_gain(&mut self, voice: VoiceId, gain: f32, fade: Fade) -> Result<()> {
1562        self.now().set_gain(voice, gain, fade)
1563    }
1564    /// Set legacy pan. A point emitter uses its direction instead.
1565    pub fn set_pan(&mut self, voice: VoiceId, pan: f32, fade: Fade) -> Result<()> {
1566        self.now().set_pan(voice, pan, fade)
1567    }
1568    pub fn set_rate(&mut self, voice: VoiceId, rate: f32, fade: Fade) -> Result<()> {
1569        self.now().set_rate(voice, rate, fade)
1570    }
1571    pub fn set_pitch(&mut self, voice: VoiceId, semitones: f32, fade: Fade) -> Result<()> {
1572        self.now().set_pitch(voice, semitones, fade)
1573    }
1574    /// Moves a voice to `position` relative to the listener, ramping its pan and
1575    /// distance gain over the fade so a moving sound does not zipper. A fade of about
1576    /// one game frame suits positions updated every frame.
1577    /// A 3D emitter ignores 2D pan/position commands; update it with `set_emitter`.
1578    pub fn set_position(&mut self, voice: VoiceId, position: [f32; 2], fade: Fade) -> Result<()> {
1579        self.now().set_position(voice, position, fade)
1580    }
1581    /// Enable/update permanent point-source mode. Stereo collapses immediately to
1582    /// (L+R)/2; fade ramps direction, attenuation and Doppler, not that conversion.
1583    pub fn set_emitter(&mut self, voice: VoiceId, emitter: Emitter, fade: Fade) -> Result<()> {
1584        self.now().set_emitter(voice, emitter, fade)
1585    }
1586    /// Schedule a global listener update; placement is recomputed on application.
1587    pub fn set_listener(&mut self, listener: Listener, fade: Fade) -> Result<()> {
1588        self.now().set_listener(listener, fade)
1589    }
1590    /// Output frames by which the master limiter's look-ahead delays the sound: a
1591    /// change scheduled at frame `f` is heard at `f + latency()`. 0 without a limiter.
1592    pub fn latency(&self) -> u64 {
1593        self.latency
1594    }
1595    /// The lowest gain the master limiter applied during the last render, from 0 to 1
1596    /// (1 when it did not reduce), for meters. `None` without a limiter.
1597    pub fn limiter_gain(&self) -> Option<f32> {
1598        self.limited
1599            .then(|| f32::from_bits(self.limiter_gain.load(Ordering::Relaxed)))
1600    }
1601    /// The 2D space that positions use.
1602    pub fn space(&self) -> Space2d {
1603        self.space
1604    }
1605    fn place(&self, position: [f32; 2]) -> Result<(f64, f64)> {
1606        if !position.iter().all(|c| c.is_finite()) {
1607            return Err(Error::invalid("A position must be finite"));
1608        }
1609        let (pan, gain) = self.space.place(position);
1610        Ok((pan as f64, gain as f64))
1611    }
1612    /// Restarts the generator of variations from `seed`.
1613    pub fn reseed(&mut self, seed: u64) {
1614        self.random = SplitMix(seed);
1615    }
1616    pub fn set_bus_gain(&mut self, bus: BusId, gain: f32, fade: Fade) -> Result<()> {
1617        self.now().set_bus_gain(bus, gain, fade)
1618    }
1619    /// Returns the next event, freeing finished voices and retired effect/routing
1620    /// edits on this thread. These queues are independent; combined polling order
1621    /// is not a chronological ordering across event kinds.
1622    pub fn poll_event(&mut self) -> Option<MixerEvent> {
1623        if let Some(event) = self.effects.poll() {
1624            return Some(event);
1625        }
1626        if let Some(event) = self.routing.poll() {
1627            return Some(event);
1628        }
1629        while let Some(returned) = self.returns.pop() {
1630            drop(returned.voice);
1631            #[cfg(feature = "music")]
1632            drop(returned.music);
1633            if returned.event.is_some() {
1634                return returned.event;
1635            }
1636        }
1637        None
1638    }
1639
1640    fn frames(&self, fade: Fade) -> Result<(u64, FadeCurve)> {
1641        if !fade.seconds.is_finite() || fade.seconds < 0.0 || fade.seconds > 3600.0 {
1642            return Err(Error::invalid("A fade lasts 0 to 3600 seconds"));
1643        }
1644        Ok(((fade.seconds * self.rate as f64).round() as u64, fade.curve))
1645    }
1646    fn table(&mut self, source_rate: u32) -> Arc<SincTable> {
1647        if let Some((_, table)) = self.tables.iter().find(|(r, _)| *r == source_rate) {
1648            return table.clone();
1649        }
1650        let table = Arc::new(SincTable::new(source_rate as f64 / self.rate as f64));
1651        self.tables.push((source_rate, table.clone()));
1652        table
1653    }
1654    fn send(&mut self, frame: Option<u64>, action: Action) -> Result<()> {
1655        let sequence = self.next_sequence;
1656        self.next_sequence += 1;
1657        let command = Command {
1658            frame,
1659            sequence,
1660            action,
1661        };
1662        match self.commands.push(command) {
1663            Ok(()) => Ok(()),
1664            Err(PushError::Full(_)) => Err(Error::limit("The mixer's command queue is full")),
1665            Err(PushError::Disconnected(_)) => Err(Error::invalid("The mixer was dropped")),
1666        }
1667    }
1668}
1669
1670/// Commands applied at one moment: see [`MixerHandle::now`] and [`MixerHandle::at`].
1671pub struct Commands<'a> {
1672    handle: &'a mut MixerHandle,
1673    frame: Option<u64>,
1674}
1675impl MixerHandle {
1676    /// Build a voice on the control thread, before publishing any command.
1677    fn prepare_voice(&mut self, sound: Sound, play: &Play) -> Result<Box<Voice>> {
1678        let handle = self;
1679        let (rate, channels, frames) = match &sound {
1680            Sound::Memory(audio) => (
1681                audio.sample_rate(),
1682                audio.channels(),
1683                Some(audio.frames() as u64),
1684            ),
1685            Sound::Stream(stream) => (stream.sample_rate(), stream.channels(), stream.frames()),
1686        };
1687        Limits::default().check(0, channels, rate)?;
1688        if !(1..=2).contains(&channels) {
1689            return Err(Error::invalid("A voice plays a mono or stereo sound"));
1690        }
1691        if !play.gain.is_finite() || play.gain < 0.0 || !play.pan.is_finite() {
1692            return Err(Error::invalid(
1693                "Gain must be finite and non-negative, and pan finite",
1694            ));
1695        }
1696        if play.bus.0 > handle.buses.len() {
1697            return Err(Error::invalid("Unknown bus"));
1698        }
1699        if let Some(emitter) = play.emitter {
1700            emitter.validate()?;
1701            if play.position.is_some() {
1702                return Err(Error::invalid("Choose 2D position or a 3D emitter"));
1703            }
1704        }
1705        check_rate(play.rate)?;
1706        check_pitch(play.pitch)?;
1707        let Variation {
1708            pitch: vary_pitch,
1709            gain_db: vary_gain,
1710        } = play.variation;
1711        if !(0.0..=24.0).contains(&vary_pitch) || !(0.0..=24.0).contains(&vary_gain) {
1712            return Err(Error::invalid("Variation ranges must be 0..=24"));
1713        }
1714        if let Loop::Region { start, end } = play.looping {
1715            if start >= end || frames.is_some_and(|n| end > n) {
1716                return Err(Error::invalid(
1717                    "A loop region needs start < end <= the sound's frames",
1718                ));
1719            }
1720        }
1721        let fade_in = handle.frames(play.fade_in)?;
1722        let start = start_frame(play.start, rate, frames)?;
1723        let step = (((rate as u128) << 32) + handle.rate as u128 / 2) / handle.rate as u128;
1724        let step = step as u64;
1725        let table =
1726            (step != ONE && play.resampling == ResamplingQuality::Sinc).then(|| handle.table(rate));
1727        let base_ratio = rate as f64 / handle.rate as f64;
1728        let mut gain = play.gain as f64;
1729        let mut pitch = play.pitch as f64;
1730        if vary_pitch > 0.0 {
1731            pitch += handle.random.symmetric() * vary_pitch as f64;
1732        }
1733        if vary_gain > 0.0 {
1734            let db = handle.random.symmetric() * vary_gain as f64;
1735            gain *= crate::math::exp2(db * std::f64::consts::LOG2_10 / 20.0);
1736        }
1737        let id = VoiceId(handle.next_voice);
1738        handle.next_voice += 1;
1739        let source_peak = match &sound {
1740            Sound::Memory(audio) => handle.source_peak(audio),
1741            Sound::Stream(_) => 1.0,
1742        };
1743        let feed = match sound {
1744            Sound::Memory(audio) => Feed::Memory(audio),
1745            Sound::Stream(stream) => Feed::Stream(stream),
1746        };
1747        let bank = handle.bank.clone();
1748        let resampling = Resampling {
1749            quality: play.resampling,
1750            step,
1751            base_ratio,
1752            table,
1753            bank,
1754        };
1755        let mut voice = Box::new(Voice::new(id, feed, channels, play.looping, resampling));
1756        voice.source_peak = source_peak;
1757        voice.bus = play.bus.0;
1758        voice.priority = play.priority;
1759        voice.gain = Ramp::constant(gain);
1760        voice.rate = Ramp::constant(play.rate as f64);
1761        voice.pitch = Ramp::constant(pitch);
1762        voice.pan = Ramp::constant(play.pan.clamp(-1.0, 1.0) as f64);
1763        voice.emitter = play.emitter;
1764        if let Some(position) = play.position {
1765            let (pan, distance) = handle.place(position)?;
1766            voice.pan = Ramp::constant(pan);
1767            voice.distance = Ramp::constant(distance);
1768        }
1769        voice.fade_in = fade_in;
1770        voice.set_start(start)?;
1771        if let Some(limit) = play.instances {
1772            if !limit.min_gap.is_finite() || !(0.0..=MAX_GAP).contains(&limit.min_gap) {
1773                return Err(Error::invalid("An instance gap must be 0..=60 seconds"));
1774            }
1775            voice.instances = Some(voice::InstanceLimit {
1776                key: limit.key,
1777                max: limit.max,
1778                min_gap: (limit.min_gap * handle.rate as f64).round() as u64,
1779                policy: limit.policy,
1780            });
1781        }
1782        Ok(voice)
1783    }
1784}
1785
1786impl Commands<'_> {
1787    /// Starts a voice and returns its id. Fails if the sound has more than two
1788    /// channels, a loop region is outside it, or the command queue is full.
1789    pub fn play(self, sound: impl Into<Sound>, play: &Play) -> Result<VoiceId> {
1790        let voice = self.handle.prepare_voice(sound.into(), play)?;
1791        let id = voice.id;
1792        self.handle.send(self.frame, Action::Play(voice))?;
1793        Ok(id)
1794    }
1795    /// Stops a voice, fading it out first. Unknown or ended voices are ignored.
1796    pub fn stop(self, voice: VoiceId, fade: Fade) -> Result<()> {
1797        let (len, curve) = self.handle.frames(fade)?;
1798        self.handle
1799            .send(self.frame, Action::Stop(voice, len, curve))
1800    }
1801    pub fn stop_all(self, fade: Fade) -> Result<()> {
1802        let (len, curve) = self.handle.frames(fade)?;
1803        self.handle.send(self.frame, Action::StopAll(len, curve))
1804    }
1805    pub fn set_gain(self, voice: VoiceId, gain: f32, fade: Fade) -> Result<()> {
1806        if !gain.is_finite() || gain < 0.0 {
1807            return Err(Error::invalid("Gain must be finite and non-negative"));
1808        }
1809        let (len, curve) = self.handle.frames(fade)?;
1810        self.handle
1811            .send(self.frame, Action::Gain(voice, gain as f64, len, curve))
1812    }
1813    /// Pans a voice, from -1 (left) to 1 (right).
1814    pub fn set_pan(self, voice: VoiceId, pan: f32, fade: Fade) -> Result<()> {
1815        if !pan.is_finite() {
1816            return Err(Error::invalid("Pan must be finite"));
1817        }
1818        let (len, curve) = self.handle.frames(fade)?;
1819        let pan = pan.clamp(-1.0, 1.0) as f64;
1820        self.handle
1821            .send(self.frame, Action::Pan(voice, pan, len, curve))
1822    }
1823    /// Changes a voice's playback rate, which changes speed and pitch together, over
1824    /// the fade. 1/16..=8.
1825    pub fn set_rate(self, voice: VoiceId, rate: f32, fade: Fade) -> Result<()> {
1826        check_rate(rate)?;
1827        let (len, curve) = self.handle.frames(fade)?;
1828        self.handle
1829            .send(self.frame, Action::Rate(voice, rate as f64, len, curve))
1830    }
1831    /// Changes a voice's pitch offset in semitones (fractions are cents) over the fade.
1832    /// -48..=48.
1833    pub fn set_pitch(self, voice: VoiceId, semitones: f32, fade: Fade) -> Result<()> {
1834        check_pitch(semitones)?;
1835        let (len, curve) = self.handle.frames(fade)?;
1836        self.handle.send(
1837            self.frame,
1838            Action::Pitch(voice, semitones as f64, len, curve),
1839        )
1840    }
1841    pub fn set_position(self, voice: VoiceId, position: [f32; 2], fade: Fade) -> Result<()> {
1842        let (pan, gain) = self.handle.place(position)?;
1843        let (len, curve) = self.handle.frames(fade)?;
1844        self.handle
1845            .send(self.frame, Action::Position(voice, pan, gain, len, curve))
1846    }
1847    pub fn pause(self, voice: VoiceId, fade: Fade) -> Result<()> {
1848        let (len, curve) = self.handle.frames(fade)?;
1849        self.handle
1850            .send(self.frame, Action::Pause(voice, len, curve))
1851    }
1852    /// Enable/update permanent point-source mode, using the listener at application
1853    /// time. Stereo collapses immediately; fade only ramps spatial parameters.
1854    pub fn set_emitter(self, voice: VoiceId, emitter: Emitter, fade: Fade) -> Result<()> {
1855        emitter.validate()?;
1856        let (len, curve) = self.handle.frames(fade)?;
1857        self.handle
1858            .send(self.frame, Action::Emitter(voice, emitter, len, curve))
1859    }
1860    /// Update every spatial voice, including paused voices and fading tails.
1861    pub fn set_listener(self, listener: Listener, fade: Fade) -> Result<()> {
1862        listener.validate()?;
1863        let (len, curve) = self.handle.frames(fade)?;
1864        self.handle
1865            .send(self.frame, Action::Listener(listener, len, curve))
1866    }
1867    pub fn resume(self, voice: VoiceId, fade: Fade) -> Result<()> {
1868        let (len, curve) = self.handle.frames(fade)?;
1869        self.handle
1870            .send(self.frame, Action::Resume(voice, len, curve))
1871    }
1872    pub fn pause_bus(self, bus: BusId, fade: Fade) -> Result<()> {
1873        if bus.0 > self.handle.buses.len() {
1874            return Err(Error::invalid("Unknown bus"));
1875        }
1876        let (len, curve) = self.handle.frames(fade)?;
1877        self.handle
1878            .send(self.frame, Action::PauseBus(bus.0, len, curve))
1879    }
1880    pub fn resume_bus(self, bus: BusId, fade: Fade) -> Result<()> {
1881        if bus.0 > self.handle.buses.len() {
1882            return Err(Error::invalid("Unknown bus"));
1883        }
1884        let (len, curve) = self.handle.frames(fade)?;
1885        self.handle
1886            .send(self.frame, Action::ResumeBus(bus.0, len, curve))
1887    }
1888    /// Sets a bus's gain; [`BusId::MASTER`] sets the master gain.
1889    pub fn set_bus_gain(self, bus: BusId, gain: f32, fade: Fade) -> Result<()> {
1890        if !gain.is_finite() || gain < 0.0 {
1891            return Err(Error::invalid("Gain must be finite and non-negative"));
1892        }
1893        if bus.0 > self.handle.buses.len() {
1894            return Err(Error::invalid("Unknown bus"));
1895        }
1896        let (len, curve) = self.handle.frames(fade)?;
1897        self.handle
1898            .send(self.frame, Action::BusGain(bus.0, gain as f64, len, curve))
1899    }
1900}
1901
1902fn check_rate(rate: f32) -> Result<()> {
1903    if (1.0 / 16.0..=8.0).contains(&rate) {
1904        Ok(())
1905    } else {
1906        Err(Error::invalid("Playback rate must be 1/16..=8"))
1907    }
1908}
1909fn check_pitch(semitones: f32) -> Result<()> {
1910    if (-48.0..=48.0).contains(&semitones) {
1911        Ok(())
1912    } else {
1913        Err(Error::invalid("Pitch must be -48..=48 semitones"))
1914    }
1915}
1916
1917/// The source frame where a voice starts.
1918fn start_frame(start: TimePosition, rate: u32, frames: Option<u64>) -> Result<u64> {
1919    let position = match start {
1920        TimePosition::Frames(n) => n as f64,
1921        TimePosition::Seconds(seconds) => seconds * rate as f64,
1922    };
1923    if !position.is_finite() {
1924        return Err(Error::invalid("The start must be finite"));
1925    }
1926    let position = position.round();
1927    let frame = if position < 0.0 {
1928        let total = frames.ok_or_else(|| {
1929            Error::invalid("A start counted from the end needs a sound of known length")
1930        })?;
1931        (total as f64 + position).max(0.0)
1932    } else {
1933        position
1934    };
1935    if frames.is_some_and(|total| frame > total as f64) {
1936        return Err(Error::invalid("The start is past the end of the sound"));
1937    }
1938    Ok(frame as u64)
1939}