use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use crate::dsl::{ENGINE_VERSION, Node, SoundDoc};
use crate::edit::{EditOp, apply_ops};
use crate::patch::Patch;
use crate::player::Player;
use crate::streaming::{EffectChain, StreamGraph};
pub trait AudioSource {
fn fill(&mut self, out: &mut [f32]) -> usize;
fn reset(&mut self) {}
}
pub fn write_interleaved(data: &mut [f32], channels: usize, stereo: &[f32]) {
let channels = channels.max(1);
let frames = data.len() / channels;
for f in 0..frames {
let (l, r) = (stereo[f * 2], stereo[f * 2 + 1]);
let base = f * channels;
if channels == 1 {
data[base] = 0.5 * (l + r);
} else {
data[base] = l;
data[base + 1] = r;
for c in 2..channels {
data[base + c] = 0.0;
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PatchId(usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct InstanceHandle(u64);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ParamId {
patch: usize,
index: usize,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct LayerId {
patch: usize,
index: usize,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Priority(pub u8);
impl Priority {
pub const LOW: Priority = Priority(0);
pub const NORMAL: Priority = Priority(64);
pub const HIGH: Priority = Priority(128);
pub const CRITICAL: Priority = Priority(255);
}
impl Default for Priority {
fn default() -> Self {
Priority::NORMAL
}
}
#[derive(Clone, Copy, Debug)]
pub struct Tween {
frames: u32,
}
impl Tween {
pub const IMMEDIATE: Tween = Tween { frames: 0 };
pub const fn frames(n: u32) -> Self {
Tween { frames: n }
}
pub fn ms(ms: f32, sample_rate: u32) -> Self {
let f = (ms / 1000.0 * sample_rate as f32).round();
Tween {
frames: if f > 0.0 { f as u32 } else { 0 },
}
}
}
impl Default for Tween {
fn default() -> Self {
Tween::IMMEDIATE
}
}
#[derive(Clone, Copy)]
struct Ramp {
value: f32,
target: f32,
step: f32,
remaining: u32,
}
impl Ramp {
fn new(v: f32) -> Self {
Ramp {
value: v,
target: v,
step: 0.0,
remaining: 0,
}
}
fn set(&mut self, target: f32, tw: Tween) {
self.target = target;
if tw.frames == 0 {
self.value = target;
self.step = 0.0;
self.remaining = 0;
} else {
self.step = (target - self.value) / tw.frames as f32;
self.remaining = tw.frames;
}
}
fn tick(&mut self) -> f32 {
if self.remaining > 0 {
self.value += self.step;
self.remaining -= 1;
if self.remaining == 0 {
self.value = self.target;
}
}
self.value
}
fn at_target(&self) -> bool {
self.remaining == 0
}
}
fn balance(pan: f32) -> (f32, f32) {
let l = if pan <= 0.0 { 1.0 } else { 1.0 - pan };
let r = if pan >= 0.0 { 1.0 } else { 1.0 + pan };
(l, r)
}
fn layer_ids(doc: &SoundDoc) -> Vec<Option<String>> {
match &doc.root {
Node::Tracks { tracks, .. } => tracks.iter().map(|t| t.id.clone()).collect(),
_ => Vec::new(),
}
}
struct Instance {
id: u64,
patch: usize,
values: BTreeMap<String, f32>,
layer_gains: BTreeMap<usize, f32>,
player: Player,
fading_in: Option<(Player, Ramp)>,
gain: Ramp,
pan: Ramp,
stopping: bool,
priority: Priority,
}
pub struct Engine {
sample_rate: u32,
patches: Vec<Patch>,
instances: Vec<Instance>,
next_id: u64,
max_voices: Option<usize>,
buf_a: Vec<f32>,
buf_b: Vec<f32>,
}
impl Engine {
pub fn new(sample_rate: u32) -> Self {
Engine {
sample_rate,
patches: Vec::new(),
instances: Vec::new(),
next_id: 1,
max_voices: None,
buf_a: Vec::new(),
buf_b: Vec::new(),
}
}
pub fn set_max_voices(&mut self, max: usize) {
self.max_voices = Some(max.max(1));
}
pub fn max_voices(&self) -> Option<usize> {
self.max_voices
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn load(&mut self, doc: &SoundDoc) -> PatchId {
self.load_patch(&Patch {
doc: doc.clone(),
params: Vec::new(),
})
}
pub fn load_patch(&mut self, patch: &Patch) -> PatchId {
self.patches.push(patch.clone());
PatchId(self.patches.len() - 1)
}
pub fn param(&self, patch: PatchId, name: &str) -> Option<ParamId> {
self.patches[patch.0]
.params
.iter()
.position(|p| p.name == name)
.map(|index| ParamId {
patch: patch.0,
index,
})
}
pub fn layer(&self, patch: PatchId, name: &str) -> Option<LayerId> {
layer_ids(&self.patches[patch.0].doc)
.iter()
.position(|id| id.as_deref() == Some(name))
.map(|index| LayerId {
patch: patch.0,
index,
})
}
pub fn play(&mut self, patch: PatchId) -> InstanceHandle {
self.spawn(patch, false, Priority::NORMAL)
}
pub fn play_looping(&mut self, patch: PatchId) -> InstanceHandle {
self.spawn(patch, true, Priority::NORMAL)
}
pub fn play_prioritized(&mut self, patch: PatchId, priority: Priority) -> InstanceHandle {
self.spawn(patch, false, priority)
}
pub fn play_looping_prioritized(
&mut self,
patch: PatchId,
priority: Priority,
) -> InstanceHandle {
self.spawn(patch, true, priority)
}
pub fn set_priority(&mut self, h: InstanceHandle, priority: Priority) {
if let Some(i) = self.instance_mut(h) {
i.priority = priority;
}
}
fn spawn(&mut self, patch: PatchId, looping: bool, priority: Priority) -> InstanceHandle {
if let Some(max) = self.max_voices
&& !self.make_room(max, priority)
{
return InstanceHandle(0);
}
let values = self.patches[patch.0].defaults();
let doc = self.build_doc(patch.0, &values, &BTreeMap::new());
let mut player = self.new_player(doc, looping, 0);
player.play();
let id = self.next_id;
self.next_id += 1;
self.instances.push(Instance {
id,
patch: patch.0,
values,
layer_gains: BTreeMap::new(),
player,
fading_in: None,
gain: Ramp::new(1.0),
pan: Ramp::new(0.0),
stopping: false,
priority,
});
InstanceHandle(id)
}
fn make_room(&mut self, max: usize, priority: Priority) -> bool {
let sounding = self.instances.iter().filter(|i| !i.stopping).count();
if sounding >= max {
let victim = self
.instances
.iter()
.filter(|i| !i.stopping)
.min_by(|a, b| a.priority.cmp(&b.priority).then(a.id.cmp(&b.id)))
.map(|i| (i.id, i.priority));
match victim {
Some((id, vp)) if vp <= priority => {
let fade = Tween::ms(5.0, self.sample_rate);
if let Some(v) = self.instances.iter_mut().find(|i| i.id == id) {
v.gain.set(0.0, fade);
v.stopping = true;
}
}
_ => return false,
}
}
if self.instances.len() >= max * 2
&& let Some(pos) = self
.instances
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.priority.cmp(&b.priority).then(a.id.cmp(&b.id)))
.map(|(pos, _)| pos)
{
self.instances.remove(pos);
}
true
}
fn build_doc(
&self,
patch: usize,
values: &BTreeMap<String, f32>,
layer_gains: &BTreeMap<usize, f32>,
) -> SoundDoc {
let p = &self.patches[patch];
let doc = p.instantiate(values).unwrap_or_else(|_| p.doc.clone());
if layer_gains.is_empty() {
return doc;
}
let ops: Vec<EditOp> = layer_gains
.iter()
.map(|(i, g)| EditOp::Set {
path: format!("root.tracks[{i}].gain"),
value: serde_json::json!(g),
})
.collect();
apply_ops(&doc, &ops).unwrap_or(doc)
}
fn new_player(&self, mut doc: SoundDoc, looping: bool, seek: usize) -> Player {
doc.sample_rate = self.sample_rate;
let mut player = Player::new(doc);
player.looping = looping;
player.seek(seek);
player.play();
player
}
fn instance_mut(&mut self, h: InstanceHandle) -> Option<&mut Instance> {
self.instances.iter_mut().find(|i| i.id == h.0)
}
fn find(&self, h: InstanceHandle) -> Option<usize> {
self.instances.iter().position(|i| i.id == h.0)
}
pub fn set_gain(&mut self, h: InstanceHandle, gain: f32, tw: Tween) {
if let Some(i) = self.instance_mut(h) {
i.gain.set(gain.max(0.0), tw);
}
}
pub fn set_pan(&mut self, h: InstanceHandle, pan: f32, tw: Tween) {
if let Some(i) = self.instance_mut(h) {
i.pan.set(pan.clamp(-1.0, 1.0), tw);
}
}
pub fn set_param(&mut self, h: InstanceHandle, param: ParamId, value: f32, tw: Tween) {
let Some(idx) = self.find(h) else { return };
if self.instances[idx].patch != param.patch {
return;
}
let name = self.patches[param.patch].params[param.index].name.clone();
self.instances[idx].values.insert(name, value);
self.rerender(idx, tw);
}
pub fn set_layer_gain(&mut self, h: InstanceHandle, layer: LayerId, gain: f32, tw: Tween) {
let Some(idx) = self.find(h) else { return };
if self.instances[idx].patch != layer.patch {
return;
}
self.instances[idx].layer_gains.insert(layer.index, gain);
self.rerender(idx, tw);
}
fn rerender(&mut self, idx: usize, tw: Tween) {
let (patch, looping, pos) = {
let i = &self.instances[idx];
(i.patch, i.player.looping, i.player.position())
};
let values = self.instances[idx].values.clone();
let layer_gains = self.instances[idx].layer_gains.clone();
let doc = self.build_doc(patch, &values, &layer_gains);
let fresh = self.new_player(doc, looping, pos);
let fade = if tw.frames == 0 {
Tween::ms(8.0, self.sample_rate)
} else {
tw
};
let inst = &mut self.instances[idx];
let outgoing = std::mem::replace(&mut inst.player, fresh);
let mut mix = Ramp::new(0.0);
mix.set(1.0, fade);
inst.fading_in = Some((outgoing, mix));
}
pub fn stop(&mut self, h: InstanceHandle, fade: Tween) {
let min_fade = Tween::ms(5.0, self.sample_rate);
if let Some(i) = self.instance_mut(h) {
let fade = if fade.frames == 0 { min_fade } else { fade };
i.gain.set(0.0, fade);
i.stopping = true;
}
}
pub fn active(&self) -> usize {
self.instances.len()
}
pub fn is_active(&self, h: InstanceHandle) -> bool {
self.instances.iter().any(|i| i.id == h.0)
}
}
impl AudioSource for Engine {
fn fill(&mut self, out: &mut [f32]) -> usize {
let frames = out.len() / 2;
out.fill(0.0);
if frames == 0 {
return 0;
}
if self.buf_a.len() < out.len() {
self.buf_a.resize(out.len(), 0.0);
self.buf_b.resize(out.len(), 0.0);
}
let (a, b) = (&mut self.buf_a[..out.len()], &mut self.buf_b[..out.len()]);
for inst in self.instances.iter_mut() {
inst.player.fill(a);
if let Some((out_player, _)) = inst.fading_in.as_mut() {
out_player.fill(b);
}
for f in 0..frames {
let g = inst.gain.tick();
let (lg, rg) = balance(inst.pan.tick());
let (mut l, mut r) = (a[f * 2], a[f * 2 + 1]);
if let Some((_, mix)) = inst.fading_in.as_mut() {
let w = mix.tick();
l = l * w + b[f * 2] * (1.0 - w);
r = r * w + b[f * 2 + 1] * (1.0 - w);
}
out[f * 2] += l * g * lg;
out[f * 2 + 1] += r * g * rg;
}
if let Some((_, mix)) = &inst.fading_in
&& mix.at_target()
{
inst.fading_in = None; }
}
self.instances
.retain(|i| i.player.playing && !(i.stopping && i.gain.at_target()));
frames
}
}
struct SampleRing {
buf: Vec<AtomicU32>,
head: AtomicUsize,
tail: AtomicUsize,
}
impl SampleRing {
fn new(capacity: usize) -> Self {
let n = (capacity + 1).max(2);
SampleRing {
buf: (0..n).map(|_| AtomicU32::new(0)).collect(),
head: AtomicUsize::new(0),
tail: AtomicUsize::new(0),
}
}
fn cap(&self) -> usize {
self.buf.len()
}
fn len(&self) -> usize {
let t = self.tail.load(Ordering::Acquire);
let h = self.head.load(Ordering::Acquire);
(t + self.cap() - h) % self.cap()
}
fn free(&self) -> usize {
self.cap() - 1 - self.len()
}
fn push(&self, sample: f32) -> bool {
let tail = self.tail.load(Ordering::Relaxed);
let next = (tail + 1) % self.cap();
if next == self.head.load(Ordering::Acquire) {
return false;
}
self.buf[tail].store(sample.to_bits(), Ordering::Relaxed);
self.tail.store(next, Ordering::Release);
true
}
fn pop(&self) -> Option<f32> {
let head = self.head.load(Ordering::Relaxed);
if head == self.tail.load(Ordering::Acquire) {
return None;
}
let bits = self.buf[head].load(Ordering::Relaxed);
self.head.store((head + 1) % self.cap(), Ordering::Release);
Some(f32::from_bits(bits))
}
}
pub struct Pump<S: AudioSource> {
source: S,
ring: Arc<SampleRing>,
pump_buf: Vec<f32>,
}
impl<S: AudioSource> Pump<S> {
pub fn pump(&mut self, frames: usize) -> usize {
let frames = frames.min(self.ring.free() / 2);
if frames == 0 {
return 0;
}
if self.pump_buf.len() < frames * 2 {
self.pump_buf.resize(frames * 2, 0.0);
}
let block = &mut self.pump_buf[..frames * 2];
self.source.fill(block);
for s in block.iter() {
self.ring.push(*s);
}
frames
}
}
impl<S: AudioSource> std::ops::Deref for Pump<S> {
type Target = S;
fn deref(&self) -> &S {
&self.source
}
}
impl<S: AudioSource> std::ops::DerefMut for Pump<S> {
fn deref_mut(&mut self) -> &mut S {
&mut self.source
}
}
pub type Controller = Pump<Engine>;
pub fn spsc<S: AudioSource>(source: S, ring_frames: usize) -> (Pump<S>, Renderer) {
let ring = Arc::new(SampleRing::new(ring_frames * 2));
(
Pump {
source,
ring: ring.clone(),
pump_buf: Vec::new(),
},
Renderer { ring },
)
}
pub struct Renderer {
ring: Arc<SampleRing>,
}
impl AudioSource for Renderer {
fn fill(&mut self, out: &mut [f32]) -> usize {
for frame in out.chunks_mut(2) {
if frame.len() == 2 && self.ring.len() >= 2 {
frame[0] = self.ring.pop().unwrap_or(0.0);
frame[1] = self.ring.pop().unwrap_or(0.0);
} else {
frame.fill(0.0);
}
}
out.len() / 2
}
}
impl Engine {
pub fn split(self, ring_frames: usize) -> (Controller, Renderer) {
spsc(self, ring_frames)
}
}
pub struct StreamSource {
graph: StreamGraph,
scratch: Vec<f32>,
gain: f32,
}
impl StreamSource {
pub fn from_doc(doc: &SoundDoc) -> Option<Self> {
let graph = StreamGraph::try_from_doc(doc)?;
let mut probe = StreamGraph::try_from_doc(doc)?;
let mut remaining = ((doc.duration * doc.sample_rate as f32).ceil() as usize).max(1);
let mut block = [0.0f32; 1024];
let mut peak = 0.0f32;
while remaining > 0 {
let take = block.len().min(remaining);
probe.fill(&mut block[..take]);
peak = block[..take].iter().fold(peak, |m, x| m.max(x.abs()));
remaining -= take;
}
let gain = if peak > crate::dsp::CEIL {
crate::dsp::CEIL / peak
} else {
1.0
};
Some(StreamSource {
graph,
scratch: Vec::new(),
gain,
})
}
}
impl AudioSource for StreamSource {
fn fill(&mut self, out: &mut [f32]) -> usize {
let frames = out.len() / 2;
if self.scratch.len() < frames {
self.scratch.resize(frames, 0.0);
}
let mono = &mut self.scratch[..frames];
self.graph.fill(mono);
for f in 0..frames {
let v = mono[f] * self.gain;
out[f * 2] = v;
out[f * 2 + 1] = v;
}
frames
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct SourceId(u64);
trait AnySource: AudioSource + std::any::Any {}
impl<T: AudioSource + 'static> AnySource for T {}
struct MixedSource {
id: u64,
source: Box<dyn AnySource + Send>,
gain: f32,
bus: BusId,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct BusId(u32);
impl BusId {
pub const MASTER: BusId = BusId(0);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum BusKind {
Master,
Input,
Fx,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MixerError {
NotStreamable,
NoSampleRate,
}
impl std::fmt::Display for MixerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MixerError::NotStreamable => {
write!(f, "effect chain contains a non-streamable node")
}
MixerError::NoSampleRate => {
write!(f, "mixer has no sample rate; build it with Mixer::new_at")
}
}
}
}
impl std::error::Error for MixerError {}
struct Bus {
id: u32,
name: String,
kind: BusKind,
gain: f32,
to_master: f32,
inserts: Option<(EffectChain, EffectChain)>,
sends: Vec<(u32, f32)>,
}
pub struct Mixer {
sources: Vec<MixedSource>,
buses: Vec<Bus>,
next_id: u64,
sample_rate: Option<u32>,
scratch: Vec<f32>,
master_l: Vec<f32>,
master_r: Vec<f32>,
bus_l: Vec<f32>,
bus_r: Vec<f32>,
fx_in: Vec<(Vec<f32>, Vec<f32>)>,
}
impl Default for Mixer {
fn default() -> Self {
Mixer::new()
}
}
impl Mixer {
pub fn new() -> Self {
Mixer::build(None)
}
pub fn new_at(sample_rate: u32) -> Self {
Mixer::build(Some(sample_rate))
}
fn build(sample_rate: Option<u32>) -> Self {
let master = Bus {
id: 0,
name: "master".into(),
kind: BusKind::Master,
gain: 1.0,
to_master: 1.0,
inserts: None,
sends: Vec::new(),
};
Mixer {
sources: Vec::new(),
buses: vec![master],
next_id: 1,
sample_rate,
scratch: Vec::new(),
master_l: Vec::new(),
master_r: Vec::new(),
bus_l: Vec::new(),
bus_r: Vec::new(),
fx_in: Vec::new(),
}
}
pub fn add(&mut self, source: impl AudioSource + Send + 'static) -> SourceId {
self.add_to(BusId::MASTER, source)
}
pub fn add_to(&mut self, bus: BusId, source: impl AudioSource + Send + 'static) -> SourceId {
let bus = if (bus.0 as usize) < self.buses.len() {
bus
} else {
BusId::MASTER
};
let id = self.next_id;
self.next_id += 1;
self.sources.push(MixedSource {
id,
source: Box::new(source),
gain: 1.0,
bus,
});
SourceId(id)
}
pub fn bus(&mut self, name: impl Into<String>) -> BusId {
self.push_bus(name.into(), BusKind::Input, None)
}
pub fn fx_bus(
&mut self,
name: impl Into<String>,
effects: Vec<Node>,
) -> Result<BusId, MixerError> {
let inserts = self.build_chain(&effects)?;
Ok(self.push_bus(name.into(), BusKind::Fx, inserts))
}
fn push_bus(
&mut self,
name: String,
kind: BusKind,
inserts: Option<(EffectChain, EffectChain)>,
) -> BusId {
let id = self.buses.len() as u32;
self.buses.push(Bus {
id,
name,
kind,
gain: 1.0,
to_master: 1.0,
inserts,
sends: Vec::new(),
});
BusId(id)
}
pub fn bus_named(&self, name: &str) -> Option<BusId> {
self.buses
.iter()
.find(|b| b.name == name)
.map(|b| BusId(b.id))
}
pub fn set_bus_effects(&mut self, bus: BusId, effects: Vec<Node>) -> Result<(), MixerError> {
let inserts = self.build_chain(&effects)?;
if let Some(b) = self.buses.get_mut(bus.0 as usize) {
b.inserts = inserts;
}
Ok(())
}
pub fn master_effects(&mut self, effects: Vec<Node>) -> Result<(), MixerError> {
self.set_bus_effects(BusId::MASTER, effects)
}
pub fn set_send(&mut self, from: BusId, to_fx: BusId, level: f32) {
let valid = matches!(
self.buses.get(from.0 as usize).map(|b| b.kind),
Some(BusKind::Input)
) && matches!(
self.buses.get(to_fx.0 as usize).map(|b| b.kind),
Some(BusKind::Fx)
);
if !valid {
return;
}
let level = level.max(0.0);
let bus = &mut self.buses[from.0 as usize];
if let Some(s) = bus.sends.iter_mut().find(|s| s.0 == to_fx.0) {
s.1 = level;
} else {
bus.sends.push((to_fx.0, level));
}
}
pub fn set_bus_gain(&mut self, bus: BusId, gain: f32) {
if let Some(b) = self.buses.get_mut(bus.0 as usize) {
b.gain = gain.max(0.0);
}
}
pub fn set_bus_dry(&mut self, bus: BusId, level: f32) {
if bus != BusId::MASTER
&& let Some(b) = self.buses.get_mut(bus.0 as usize)
{
b.to_master = level.max(0.0);
}
}
fn build_chain(
&self,
effects: &[Node],
) -> Result<Option<(EffectChain, EffectChain)>, MixerError> {
if effects.is_empty() {
return Ok(None);
}
let sr = self.sample_rate.ok_or(MixerError::NoSampleRate)?;
let build = || EffectChain::try_new(effects, sr, ENGINE_VERSION);
let l = build().ok_or(MixerError::NotStreamable)?;
let r = build().ok_or(MixerError::NotStreamable)?;
Ok(Some((l, r)))
}
pub fn set_gain(&mut self, id: SourceId, gain: f32) {
if let Some(s) = self.sources.iter_mut().find(|s| s.id == id.0) {
s.gain = gain.max(0.0);
}
}
pub fn get_mut<T: AudioSource + 'static>(&mut self, id: SourceId) -> Option<&mut T> {
let s = self.sources.iter_mut().find(|s| s.id == id.0)?;
let any: &mut dyn std::any::Any = s.source.as_mut();
any.downcast_mut::<T>()
}
pub fn remove(&mut self, id: SourceId) {
self.sources.retain(|s| s.id != id.0);
}
pub fn source_count(&self) -> usize {
self.sources.len()
}
pub fn contains(&self, id: SourceId) -> bool {
self.sources.iter().any(|s| s.id == id.0)
}
}
fn grow(v: &mut Vec<f32>, n: usize) {
if v.len() < n {
v.resize(n, 0.0);
}
}
impl AudioSource for Mixer {
fn fill(&mut self, out: &mut [f32]) -> usize {
let frames = out.len() / 2;
let mut scratch = std::mem::take(&mut self.scratch);
let mut master_l = std::mem::take(&mut self.master_l);
let mut master_r = std::mem::take(&mut self.master_r);
let mut bus_l = std::mem::take(&mut self.bus_l);
let mut bus_r = std::mem::take(&mut self.bus_r);
let mut fx_in = std::mem::take(&mut self.fx_in);
grow(&mut scratch, frames * 2);
grow(&mut master_l, frames);
grow(&mut master_r, frames);
grow(&mut bus_l, frames);
grow(&mut bus_r, frames);
if fx_in.len() < self.buses.len() {
fx_in.resize_with(self.buses.len(), || (Vec::new(), Vec::new()));
}
for (l, r) in fx_in.iter_mut() {
grow(l, frames);
grow(r, frames);
}
master_l[..frames].fill(0.0);
master_r[..frames].fill(0.0);
for (l, r) in fx_in.iter_mut() {
l[..frames].fill(0.0);
r[..frames].fill(0.0);
}
let scr = &mut scratch[..frames * 2];
for s in self.sources.iter_mut().filter(|s| s.bus == BusId::MASTER) {
s.source.fill(scr);
for f in 0..frames {
master_l[f] += scr[f * 2] * s.gain;
master_r[f] += scr[f * 2 + 1] * s.gain;
}
}
for bi in 1..self.buses.len() {
if self.buses[bi].kind != BusKind::Input {
continue;
}
bus_l[..frames].fill(0.0);
bus_r[..frames].fill(0.0);
let bus_id = self.buses[bi].id;
for s in self.sources.iter_mut().filter(|s| s.bus.0 == bus_id) {
s.source.fill(scr);
for f in 0..frames {
bus_l[f] += scr[f * 2] * s.gain;
bus_r[f] += scr[f * 2 + 1] * s.gain;
}
}
if let Some((cl, cr)) = &mut self.buses[bi].inserts {
cl.process(&mut bus_l[..frames]);
cr.process(&mut bus_r[..frames]);
}
let fader = self.buses[bi].gain;
let dry = fader * self.buses[bi].to_master;
for f in 0..frames {
master_l[f] += bus_l[f] * dry;
master_r[f] += bus_r[f] * dry;
}
for &(target, level) in &self.buses[bi].sends {
let k = target as usize;
if k < fx_in.len() {
let g = fader * level;
let (fl, fr) = &mut fx_in[k];
for f in 0..frames {
fl[f] += bus_l[f] * g;
fr[f] += bus_r[f] * g;
}
}
}
}
#[allow(clippy::needless_range_loop)]
for bi in 1..self.buses.len() {
if self.buses[bi].kind != BusKind::Fx {
continue;
}
let (fl, fr) = &mut fx_in[bi];
if let Some((cl, cr)) = &mut self.buses[bi].inserts {
cl.process(&mut fl[..frames]);
cr.process(&mut fr[..frames]);
}
let ret = self.buses[bi].gain * self.buses[bi].to_master;
for f in 0..frames {
master_l[f] += fl[f] * ret;
master_r[f] += fr[f] * ret;
}
}
if let Some((cl, cr)) = &mut self.buses[0].inserts {
cl.process(&mut master_l[..frames]);
cr.process(&mut master_r[..frames]);
}
for f in 0..frames {
out[f * 2] = master_l[f];
out[f * 2 + 1] = master_r[f];
}
self.scratch = scratch;
self.master_l = master_l;
self.master_r = master_r;
self.bus_l = bus_l;
self.bus_r = bus_r;
self.fx_in = fx_in;
frames
}
fn reset(&mut self) {
for s in self.sources.iter_mut() {
s.source.reset();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn doc(duration: f32) -> SoundDoc {
serde_json::from_str(&format!(
r#"{{ "name": "t", "duration": {duration}, "root": {{ "type": "sine", "freq": 440 }} }}"#
))
.unwrap()
}
fn pitch_patch() -> Patch {
serde_json::from_str(
r#"{ "doc": { "name": "t", "duration": 0.5, "root": { "type": "sine", "freq": 440 } },
"params": [ { "name": "pitch", "paths": ["root.freq"], "min": 100, "max": 2000, "default": 440 } ] }"#,
)
.unwrap()
}
fn two_layer_doc() -> SoundDoc {
serde_json::from_str(
r#"{ "name": "m", "duration": 0.5, "root": { "type": "tracks", "tracks": [
{ "id": "bass", "node": { "type": "sine", "freq": 110 } },
{ "id": "arp", "node": { "type": "sine", "freq": 880 } }
] } }"#,
)
.unwrap()
}
fn peak(buf: &[f32]) -> f32 {
buf.iter().fold(0.0f32, |m, &x| m.max(x.abs()))
}
#[test]
fn no_cap_spawns_unbounded() {
let mut e = Engine::new(44_100);
let p = e.load(&doc(1.0));
for _ in 0..100 {
e.play(p);
}
assert_eq!(e.active(), 100, "no cap → unbounded voices");
assert_eq!(e.max_voices(), None);
}
#[test]
fn cap_bounds_the_sounding_voices() {
let mut e = Engine::new(44_100);
e.set_max_voices(4);
let p = e.load(&doc(1.0));
for _ in 0..12 {
e.play(p); }
let sounding = e.instances.iter().filter(|i| !i.stopping).count();
assert!(
sounding <= 4,
"sounding voices exceeded the cap: {sounding}"
);
assert!(e.active() <= 8, "hard bound is 2×max: {}", e.active());
}
#[test]
fn higher_priority_steals_a_lower_one() {
let mut e = Engine::new(44_100);
e.set_max_voices(2);
let p = e.load(&doc(1.0));
let a = e.play_looping_prioritized(p, Priority::LOW);
let b = e.play_looping_prioritized(p, Priority::LOW);
let hi = e.play_prioritized(p, Priority::HIGH);
assert!(e.is_active(hi), "the high-priority voice got in");
let stopping: Vec<u64> = e
.instances
.iter()
.filter(|i| i.stopping)
.map(|i| i.id)
.collect();
assert_eq!(stopping.len(), 1, "one low voice is fading out");
assert!(
stopping == vec![a.0] || stopping == vec![b.0],
"the stolen voice is one of the two low loops"
);
}
#[test]
fn outranked_voice_is_denied() {
let mut e = Engine::new(44_100);
e.set_max_voices(2);
let p = e.load(&doc(1.0));
e.play_looping_prioritized(p, Priority::HIGH);
e.play_looping_prioritized(p, Priority::HIGH);
let low = e.play_prioritized(p, Priority::LOW);
assert!(!e.is_active(low), "a fully-outranked voice is denied");
assert_eq!(
e.instances.iter().filter(|i| !i.stopping).count(),
2,
"the high voices are untouched"
);
}
#[test]
fn stealing_is_deterministic() {
let run = || {
let mut e = Engine::new(44_100);
e.set_max_voices(3);
let p = e.load(&doc(1.0));
for i in 0..15 {
let prio = Priority((i % 3) as u8 * 64);
e.play_prioritized(p, prio);
}
let mut ids: Vec<u64> = e.instances.iter().map(|i| i.id).collect();
ids.sort_unstable();
ids
};
assert_eq!(run(), run(), "the same sequence yields the same survivors");
}
#[test]
fn one_patch_spawns_many_independent_instances() {
let mut e = Engine::new(44_100);
let p = e.load(&doc(1.0));
let _a = e.play(p);
let _b = e.play(p);
let _c = e.play(p);
assert_eq!(
e.active(),
3,
"resource → instance: many instances of one patch"
);
let mut out = vec![0.0f32; 512 * 2];
assert_eq!(e.fill(&mut out), 512);
assert!(peak(&out) > 0.0, "the mix should produce audio");
}
#[test]
fn gain_tween_ramps_to_silence() {
let mut e = Engine::new(1000);
let p = e.load(&doc(1.0));
let h = e.play(p);
e.set_gain(h, 0.0, Tween::frames(100));
let mut out = vec![0.0f32; 50 * 2];
e.fill(&mut out);
let mut rest = vec![0.0f32; 100 * 2];
e.fill(&mut rest);
assert!(
peak(&rest[80 * 2..]) < 1e-3,
"gain reached 0 after the tween"
);
}
#[test]
fn stop_declicks_and_culls_the_instance() {
let mut e = Engine::new(44_100);
let p = e.load(&doc(5.0));
let h = e.play_looping(p);
assert!(e.is_active(h));
e.stop(h, Tween::ms(10.0, 44_100));
let mut out = vec![0.0f32; 1024 * 2];
e.fill(&mut out);
e.fill(&mut out);
assert!(!e.is_active(h), "stopped instance is culled once silent");
}
#[test]
fn one_shot_culls_itself_at_end() {
let mut e = Engine::new(1000);
let p = e.load(&doc(0.1));
e.play(p);
assert_eq!(e.active(), 1);
let mut out = vec![0.0f32; 256 * 2];
e.fill(&mut out);
assert_eq!(e.active(), 0, "a finished one-shot removes itself");
}
#[test]
fn param_resolves_and_set_param_keeps_it_playing() {
let mut e = Engine::new(44_100);
let p = e.load_patch(&pitch_patch());
let pitch = e.param(p, "pitch").expect("pitch param");
assert!(e.param(p, "nope").is_none());
let h = e.play_looping(p);
let mut out = vec![0.0f32; 256 * 2];
e.fill(&mut out);
e.set_param(h, pitch, 880.0, Tween::ms(5.0, 44_100));
assert_eq!(e.active(), 1);
let mut out2 = vec![0.0f32; 1024 * 2];
e.fill(&mut out2);
assert!(peak(&out2) > 0.0, "still playing at the new pitch");
}
#[test]
fn layer_resolves_and_gain_change_is_click_free() {
let mut e = Engine::new(44_100);
let p = e.load(&two_layer_doc());
let arp = e.layer(p, "arp").expect("arp layer");
assert!(e.layer(p, "missing").is_none());
let h = e.play_looping(p);
let mut out = vec![0.0f32; 256 * 2];
e.fill(&mut out);
e.set_layer_gain(h, arp, 0.0, Tween::ms(20.0, 44_100));
e.fill(&mut out);
assert!(e.is_active(h), "layer move does not drop the instance");
}
#[test]
fn hard_pan_silences_the_opposite_channel() {
let (l, r) = balance(1.0); assert!(l.abs() < 1e-6 && (r - 1.0).abs() < 1e-6);
let (l, r) = balance(-1.0); assert!((l - 1.0).abs() < 1e-6 && r.abs() < 1e-6);
let (l, r) = balance(0.0);
assert!(
(l - 1.0).abs() < 1e-6 && (r - 1.0).abs() < 1e-6,
"unity at centre"
);
}
#[test]
fn ring_pushes_pops_and_wraps() {
let r = SampleRing::new(4); assert!(r.pop().is_none());
for i in 0..4 {
assert!(r.push(i as f32));
}
assert!(!r.push(9.0), "full");
assert_eq!(r.pop(), Some(0.0));
assert!(r.push(9.0), "space freed after a pop");
let got: Vec<f32> = std::iter::from_fn(|| r.pop()).collect();
assert_eq!(got, vec![1.0, 2.0, 3.0, 9.0]);
}
#[test]
fn split_pumps_audio_across_the_seam() {
let mut e = Engine::new(44_100);
let p = e.load(&doc(1.0));
let (mut ctl, mut rend) = e.split(1024);
ctl.play_looping(p); assert!(ctl.pump(512) > 0, "controller produced frames");
let mut out = vec![0.0f32; 512 * 2];
assert_eq!(rend.fill(&mut out), 512);
assert!(peak(&out) > 0.0, "renderer drained real audio");
}
#[test]
fn spsc_pumps_a_mixer_across_the_seam() {
let mut engine = Engine::new(44_100);
let p = engine.load(&doc(1.0));
engine.play_looping(p);
let mut mixer = Mixer::new();
mixer.add(engine);
let (mut ctl, mut rend) = spsc(mixer, 1024);
assert!(ctl.pump(512) > 0, "pump produced frames");
assert_eq!(ctl.source_count(), 1, "deref reaches the Mixer");
let mut out = vec![0.0f32; 512 * 2];
assert_eq!(rend.fill(&mut out), 512);
assert!(peak(&out) > 0.0, "renderer drained real audio");
}
#[test]
fn pump_never_drops_rendered_frames() {
let mk = || {
let mut e = Engine::new(44_100);
let p = e.load(&doc(1.0));
e.play_looping(p);
e
};
let mut reference = mk();
let mut expected = vec![0.0f32; 192 * 2];
reference.fill(&mut expected);
let (mut ctl, mut rend) = mk().split(64);
let mut got = Vec::new();
let mut out = vec![0.0f32; 64 * 2];
while got.len() < expected.len() {
ctl.pump(200); rend.fill(&mut out);
got.extend_from_slice(&out);
}
assert_eq!(
&got[..expected.len()],
&expected[..],
"over-pumping dropped rendered frames"
);
}
#[test]
fn renderer_drains_whole_frames_only() {
let ring = SampleRing::new(8);
for s in [1.0f32, 2.0, 3.0] {
ring.push(s); }
let mut rend = Renderer {
ring: std::sync::Arc::new(ring),
};
let mut out = vec![9.0f32; 4];
rend.fill(&mut out);
assert_eq!(out, vec![1.0, 2.0, 0.0, 0.0], "half frame must stay queued");
rend.ring.push(4.0);
let mut out = vec![9.0f32; 2];
rend.fill(&mut out);
assert_eq!(
out,
vec![3.0, 4.0],
"queued half frame pairs with the next sample"
);
}
#[test]
fn renderer_underrun_writes_silence() {
let e = Engine::new(44_100);
let (_ctl, mut rend) = e.split(256); let mut out = vec![1.0f32; 128 * 2];
rend.fill(&mut out);
assert!(peak(&out) < 1e-9, "underrun is clean silence, not garbage");
}
#[test]
fn control_and_audio_sides_are_send() {
fn assert_send<T: Send>() {}
assert_send::<Controller>();
assert_send::<Renderer>();
}
#[test]
fn stream_source_streams_a_streamable_doc() {
let d: SoundDoc = serde_json::from_str(
r#"{ "name":"s", "duration":0.1, "root": { "type":"chain", "stages": [
{ "type":"sawtooth", "freq":220 },
{ "type":"lowpass", "cutoff":900, "q":0.7 } ] } }"#,
)
.unwrap();
let mut src = StreamSource::from_doc(&d).expect("streamable");
let mut out = vec![0.0f32; 256 * 2];
assert_eq!(src.fill(&mut out), 256);
assert!(peak(&out) > 0.0, "streams real audio");
assert!((0..256).all(|f| out[f * 2] == out[f * 2 + 1]));
}
#[test]
fn stream_source_matches_the_bounce_including_its_peak_limit() {
let d: SoundDoc = serde_json::from_str(
r#"{ "name":"loud", "duration":0.1, "root": { "type":"sine", "freq":220 } }"#,
)
.unwrap();
let bounce = crate::render::render(&d);
let mut src = StreamSource::from_doc(&d).expect("streamable");
let mut out = vec![0.0f32; bounce.len() * 2];
src.fill(&mut out);
for (i, b) in bounce.iter().enumerate() {
assert_eq!(
out[i * 2].to_bits(),
b.to_bits(),
"stream diverges from the bounce at sample {i}"
);
}
}
#[test]
fn stream_source_rejects_non_streamable() {
let d: SoundDoc = serde_json::from_str(
r#"{ "name":"n", "duration":0.05, "root": { "type":"noise", "color":"white" } }"#,
)
.unwrap();
assert!(StreamSource::from_doc(&d).is_none());
}
#[test]
fn mixer_sums_and_reaches_in_by_type() {
let mut e = Engine::new(44_100);
let p = e.load(&doc(1.0));
e.play_looping(p);
let mut mixer = Mixer::new();
let id = mixer.add(e);
assert_eq!(mixer.source_count(), 1);
mixer.get_mut::<Engine>(id).unwrap().play_looping(p);
assert_eq!(mixer.get_mut::<Engine>(id).unwrap().active(), 2);
mixer.set_gain(id, 0.5);
let mut out = vec![0.0f32; 256 * 2];
assert_eq!(mixer.fill(&mut out), 256);
assert!(peak(&out) > 0.0, "mixer sums its sources");
mixer.remove(id);
assert_eq!(mixer.source_count(), 0);
}
struct Const {
l: f32,
r: f32,
}
impl AudioSource for Const {
fn fill(&mut self, out: &mut [f32]) -> usize {
for frame in out.chunks_mut(2) {
frame[0] = self.l;
frame[1] = self.r;
}
out.len() / 2
}
}
struct Burst {
fired: bool,
}
impl AudioSource for Burst {
fn fill(&mut self, out: &mut [f32]) -> usize {
let v = if self.fired { 0.0 } else { 1.0 };
out.fill(v);
self.fired = true;
out.len() / 2
}
}
#[test]
fn no_bus_mix_is_the_plain_additive_sum() {
let mut mixer = Mixer::new();
mixer.add(Const { l: 0.3, r: -0.2 });
let b = mixer.add(Const { l: 0.1, r: 0.4 });
mixer.set_gain(b, 0.5);
let mut out = vec![0.0f32; 64 * 2];
mixer.fill(&mut out);
for frame in out.chunks(2) {
assert_eq!(frame[0], 0.3 + 0.1 * 0.5);
assert_eq!(frame[1], -0.2 + 0.4 * 0.5);
}
}
#[test]
fn bus_insert_scales_only_its_bus() {
let mut mixer = Mixer::new_at(44_100);
mixer.add(Const { l: 0.4, r: 0.4 }); let music = mixer.bus("music");
mixer.add_to(music, Const { l: 0.4, r: 0.4 });
mixer.set_bus_effects(music, vec![gain_node(0.5)]).unwrap();
let mut out = vec![0.0f32; 32 * 2];
mixer.fill(&mut out);
for frame in out.chunks(2) {
assert!((frame[0] - 0.6).abs() < 1e-6, "got {}", frame[0]);
}
}
#[test]
fn reverb_send_tail_outlives_the_source() {
let mut mixer = Mixer::new_at(44_100);
let sfx = mixer.bus("sfx");
mixer.add_to(sfx, Burst { fired: false });
let rev = mixer.fx_bus("rev", vec![reverb_node()]).unwrap();
mixer.set_send(sfx, rev, 0.9);
let mut out = vec![0.0f32; 128 * 2];
mixer.fill(&mut out);
assert!(peak(&out) > 0.0);
let mut tail = 0.0f32;
for _ in 0..8 {
mixer.fill(&mut out);
tail = tail.max(peak(&out));
}
assert!(
tail > 0.0,
"reverb send should ring after the source goes silent"
);
}
#[test]
fn bus_routing_is_deterministic() {
let build = || {
let mut mixer = Mixer::new_at(44_100);
let music = mixer.bus("music");
mixer.add_to(music, Const { l: 0.2, r: 0.1 });
mixer.set_bus_effects(music, vec![gain_node(0.7)]).unwrap();
let rev = mixer.fx_bus("rev", vec![reverb_node()]).unwrap();
mixer.set_send(music, rev, 0.5);
mixer.master_effects(vec![gain_node(0.9)]).unwrap();
mixer
};
let render = |mut mixer: Mixer| {
let mut acc = Vec::new();
let mut out = vec![0.0f32; 96 * 2];
for _ in 0..6 {
mixer.fill(&mut out);
acc.extend_from_slice(&out);
}
acc
};
assert_eq!(
render(build()),
render(build()),
"bus routing must be byte-identical"
);
}
fn gain_node(amount: f32) -> Node {
serde_json::from_str(&format!(r#"{{ "type": "gain", "amount": {amount} }}"#)).unwrap()
}
fn reverb_node() -> Node {
serde_json::from_str(r#"{ "type": "reverb", "room": 0.6, "mix": 0.5 }"#).unwrap()
}
}