Skip to main content

tono_core/runtime/
mixer.rs

1//! The routing [`Mixer`]: input/FX buses with insert chains, faders, and
2//! post-fader sends over any set of [`AudioSource`]s.
3
4use super::SCRATCH_FRAMES;
5use super::source::AudioSource;
6use crate::dsl::{ENGINE_VERSION, Node};
7use crate::streaming::EffectChain;
8
9/// Handle to a source added to a [`Mixer`].
10#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
11pub struct SourceId(u64);
12
13/// Blanket-implemented for every source, so a [`Mixer`] can hand back a typed
14/// `&mut` to a source it owns without forcing `Any` onto the public
15/// [`AudioSource`] trait (every plain `fill`-only adapter stays unencumbered).
16trait AnySource: AudioSource + std::any::Any {}
17impl<T: AudioSource + 'static> AnySource for T {}
18
19struct MixedSource {
20    id: u64,
21    source: Box<dyn AnySource + Send>,
22    gain: f32,
23    /// The bus this source feeds ([`BusId::MASTER`] by default).
24    bus: BusId,
25}
26
27/// Handle to a bus in a [`Mixer`] — an input group or an FX/return bus. The
28/// master bus is always [`BusId::MASTER`]. The handle's value is the bus's index
29/// in the mixer, so it is stable for the mixer's life.
30#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
31pub struct BusId(u32);
32
33impl BusId {
34    /// The always-present master bus. Every source, dry bus output, and FX
35    /// return sums here, and the master insert chain is the final stage.
36    pub const MASTER: BusId = BusId(0);
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Debug)]
40enum BusKind {
41    Master,
42    Input,
43    Fx,
44}
45
46/// Why a [`Mixer`] bus operation failed.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum MixerError {
49    /// An effect node is outside the real-time-streamable subset.
50    NotStreamable,
51    /// The bus handle names no live bus (foreign or stale).
52    UnknownBus,
53}
54
55impl std::fmt::Display for MixerError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            MixerError::NotStreamable => {
59                write!(f, "effect chain contains a non-streamable node")
60            }
61            MixerError::UnknownBus => {
62                write!(f, "unknown bus (foreign or stale BusId)")
63            }
64        }
65    }
66}
67
68impl std::error::Error for MixerError {}
69
70/// One bus: a summing point with an optional insert chain, a fader, post-fader
71/// sends to FX buses, and a dry level into master.
72struct Bus {
73    name: String,
74    kind: BusKind,
75    gain: f32,
76    /// Dry level into master (0 = send-only). Unused by the master bus.
77    to_master: f32,
78    /// A stereo insert chain (identical coefficients, independent L/R state).
79    inserts: Option<(EffectChain, EffectChain)>,
80    /// Post-fader sends into FX buses, as `(target bus index, level)`.
81    sends: Vec<(u32, f32)>,
82}
83
84/// A routing stereo mixer of audio sources — instruments, the SFX [`Engine`](super::Engine),
85/// [`StreamSource`](super::StreamSource)s. Each source feeds a **bus**; buses carry live insert
86/// chains (reverb / EQ / compressor / …) and post-fader **sends** into shared
87/// FX/return buses, all summing through a **master** insert chain. It is itself
88/// an [`AudioSource`], so it feeds one output callback (or nests).
89///
90/// With no buses or effects created, every source sits on the master bus at unity
91/// and the output is a plain additive sum — byte-identical to a bare mixer. Live
92/// effects need a sample rate: build with [`Mixer::new`].
93/// ```
94/// use tono_core::prelude::*;
95/// use tono_core::dsl::Node;
96///
97/// let mut mixer = Mixer::new(48_000);
98/// let sfx = mixer.bus("sfx");
99///
100/// let mut engine = Engine::new(48_000);
101/// let drone = engine.load(&SoundDoc::new("drone", Node::Sine { freq: 110.0.into() }));
102/// engine.play_looping(drone);
103/// mixer.add_to(sfx, engine);             // the engine feeds the sfx bus
104/// mixer.set_bus_gain(sfx, 0.8);          // a live fader
105///
106/// let mut out = vec![0.0f32; 512];
107/// mixer.fill(&mut out);                  // one callback serves the whole desk
108/// assert!(out.iter().any(|s| s.abs() > 0.0));
109/// ```
110pub struct Mixer {
111    sources: Vec<MixedSource>,
112    /// `buses[0]` is always the master bus; a bus's index equals its [`BusId`].
113    buses: Vec<Bus>,
114    next_id: u64,
115    sample_rate: u32,
116    // Reused planar scratch (grown lazily, never shrunk).
117    scratch: Vec<f32>,
118    master_l: Vec<f32>,
119    master_r: Vec<f32>,
120    bus_l: Vec<f32>,
121    bus_r: Vec<f32>,
122    /// Per-bus FX input accumulators (indexed by bus index; only FX slots used).
123    fx_in: Vec<(Vec<f32>, Vec<f32>)>,
124}
125
126impl Mixer {
127    /// An empty mixer that renders at `sample_rate`, so buses can carry live
128    /// effect chains (every other runtime constructor — `Engine::new`,
129    /// `AdaptiveMusic::new`, `Instrument::new` — takes the rate up front too).
130    pub fn new(sample_rate: u32) -> Self {
131        Mixer::build(sample_rate)
132    }
133
134    fn build(sample_rate: u32) -> Self {
135        let master = Bus {
136            name: "master".into(),
137            kind: BusKind::Master,
138            gain: 1.0,
139            to_master: 1.0,
140            inserts: None,
141            sends: Vec::new(),
142        };
143        Mixer {
144            sources: Vec::new(),
145            buses: vec![master],
146            next_id: 1,
147            sample_rate,
148            // Pre-sized so common host blocks never allocate in `fill` (the
149            // grow() calls stay as the fallback for bigger ones).
150            scratch: vec![0.0; SCRATCH_FRAMES * 2],
151            master_l: vec![0.0; SCRATCH_FRAMES],
152            master_r: vec![0.0; SCRATCH_FRAMES],
153            bus_l: vec![0.0; SCRATCH_FRAMES],
154            bus_r: vec![0.0; SCRATCH_FRAMES],
155            fx_in: Vec::new(),
156        }
157    }
158
159    /// Add a source to the master bus at unity gain; returns its handle.
160    pub fn add(&mut self, source: impl AudioSource + Send + 'static) -> SourceId {
161        self.add_to(BusId::MASTER, source)
162    }
163
164    /// Add a source to a specific bus at unity gain; returns its handle. An
165    /// unknown bus falls back to master.
166    pub fn add_to(&mut self, bus: BusId, source: impl AudioSource + Send + 'static) -> SourceId {
167        let bus = if (bus.0 as usize) < self.buses.len() {
168            bus
169        } else {
170            BusId::MASTER
171        };
172        let id = self.next_id;
173        self.next_id += 1;
174        self.sources.push(MixedSource {
175            id,
176            source: Box::new(source),
177            gain: 1.0,
178            bus,
179        });
180        SourceId(id)
181    }
182
183    /// Create an input bus (sources → inserts → master, with optional sends).
184    pub fn bus(&mut self, name: impl Into<String>) -> BusId {
185        self.push_bus(name.into(), BusKind::Input, None)
186    }
187
188    /// Create an FX/return bus with an insert chain fed only by sends. Returns
189    /// [`MixerError`] if the effects aren't streamable or the mixer has no rate.
190    pub fn fx_bus(
191        &mut self,
192        name: impl Into<String>,
193        effects: Vec<Node>,
194    ) -> Result<BusId, MixerError> {
195        let inserts = self.build_chain(&effects)?;
196        Ok(self.push_bus(name.into(), BusKind::Fx, inserts))
197    }
198
199    fn push_bus(
200        &mut self,
201        name: String,
202        kind: BusKind,
203        inserts: Option<(EffectChain, EffectChain)>,
204    ) -> BusId {
205        // A bus's id is its index — buses are only ever pushed, never removed.
206        let id = self.buses.len() as u32;
207        self.buses.push(Bus {
208            name,
209            kind,
210            gain: 1.0,
211            to_master: 1.0,
212            inserts,
213            sends: Vec::new(),
214        });
215        BusId(id)
216    }
217
218    /// Look up a bus by name.
219    pub fn bus_named(&self, name: &str) -> Option<BusId> {
220        self.buses
221            .iter()
222            .position(|b| b.name == name)
223            .map(|i| BusId(i as u32))
224    }
225
226    /// Set (or clear, with an empty list) a bus's insert chain. Works on any bus,
227    /// including master. Returns [`MixerError::UnknownBus`] for a foreign/stale
228    /// handle (it used to build the chain and silently discard it).
229    pub fn set_bus_effects(&mut self, bus: BusId, effects: Vec<Node>) -> Result<(), MixerError> {
230        let inserts = self.build_chain(&effects)?;
231        match self.buses.get_mut(bus.0 as usize) {
232            Some(b) => {
233                b.inserts = inserts;
234                Ok(())
235            }
236            None => Err(MixerError::UnknownBus),
237        }
238    }
239
240    /// Set the master insert chain (a convenience for `set_bus_effects(MASTER, …)`).
241    pub fn master_effects(&mut self, effects: Vec<Node>) -> Result<(), MixerError> {
242        self.set_bus_effects(BusId::MASTER, effects)
243    }
244
245    /// Set a post-fader send from an input bus into an FX bus. A no-op unless
246    /// `from` is an input bus and `to_fx` is an FX bus.
247    pub fn set_send(&mut self, from: BusId, to_fx: BusId, level: f32) {
248        let valid = matches!(
249            self.buses.get(from.0 as usize).map(|b| b.kind),
250            Some(BusKind::Input)
251        ) && matches!(
252            self.buses.get(to_fx.0 as usize).map(|b| b.kind),
253            Some(BusKind::Fx)
254        );
255        if !valid {
256            return;
257        }
258        let level = level.max(0.0);
259        let bus = &mut self.buses[from.0 as usize];
260        if let Some(s) = bus.sends.iter_mut().find(|s| s.0 == to_fx.0) {
261            s.1 = level;
262        } else {
263            bus.sends.push((to_fx.0, level));
264        }
265    }
266
267    /// Set a bus fader (0 = silent). Applies to the bus's dry output and its sends.
268    pub fn set_bus_gain(&mut self, bus: BusId, gain: f32) {
269        if let Some(b) = self.buses.get_mut(bus.0 as usize) {
270            b.gain = gain.max(0.0);
271        }
272    }
273
274    /// Set a bus's dry level into master (0 = send-only). No-op for master.
275    pub fn set_bus_dry(&mut self, bus: BusId, level: f32) {
276        if bus != BusId::MASTER
277            && let Some(b) = self.buses.get_mut(bus.0 as usize)
278        {
279            b.to_master = level.max(0.0);
280        }
281    }
282
283    /// Build a paired L/R insert chain from effect nodes (empty → `None`).
284    fn build_chain(
285        &self,
286        effects: &[Node],
287    ) -> Result<Option<(EffectChain, EffectChain)>, MixerError> {
288        if effects.is_empty() {
289            return Ok(None);
290        }
291        let sr = self.sample_rate;
292        let build = || EffectChain::try_new(effects, sr, ENGINE_VERSION);
293        let l = build().ok_or(MixerError::NotStreamable)?;
294        let r = build().ok_or(MixerError::NotStreamable)?;
295        Ok(Some((l, r)))
296    }
297
298    /// Set a source's gain (no-op for an unknown handle).
299    pub fn set_gain(&mut self, id: SourceId, gain: f32) {
300        if let Some(s) = self.sources.iter_mut().find(|s| s.id == id.0) {
301            s.gain = gain.max(0.0);
302        }
303    }
304
305    /// Mutable access to an added source, downcast to its concrete type — e.g. to
306    /// call [`Instrument::note_on`](crate::instrument::Instrument::note_on).
307    pub fn get_mut<T: AudioSource + 'static>(&mut self, id: SourceId) -> Option<&mut T> {
308        let s = self.sources.iter_mut().find(|s| s.id == id.0)?;
309        let any: &mut dyn std::any::Any = s.source.as_mut();
310        any.downcast_mut::<T>()
311    }
312
313    /// Remove a source.
314    pub fn remove(&mut self, id: SourceId) {
315        self.sources.retain(|s| s.id != id.0);
316    }
317
318    /// Number of sources in the mix.
319    pub fn source_count(&self) -> usize {
320        self.sources.len()
321    }
322
323    /// Whether `id` still refers to a source in the mix.
324    pub fn contains(&self, id: SourceId) -> bool {
325        self.sources.iter().any(|s| s.id == id.0)
326    }
327}
328
329/// Grow `v` to at least `n` samples (never shrinks).
330fn grow(v: &mut Vec<f32>, n: usize) {
331    if v.len() < n {
332        v.resize(n, 0.0);
333    }
334}
335
336impl AudioSource for Mixer {
337    fn fill(&mut self, out: &mut [f32]) -> usize {
338        let frames = out.len() / 2;
339
340        // Take the reusable planar buffers out so we can also borrow
341        // self.sources / self.buses mutably during routing.
342        let mut scratch = std::mem::take(&mut self.scratch);
343        let mut master_l = std::mem::take(&mut self.master_l);
344        let mut master_r = std::mem::take(&mut self.master_r);
345        let mut bus_l = std::mem::take(&mut self.bus_l);
346        let mut bus_r = std::mem::take(&mut self.bus_r);
347        let mut fx_in = std::mem::take(&mut self.fx_in);
348
349        grow(&mut scratch, frames * 2);
350        grow(&mut master_l, frames);
351        grow(&mut master_r, frames);
352        grow(&mut bus_l, frames);
353        grow(&mut bus_r, frames);
354        if fx_in.len() < self.buses.len() {
355            fx_in.resize_with(self.buses.len(), || (Vec::new(), Vec::new()));
356        }
357        for (l, r) in fx_in.iter_mut() {
358            grow(l, frames);
359            grow(r, frames);
360        }
361
362        master_l[..frames].fill(0.0);
363        master_r[..frames].fill(0.0);
364        for (l, r) in fx_in.iter_mut() {
365            l[..frames].fill(0.0);
366            r[..frames].fill(0.0);
367        }
368
369        let scr = &mut scratch[..frames * 2];
370
371        // 1a. Master-direct sources sum straight into the master accumulator.
372        for s in self.sources.iter_mut().filter(|s| s.bus == BusId::MASTER) {
373            s.source.fill(scr);
374            for f in 0..frames {
375                master_l[f] += scr[f * 2] * s.gain;
376                master_r[f] += scr[f * 2 + 1] * s.gain;
377            }
378        }
379
380        // 1b. Input buses: sum sources → inserts → dry to master + post-fader sends.
381        for bi in 1..self.buses.len() {
382            if self.buses[bi].kind != BusKind::Input {
383                continue;
384            }
385            bus_l[..frames].fill(0.0);
386            bus_r[..frames].fill(0.0);
387            let bus_id = bi as u32;
388            for s in self.sources.iter_mut().filter(|s| s.bus.0 == bus_id) {
389                s.source.fill(scr);
390                for f in 0..frames {
391                    bus_l[f] += scr[f * 2] * s.gain;
392                    bus_r[f] += scr[f * 2 + 1] * s.gain;
393                }
394            }
395            if let Some((cl, cr)) = &mut self.buses[bi].inserts {
396                cl.process(&mut bus_l[..frames]);
397                cr.process(&mut bus_r[..frames]);
398            }
399            let fader = self.buses[bi].gain;
400            let dry = fader * self.buses[bi].to_master;
401            for f in 0..frames {
402                master_l[f] += bus_l[f] * dry;
403                master_r[f] += bus_r[f] * dry;
404            }
405            for &(target, level) in &self.buses[bi].sends {
406                let k = target as usize;
407                if k < fx_in.len() {
408                    let g = fader * level;
409                    let (fl, fr) = &mut fx_in[k];
410                    for f in 0..frames {
411                        fl[f] += bus_l[f] * g;
412                        fr[f] += bus_r[f] * g;
413                    }
414                }
415            }
416        }
417
418        // 1c. Sources routed directly onto an FX bus are wet-only: sum them into
419        // that bus's accumulator so its inserts process them like any send.
420        // Without this a source added with `add_to(fx_bus, ..)` is never mixed and
421        // its play head never advances.
422        #[allow(clippy::needless_range_loop)]
423        for bi in 1..self.buses.len() {
424            if self.buses[bi].kind != BusKind::Fx {
425                continue;
426            }
427            let bus_id = bi as u32;
428            let (fl, fr) = &mut fx_in[bi];
429            for s in self.sources.iter_mut().filter(|s| s.bus.0 == bus_id) {
430                s.source.fill(scr);
431                for f in 0..frames {
432                    fl[f] += scr[f * 2] * s.gain;
433                    fr[f] += scr[f * 2 + 1] * s.gain;
434                }
435            }
436        }
437
438        // 2. FX buses: run inserts on the accumulated sends, return to master.
439        // `bi` indexes both self.buses and fx_in, so a range loop is clearest.
440        #[allow(clippy::needless_range_loop)]
441        for bi in 1..self.buses.len() {
442            if self.buses[bi].kind != BusKind::Fx {
443                continue;
444            }
445            let (fl, fr) = &mut fx_in[bi];
446            if let Some((cl, cr)) = &mut self.buses[bi].inserts {
447                cl.process(&mut fl[..frames]);
448                cr.process(&mut fr[..frames]);
449            }
450            let ret = self.buses[bi].gain * self.buses[bi].to_master;
451            for f in 0..frames {
452                master_l[f] += fl[f] * ret;
453                master_r[f] += fr[f] * ret;
454            }
455        }
456
457        // 3. Master insert chain, then the master fader, then interleave out.
458        if let Some((cl, cr)) = &mut self.buses[0].inserts {
459            cl.process(&mut master_l[..frames]);
460            cr.process(&mut master_r[..frames]);
461        }
462        let master_gain = self.buses[0].gain;
463        for f in 0..frames {
464            out[f * 2] = master_l[f] * master_gain;
465            out[f * 2 + 1] = master_r[f] * master_gain;
466        }
467
468        self.scratch = scratch;
469        self.master_l = master_l;
470        self.master_r = master_r;
471        self.bus_l = bus_l;
472        self.bus_r = bus_r;
473        self.fx_in = fx_in;
474        frames
475    }
476
477    /// Rewind every source so a transport restart replays the mix from the top.
478    /// Bus insert/send state (reverb tails, delay lines) is left ringing.
479    fn reset(&mut self) {
480        for s in self.sources.iter_mut() {
481            s.source.reset();
482        }
483    }
484}