use core::time::Duration;
use std::collections::VecDeque;
use std::sync::Arc;
use crate::assets::Stream;
use crate::sound::MixRate;
use crate::sound::data::{Body, Clip, ClipFrame, Encoded, SampleRate};
use crate::sound::mixer::{
Audible, Command, Fade, GLIDE, Levels, LiveKnobs, Playing, STREAM_AHEAD, Sustained, Sustaining,
Sustains, Voicing, Window, limit,
};
const DECODE_PER_BLOCK: usize = 8_192;
pub(crate) struct Mixer {
rate: SampleRate,
sustained: Sustains<Sustain>,
shots: Vec<Playing<Voice>>,
starting: Vec<Voicing>,
volume: Slide,
}
impl Mixer {
pub(crate) fn new(rate: MixRate) -> Self {
Self {
rate: rate.get(),
sustained: Sustains::default(),
shots: Vec::new(),
starting: Vec::new(),
volume: Slide::held(1.0),
}
}
pub(crate) fn apply(&mut self, command: Command) {
match command {
Command::Play(voicing) => self.starting.push(voicing),
Command::Sustained(declared) => self.sustained.declare(declared, self.rate),
Command::Volume(volume) => self.volume.aim(volume.max(0.0), GLIDE, self.rate),
}
}
pub(crate) fn mix(&mut self, block: &mut [MixFrame]) {
let rate = self.rate;
self.allocate();
for sustain in self.sustained.iter_mut() {
sustain.decode();
}
for shot in self.shots.iter_mut().filter_map(Playing::voice_mut) {
shot.decode();
}
let frames = block.len();
for frame in block {
let mut mixed = MixFrame::SILENT;
for sustain in self.sustained.iter_mut() {
mixed += sustain.frame();
}
for shot in self.shots.iter_mut().filter_map(Playing::voice_mut) {
mixed += shot.frame();
}
let volume = self.volume.advance();
*frame = MixFrame::new(limit(mixed.left() * volume), limit(mixed.right() * volume));
}
for sustain in self.sustained.iter_mut() {
sustain.advance(frames as f64);
}
self.shots.retain(|shot| !shot.spent(rate));
self.sustained.settle(rate);
}
#[cfg(test)]
pub(crate) fn sounding(&self) -> usize {
self.shots.len()
+ self
.sustained
.iter()
.filter(|sustain| sustain.playing.voice().is_some())
.count()
}
fn allocate(&mut self) {
let rate = self.rate;
self.sustained
.allocate(&mut self.shots, &mut self.starting, &mut (), rate);
for voicing in self.starting.drain(..) {
let levels = Sliding::held(voicing.live.levels);
let voice = Voice::new(voicing, ClipFrame::ZERO, levels, rate);
self.shots.push(Playing::Voiced(voice));
}
}
}
struct Sustain {
sustained: Sustained,
clip: Arc<Clip>,
window: Window,
live: LiveKnobs,
levels: Sliding,
played: Played,
playing: Playing<Voice>,
}
impl Sustaining for Sustain {
type Pace = SampleRate;
type Voices<'a> = ();
fn started(sustained: Sustained, voicing: Voicing, _: SampleRate) -> Self {
Self {
sustained,
clip: voicing.clip,
window: voicing.window,
levels: Sliding::held(voicing.live.levels),
live: voicing.live,
played: Played::default(),
playing: Playing::Virtual,
}
}
fn sustained(&self) -> Sustained {
self.sustained
}
fn declare(&mut self, voicing: Voicing, rate: SampleRate) {
if voicing.window != self.window {
log::debug!(
"a sustained value declared a different window while sounding; keeping the one it started on"
);
}
self.live = voicing.live;
match self.playing.voice_mut() {
Some(voice) => voice.retarget(self.live, rate),
None => self.levels.aim(self.live.levels, self.live.glide, rate),
}
self.playing.declared(rate);
}
fn stop(&mut self, rate: SampleRate) -> bool {
self.playing.stopped(rate)
}
fn voiced(&mut self, wins: bool, _: &mut (), rate: SampleRate) {
let started = || {
let voicing = Voicing {
sound: self.sustained.sound,
clip: Arc::clone(&self.clip),
window: self.window,
live: self.live,
};
Some(Voice::new(voicing, self.played.at(), self.levels, rate))
};
self.playing.voiced(wins, rate, started);
}
fn audibility(&self, rate: SampleRate) -> f32 {
match self.playing.voice() {
Some(voice) => voice.audibility(rate),
None => self.levels.loudest(),
}
}
fn settle(&mut self, rate: SampleRate) -> bool {
match self.playing.landed(rate) {
Some((voice, Fade::Cut)) => {
self.played = Played::reached(voice.reached());
self.levels = voice.levels;
true
}
Some((_, Fade::Stop)) => false,
None => true,
}
}
}
impl Sustain {
fn frame(&mut self) -> MixFrame {
match self.playing.voice_mut() {
Some(voice) => voice.frame(),
None => MixFrame::SILENT,
}
}
fn advance(&mut self, frames: f64) {
if self.playing.voice().is_none() {
self.played.advance(f64::from(self.live.pitch) * frames);
self.levels.advance_by(frames);
}
}
fn decode(&mut self) {
if let Some(voice) = self.playing.voice_mut() {
voice.decode();
}
}
}
#[derive(Clone, Copy, Default)]
struct Played {
frames: ClipFrame,
over: f64,
}
impl Played {
fn reached(frames: ClipFrame) -> Self {
Self { frames, over: 0.0 }
}
fn at(self) -> ClipFrame {
self.frames
}
fn advance(&mut self, frames: f64) {
self.over += frames;
let whole = self.over.trunc();
self.over -= whole;
self.frames += whole as u64;
}
}
struct Voice {
clip: Arc<Clip>,
source: Source,
window: Window,
played: ClipFrame,
levels: Sliding,
envelope: Envelope,
fade: Duration,
glide: Duration,
step: f64,
over: f64,
previous: MixFrame,
next: MixFrame,
ended: bool,
}
impl Voice {
fn new(voicing: Voicing, played: ClipFrame, levels: Sliding, rate: SampleRate) -> Self {
let Voicing {
clip,
window,
live: LiveKnobs {
pitch, fade, glide, ..
},
..
} = voicing;
let source = match &clip.body {
Body::Samples(samples) => Source::Resident(Arc::clone(samples)),
Body::Encoded(encoded) => {
Source::Streamed(Streaming::new(Arc::clone(encoded), played, clip.rate))
}
};
Self {
step: f64::from(pitch),
clip,
source,
window,
played,
levels,
envelope: Envelope::rising(fade, rate),
fade,
glide,
over: 2.0,
previous: MixFrame::SILENT,
next: MixFrame::SILENT,
ended: false,
}
}
fn retarget(&mut self, live: LiveKnobs, rate: SampleRate) {
self.levels.aim(live.levels, live.glide, rate);
self.fade = live.fade;
self.glide = live.glide;
self.step = f64::from(live.pitch);
}
fn reached(&self) -> ClipFrame {
self.played.back(1)
}
fn frame(&mut self) -> MixFrame {
while self.over >= 1.0 {
if self.ended {
return MixFrame::SILENT;
}
match self.pull() {
Pulled::Waiting => return MixFrame::SILENT,
Pulled::Frame(frame) => {
self.previous = self.next;
self.next = frame;
self.over -= 1.0;
}
Pulled::Done => {
self.previous = self.next;
self.next = MixFrame::SILENT;
self.ended = true;
self.over -= 1.0;
}
}
}
let over = self.over as f32;
let gain = self.envelope.advance();
let (left, right) = self.levels.advance();
let frame = MixFrame::new(
(self.previous.left() + (self.next.left() - self.previous.left()) * over) * gain * left,
(self.previous.right() + (self.next.right() - self.previous.right()) * over)
* gain
* right,
);
self.over += self.step;
frame
}
fn pull(&mut self) -> Pulled {
let channels = self.clip.channels.count();
let window = self.window;
let played = self.played;
let pulled = match &mut self.source {
Source::Resident(samples) => match window.at(played) {
Some(at) => match samples.get(at.get() as usize * channels..) {
Some(from) if from.len() >= channels => {
Pulled::Frame(MixFrame::spread(&from[..channels]))
}
_ => Pulled::Done,
},
None => Pulled::Done,
},
Source::Streamed(streaming) => streaming.pull(&window, channels),
};
if matches!(pulled, Pulled::Frame(_)) {
self.played += 1;
}
pulled
}
fn decode(&mut self) {
let ahead = (STREAM_AHEAD.as_secs_f64() * f64::from(self.clip.rate)) as usize;
let channels = self.clip.channels.count();
let window = self.window;
if let Source::Streamed(streaming) = &mut self.source {
streaming.decode(&window, channels, ahead);
}
}
}
impl Audible for Voice {
type Pace = SampleRate;
fn audibility(&self, _: SampleRate) -> f32 {
self.levels.loudest() * self.envelope.reach()
}
fn stop(&mut self, rate: SampleRate) {
self.envelope.aim(0.0, self.fade, rate);
}
fn rise(&mut self, rate: SampleRate) {
self.envelope.aim(1.0, self.fade, rate);
}
fn cut(&mut self, rate: SampleRate) {
self.envelope.aim(0.0, self.glide, rate);
}
fn recover(&mut self, rate: SampleRate) {
self.envelope.aim(1.0, self.glide, rate);
}
fn spent(&self, rate: SampleRate) -> bool {
(self.ended && self.over >= 1.0) || self.audibility(rate) == 0.0
}
}
enum Source {
Resident(Arc<[f32]>),
Streamed(Streaming),
}
struct Streaming {
stream: Option<Box<Stream>>,
ahead: VecDeque<f32>,
produced: ClipFrame,
}
impl Streaming {
fn new(clip: Arc<Encoded>, played: ClipFrame, rate: SampleRate) -> Self {
Self {
stream: Stream::open(clip, rate)
.inspect_err(|error| log::debug!("{error}"))
.map(Box::new)
.ok(),
ahead: VecDeque::new(),
produced: played,
}
}
fn pull(&mut self, window: &Window, channels: usize) -> Pulled {
if self.ahead.len() < channels {
return match (self.stream.is_some(), window.at(self.produced)) {
(true, Some(_)) => Pulled::Waiting,
_ => Pulled::Done,
};
}
let left = self.ahead.pop_front().unwrap_or_default();
Pulled::Frame(MixFrame::new(
left,
match channels {
1 => left,
_ => self.ahead.pop_front().unwrap_or_default(),
},
))
}
fn decode(&mut self, window: &Window, channels: usize, ahead: usize) {
let Some(mut stream) = self.stream.take() else {
return;
};
let mut budget = DECODE_PER_BLOCK;
while self.ahead.len() / channels < ahead && budget > 0 {
let Some(at) = window.at(self.produced) else {
return;
};
if stream.position() != at
&& let Err(error) = stream.seek(at)
{
log::debug!("{error}");
return;
}
match stream.read(
budget.min((window.end - at).get() as usize),
&mut self.ahead,
) {
Ok(0) => return,
Ok(read) => {
self.produced += read as u64;
budget -= read;
}
Err(error) => {
log::debug!("{error}");
return;
}
}
}
self.stream = Some(stream);
}
}
enum Pulled {
Frame(MixFrame),
Waiting,
Done,
}
#[derive(Clone, Copy)]
struct Envelope {
level: f32,
target: f32,
pace: f32,
}
impl Envelope {
fn rising(fade: Duration, rate: SampleRate) -> Self {
Self {
level: 0.0,
target: 1.0,
pace: Self::paced(fade, rate),
}
}
fn paced(fade: Duration, rate: SampleRate) -> f32 {
let frames = fade.as_secs_f32() * f32::from(rate);
match frames > 1.0 {
true => 1.0 / frames,
false => 1.0,
}
}
fn aim(&mut self, target: f32, span: Duration, rate: SampleRate) {
self.pace = Self::paced(span, rate);
self.target = target;
}
fn reach(&self) -> f32 {
self.level.max(self.target)
}
fn advance(&mut self) -> f32 {
self.level = match self.level < self.target {
true => (self.level + self.pace).min(self.target),
false => (self.level - self.pace).max(self.target),
};
self.level
}
}
#[derive(Clone, Copy)]
struct Sliding {
left: Slide,
right: Slide,
}
impl Sliding {
fn held(levels: Levels) -> Self {
Self {
left: Slide::held(levels.left),
right: Slide::held(levels.right),
}
}
fn aim(&mut self, levels: Levels, span: Duration, rate: SampleRate) {
self.left.aim(levels.left, span, rate);
self.right.aim(levels.right, span, rate);
}
fn loudest(&self) -> f32 {
self.left.loudest().max(self.right.loudest())
}
fn advance(&mut self) -> (f32, f32) {
(self.left.advance(), self.right.advance())
}
fn advance_by(&mut self, frames: f64) {
self.left.advance_by(frames);
self.right.advance_by(frames);
}
}
#[derive(Clone, Copy)]
struct Slide {
level: f32,
target: f32,
step: f32,
}
impl Slide {
fn held(level: f32) -> Self {
Self {
level,
target: level,
step: 0.0,
}
}
fn aim(&mut self, target: f32, span: Duration, rate: SampleRate) {
if target == self.target {
return;
}
let frames = span.as_secs_f32() * f32::from(rate);
match frames > 1.0 {
true => self.step = (target - self.level) / frames,
false => {
self.step = 0.0;
self.level = target;
}
}
self.target = target;
}
fn loudest(&self) -> f32 {
self.level.max(self.target)
}
fn advance(&mut self) -> f32 {
self.advance_by(1.0);
self.level
}
fn advance_by(&mut self, frames: f64) {
let step = self.step * frames as f32;
self.level = match self.step < 0.0 {
true => (self.level + step).max(self.target),
false => (self.level + step).min(self.target),
};
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub(crate) struct MixFrame([f32; 2]);
impl MixFrame {
pub(crate) const SILENT: Self = Self([0.0; 2]);
#[inline]
fn new(left: f32, right: f32) -> Self {
Self([left, right])
}
#[inline]
fn spread(frame: &[f32]) -> Self {
Self(match frame {
[mono] => [*mono, *mono],
[left, right, ..] => [*left, *right],
[] => [0.0; 2],
})
}
#[inline]
pub(crate) fn ear(self, channel: usize) -> f32 {
self.0.get(channel).copied().unwrap_or_default()
}
#[inline]
fn left(self) -> f32 {
self.0[0]
}
#[inline]
fn right(self) -> f32 {
self.0[1]
}
}
impl core::ops::AddAssign for MixFrame {
#[inline]
fn add_assign(&mut self, other: Self) {
self.0[0] += other.0[0];
self.0[1] += other.0[1];
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sound::SoundData;
use crate::sound::build::Knobs;
use crate::sound::data::Channels;
use crate::sound::mixer::tests::{
MIX, QUIET, clip, counted, knobs, leveled, seconds, sustained, tone, voicing,
};
use crate::sound::mixer::{MAX_VOICES, SoundId};
const MASTER: MixRate = MixRate::chosen(SampleRate::new(80));
fn flat(frames: u64) -> Arc<Clip> {
clip(vec![0.5; frames as usize])
}
fn silent(frames: u64) -> Arc<Clip> {
clip(vec![0.0; frames as usize])
}
fn steady() -> Arc<Clip> {
Arc::new(Clip {
rate: MASTER.get(),
channels: Channels::Mono,
frames: 64,
body: Body::Samples(vec![0.5; 64].into()),
})
}
fn declare(mixer: &mut Mixer, set: Vec<(Sustained, Voicing)>) {
mixer.apply(Command::Sustained(set));
}
fn one(
sustained: Sustained,
clip: &Arc<Clip>,
knobs: &Knobs,
level: f32,
) -> (Sustained, Voicing) {
(sustained, leveled(clip, knobs, true, level))
}
fn filling(clip: &Arc<Clip>, first: u32, count: u32, level: f32) -> Vec<(Sustained, Voicing)> {
(first..first + count)
.map(|instance| {
let sustained = Sustained {
sound: SoundId(0),
instance,
};
one(sustained, clip, &knobs(), level)
})
.collect()
}
fn streamed() -> Arc<Clip> {
let encoded = Arc::new(
crate::assets::ogg::decode(crate::assets::SWEEP).expect("the fixture decodes"),
);
Arc::new(Clip {
rate: encoded.rate(),
channels: encoded.channels(),
frames: encoded.frames(),
body: Body::Encoded(encoded),
})
}
fn heard(frames: &[u64]) -> Vec<f32> {
frames.iter().map(|&at| at as f32 / QUIET).collect()
}
fn mixed(mixer: &mut Mixer, frames: usize) -> Vec<f32> {
let mut block = vec![MixFrame::SILENT; frames];
mixer.mix(&mut block);
block.iter().map(|frame| frame.left()).collect()
}
fn played(data: &SoundData, rate: MixRate, frames: usize) -> Vec<f32> {
let clip = Arc::new(crate::sound::resolve(data, Some(rate)));
let mut mixer = Mixer::new(rate);
mixer.apply(Command::Play(voicing(&clip, &knobs(), false)));
mixed(&mut mixer, frames)
}
fn noise(heard: &[f32], hertz: f64, rate: SampleRate) -> f32 {
let turn = |at: usize| core::f64::consts::TAU * hertz * at as f64 / f64::from(rate);
let paired = |by: fn(f64) -> f64| {
heard
.iter()
.enumerate()
.map(|(at, &sample)| f64::from(sample) * by(turn(at)))
.sum::<f64>()
/ (heard.len() as f64 / 2.0)
};
let (cosine, sine) = (paired(f64::cos), paired(f64::sin));
let left: f64 = heard
.iter()
.enumerate()
.map(|(at, &sample)| {
let tone = cosine * turn(at).cos() + sine * turn(at).sin();
(f64::from(sample) - tone).powi(2)
})
.sum();
let whole = (cosine * cosine + sine * sine) / 2.0 * heard.len() as f64;
(10.0 * (left / whole).log10()) as f32
}
#[test]
fn a_clip_of_another_rate_is_heard_as_its_tone_and_nothing_over_the_noise_floor() {
let rate = MixRate::chosen(SampleRate::new(44_100));
let recorded = SampleRate::new(48_000);
let heard = played(
&SoundData::mono(48_000, tone(recorded, 10_000.0, 12_000)),
rate,
8_820,
);
let noise = noise(&heard[2_205..6_615], 10_000.0, rate.get());
assert!(
noise < -96.0,
"a tone is heard with {noise} dB beside it, over the floor of a 16-bit recording"
);
}
#[test]
fn a_clip_decoded_as_it_plays_is_heard_as_the_one_decoded_at_the_start_is() {
let rate = MixRate::chosen(SampleRate::new(48_000));
let loaded = SoundData::loaded(Arc::new(
crate::assets::ogg::decode(crate::assets::SWEEP).expect("the fixture decodes"),
));
let whole = played(&loaded, rate, 4_000);
let streamed = played(&loaded.clone().streamed(), rate, 4_000);
assert_eq!(whole, streamed, "a packet at a time reads the same frames");
}
#[test]
fn a_clip_of_the_mix_rate_is_heard_frame_for_frame() {
let rate = MixRate::chosen(SampleRate::new(48_000));
let tone = tone(rate.get(), 1_000.0, 2_000);
let played = played(&SoundData::mono(48_000, tone.clone()), rate, 1_000);
assert_eq!(played.as_slice(), &tone[..1_000]);
}
#[test]
fn a_one_shot_plays_its_window_and_ends() {
let clip = counted(10);
let trimmed = Knobs {
trim: Some((seconds(2), seconds(5))),
..knobs()
};
let mut mixer = Mixer::new(MIX);
mixer.apply(Command::Play(voicing(&clip, &trimmed, false)));
assert_eq!(mixed(&mut mixer, 6), heard(&[2, 3, 4, 0, 0, 0]));
assert_eq!(mixer.sounding(), 0, "and it is gone");
}
#[test]
fn a_voice_takes_its_fade_to_come_up() {
let clip = flat(64);
let faded = Knobs {
fade: seconds(4),
..knobs()
};
let mut mixer = Mixer::new(MIX);
mixer.apply(Command::Play(voicing(&clip, &faded, false)));
assert_eq!(mixed(&mut mixer, 4), [0.125, 0.25, 0.375, 0.5]);
}
#[test]
fn a_loop_splices_its_seam_frame_for_frame() {
let clip = counted(10);
let looped = Knobs {
trim: Some((seconds(1), seconds(6))),
loop_from: Some(seconds(3)),
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(0), &clip, &looped, 1.0)]);
assert_eq!(
mixed(&mut mixer, 11),
heard(&[1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5]),
"the seam neither repeats a frame nor drops one"
);
}
#[test]
fn a_streamed_clip_reads_what_a_whole_decode_gives_and_wraps_the_same() {
let clip = streamed();
let whole = {
let Body::Encoded(encoded) = &clip.body else {
panic!("the fixture is streamed");
};
crate::assets::ogg::samples(Arc::clone(encoded), clip.rate).expect("it decodes")
};
let window = |frames: u64| Duration::from_secs_f64(frames as f64 / f64::from(clip.rate));
let looped = Knobs {
trim: Some((Duration::ZERO, window(1_000))),
loop_from: Some(window(500)),
..knobs()
};
let mut mixer = Mixer::new(MixRate::chosen(clip.rate));
declare(&mut mixer, vec![one(sustained(4), &clip, &looped, 1.0)]);
let heard = mixed(&mut mixer, 1_500);
let played = |window: core::ops::Range<ClipFrame>| {
&whole[window.start.get() as usize..window.end.get() as usize]
};
let window = Window::of(&clip, &looped, true).expect("it plays");
let wanted = played(window.start..window.end)
.iter()
.chain(played(window.wrap..window.end));
let off = heard
.iter()
.zip(wanted)
.map(|(from, to)| (from - to).abs())
.fold(0.0f32, f32::max);
assert_eq!(off, 0.0, "a stream plays and wraps sample for sample");
}
#[test]
fn a_streamed_clip_of_another_rate_wraps_where_a_whole_one_wraps() {
let rate = MixRate::chosen(SampleRate::new(48_000));
let loaded = SoundData::loaded(Arc::new(
crate::assets::ogg::decode(crate::assets::SWEEP).expect("the fixture decodes"),
));
let at = |frames: u64| Duration::from_secs_f64(frames as f64 / f64::from(rate.get()));
let looped = Knobs {
trim: Some((Duration::ZERO, at(1_000))),
loop_from: Some(at(500)),
..knobs()
};
let heard = |data: &SoundData| {
let clip = Arc::new(crate::sound::resolve(data, Some(rate)));
let mut mixer = Mixer::new(rate);
declare(&mut mixer, vec![one(sustained(8), &clip, &looped, 1.0)]);
mixed(&mut mixer, 1_500)
};
assert_eq!(
heard(&loaded),
heard(&loaded.clone().streamed()),
"a seam spliced out of packets reads as one spliced out of memory"
);
}
#[test]
fn a_re_declared_pitch_plays_on_at_the_new_step() {
let clip = counted(64);
let faster = Knobs {
pitch: 2.0,
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(5), &clip, &knobs(), 1.0)]);
assert_eq!(mixed(&mut mixer, 4), heard(&[0, 1, 2, 3]));
declare(&mut mixer, vec![one(sustained(5), &clip, &faster, 1.0)]);
assert_eq!(
mixed(&mut mixer, 4),
heard(&[4, 6, 8, 10]),
"twice the step, from the frame it had reached"
);
}
#[test]
fn a_re_declared_fade_paces_the_stop_that_follows() {
let clip = flat(64);
let slower = Knobs {
fade: seconds(4),
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(6), &clip, &knobs(), 1.0)]);
assert_eq!(mixed(&mut mixer, 1), [0.5], "up at once on no fade");
declare(&mut mixer, vec![one(sustained(6), &clip, &slower, 1.0)]);
declare(&mut mixer, Vec::new());
assert_eq!(mixed(&mut mixer, 4), [0.375, 0.25, 0.125, 0.0]);
}
#[test]
fn a_re_declared_window_leaves_the_voice_on_the_one_it_started_on() {
let clip = counted(16);
let first = Knobs {
trim: Some((seconds(0), seconds(4))),
..knobs()
};
let moved = Knobs {
trim: Some((seconds(8), seconds(12))),
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(7), &clip, &first, 1.0)]);
assert_eq!(mixed(&mut mixer, 4), heard(&[0, 1, 2, 3]));
declare(&mut mixer, vec![one(sustained(7), &clip, &moved, 1.0)]);
assert_eq!(
mixed(&mut mixer, 4),
heard(&[0, 1, 2, 3]),
"around the window it started on, not the one just declared"
);
}
#[test]
fn two_instances_of_one_value_sound_at_two_places_and_stop_one_at_a_time() {
let clip = clip(vec![0.25; 64]);
let here = sustained(1);
let elsewhere = Sustained {
sound: here.sound,
instance: 1,
};
let mut mixer = Mixer::new(MIX);
declare(
&mut mixer,
vec![
one(here, &clip, &knobs(), 1.0),
one(elsewhere, &clip, &knobs(), 1.0),
],
);
assert_eq!(mixed(&mut mixer, 1), [0.5], "both are heard at once");
assert_eq!(mixer.sounding(), 2, "on a voice each");
declare(
&mut mixer,
vec![
one(here, &clip, &knobs(), 1.0),
one(elsewhere, &clip, &knobs(), 0.5),
],
);
assert_eq!(
mixed(&mut mixer, 1),
[0.375],
"the levels of the instance they name, and of no other"
);
declare(&mut mixer, vec![one(here, &clip, &knobs(), 1.0)]);
mixed(&mut mixer, 1);
assert_eq!(mixer.sounding(), 1);
assert_eq!(mixed(&mut mixer, 1), [0.25], "and the other sounds on");
}
#[test]
fn a_sustain_declared_again_after_a_gap_starts_at_its_window_start() {
let clip = counted(64);
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(3), &clip, &knobs(), 1.0)]);
assert_eq!(mixed(&mut mixer, 4), heard(&[0, 1, 2, 3]));
declare(&mut mixer, Vec::new());
mixed(&mut mixer, 1);
assert_eq!(mixer.sounding(), 0, "with no fade it goes at once");
declare(&mut mixer, vec![one(sustained(3), &clip, &knobs(), 1.0)]);
assert_eq!(
mixed(&mut mixer, 2),
heard(&[0, 1]),
"from the top, since a value no frame declared is forgotten"
);
}
#[test]
fn a_sustain_the_rank_leaves_out_holds_no_voice_and_is_not_heard() {
let probe = counted(64);
let quiet = silent(64);
let mut mixer = Mixer::new(MIX);
let mut set = vec![one(sustained(9), &probe, &knobs(), 1.0)];
set.extend(filling(&quiet, 0, MAX_VOICES as u32 - 1, 0.5));
declare(&mut mixer, set);
assert_eq!(
mixed(&mut mixer, 4),
heard(&[0, 1, 2, 3]),
"heard while the rank voices it"
);
let mut set = vec![one(sustained(9), &probe, &knobs(), 0.25)];
set.extend(filling(&quiet, 0, MAX_VOICES as u32, 0.5));
declare(&mut mixer, set);
assert_eq!(
mixed(&mut mixer, 4),
heard(&[0, 0, 0, 0]),
"and silent once a louder one takes the last voice"
);
assert_eq!(mixer.sounding(), MAX_VOICES, "which the cap still bounds");
}
#[test]
fn a_sustain_voiced_again_enters_where_it_would_have_played_to() {
let probe = counted(64);
let quiet = silent(64);
let mut mixer = Mixer::new(MIX);
let mut set = vec![one(sustained(9), &probe, &knobs(), 1.0)];
set.extend(filling(&quiet, 0, MAX_VOICES as u32 - 1, 0.5));
declare(&mut mixer, set);
mixed(&mut mixer, 4);
let mut set = vec![one(sustained(9), &probe, &knobs(), 0.25)];
set.extend(filling(&quiet, 0, MAX_VOICES as u32, 0.5));
declare(&mut mixer, set);
mixed(&mut mixer, 4);
let mut set = vec![one(sustained(9), &probe, &knobs(), 1.0)];
set.extend(filling(&quiet, 0, MAX_VOICES as u32, 0.5));
declare(&mut mixer, set);
assert_eq!(
mixed(&mut mixer, 2),
heard(&[8, 9]),
"four frames voiced and four virtual, so it enters at the eighth"
);
}
#[test]
fn a_one_shot_the_rank_leaves_out_never_starts() {
let clip = counted(64);
let quiet = silent(64);
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, filling(&quiet, 0, MAX_VOICES as u32, 0.5));
mixed(&mut mixer, 1);
mixer.apply(Command::Play(leveled(&clip, &knobs(), false, 0.25)));
assert_eq!(mixed(&mut mixer, 2), heard(&[0, 0]), "nothing of it plays");
assert_eq!(mixer.sounding(), MAX_VOICES, "and it holds no voice");
}
#[test]
fn a_one_shot_louder_than_what_is_sounding_takes_a_voice_off_it() {
let clip = counted(64);
let quiet = silent(64);
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, filling(&quiet, 0, MAX_VOICES as u32, 0.25));
mixed(&mut mixer, 1);
mixer.apply(Command::Play(leveled(&clip, &knobs(), false, 1.0)));
assert_eq!(mixed(&mut mixer, 2), heard(&[0, 1]), "it is heard at once");
assert_eq!(mixer.sounding(), MAX_VOICES, "inside the cap all the same");
}
#[test]
fn a_playing_one_shot_the_rank_drops_fades_over_its_glide() {
let clip = flat(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let mut mixer = Mixer::new(MIX);
mixer.apply(Command::Play(leveled(&clip, &gliding, false, 1.0)));
assert_eq!(mixed(&mut mixer, 1), [0.5], "up at once on no fade");
declare(&mut mixer, filling(&silent(64), 0, MAX_VOICES as u32, 1.0));
assert_eq!(
mixed(&mut mixer, 4),
[0.375, 0.25, 0.125, 0.0],
"down over the glide it named"
);
assert_eq!(mixer.sounding(), MAX_VOICES, "and its voice is free");
}
#[test]
fn a_one_shot_the_rank_voices_again_rises_back_over_its_glide_and_plays_out() {
let clip = flat(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let quiet = silent(64);
let mut mixer = Mixer::new(MIX);
mixer.apply(Command::Play(leveled(&clip, &gliding, false, 1.0)));
assert_eq!(mixed(&mut mixer, 1), [0.5], "up at once on no fade");
declare(&mut mixer, filling(&quiet, 0, MAX_VOICES as u32, 2.0));
assert_eq!(mixed(&mut mixer, 2), [0.375, 0.25], "the rank cuts it");
declare(&mut mixer, filling(&quiet, 0, MAX_VOICES as u32, 0.25));
assert_eq!(
mixed(&mut mixer, 2),
[0.375, 0.5],
"back up over the same glide, since the rank voices it again"
);
assert_eq!(mixer.sounding(), MAX_VOICES, "and it plays on");
}
#[test]
fn a_sustain_at_no_gain_holds_no_voice() {
let clip = counted(64);
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(2), &clip, &knobs(), 0.0)]);
assert_eq!(mixed(&mut mixer, 4), heard(&[0, 0, 0, 0]));
assert_eq!(mixer.sounding(), 0, "however few are sounding");
}
#[test]
fn a_gain_a_frame_changes_slides_over_the_glide_it_named() {
let clip = flat(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(4), &clip, &gliding, 1.0)]);
assert_eq!(mixed(&mut mixer, 1), [0.5]);
declare(&mut mixer, vec![one(sustained(4), &clip, &gliding, 0.5)]);
assert_eq!(
mixed(&mut mixer, 4),
[0.4375, 0.375, 0.3125, 0.25],
"a quarter of the way down each frame, and no step"
);
}
#[test]
fn two_sustains_swapping_their_gains_crossfade_over_the_glide() {
let clip = flat(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let here = sustained(1);
let there = Sustained {
sound: here.sound,
instance: 1,
};
let mut mixer = Mixer::new(MIX);
let swap = |mixer: &mut Mixer, over: f32| {
declare(
mixer,
vec![
one(here, &clip, &gliding, 1.0 - over),
one(there, &clip, &gliding, over),
],
);
};
swap(&mut mixer, 0.0);
assert_eq!(mixed(&mut mixer, 1), [0.5], "one of them carries it all");
assert_eq!(mixer.sounding(), 1, "and the one at no gain holds no voice");
swap(&mut mixer, 1.0);
assert_eq!(
mixed(&mut mixer, 4),
[0.5; 4],
"the two together hold the mix steady while they swap"
);
assert_eq!(mixer.sounding(), 2, "both voiced while they slide");
assert_eq!(mixed(&mut mixer, 1), [0.5], "and the one left plays on");
assert_eq!(mixer.sounding(), 1, "with the faded one virtual again");
}
#[test]
fn the_master_volume_slides_to_what_a_frame_sets() {
let clip = steady();
let mut mixer = Mixer::new(MASTER);
declare(&mut mixer, vec![one(sustained(0), &clip, &knobs(), 1.0)]);
assert_eq!(mixed(&mut mixer, 1), [0.5]);
mixer.apply(Command::Volume(0.0));
assert_eq!(
mixed(&mut mixer, 4),
[0.375, 0.25, 0.125, 0.0],
"down over the glide, never a step"
);
}
#[test]
fn the_master_volume_declared_every_frame_slides_over_one_glide() {
let clip = steady();
let mut mixer = Mixer::new(MASTER);
declare(&mut mixer, vec![one(sustained(0), &clip, &knobs(), 1.0)]);
assert_eq!(mixed(&mut mixer, 1), [0.5]);
let heard: Vec<f32> = (0..4)
.map(|_| {
mixer.apply(Command::Volume(0.0));
mixed(&mut mixer, 1)[0]
})
.collect();
assert_eq!(
heard,
[0.375, 0.25, 0.125, 0.0],
"a frame loop that states the volume every frame slides over one glide, not one a frame"
);
}
#[test]
fn a_gain_declared_every_frame_reaches_its_target_at_the_end_of_one_glide() {
let clip = flat(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let mut mixer = Mixer::new(MIX);
declare(&mut mixer, vec![one(sustained(4), &clip, &gliding, 1.0)]);
assert_eq!(mixed(&mut mixer, 1), [0.5]);
let heard: Vec<f32> = (0..4)
.map(|_| {
declare(&mut mixer, vec![one(sustained(4), &clip, &gliding, 0.5)]);
mixed(&mut mixer, 1)[0]
})
.collect();
assert_eq!(
heard,
[0.4375, 0.375, 0.3125, 0.25],
"a frame loop that declares one gain every frame slides over one glide, not one a frame"
);
}
#[test]
fn a_sustain_the_rank_cuts_is_heard_fading_over_its_glide_and_plays_on_through_the_fade() {
let probe = counted(64);
let quiet = silent(64);
let gliding = Knobs {
glide: seconds(4),
..knobs()
};
let declaring = |mixer: &mut Mixer, fillers: u32, level: f32| {
let mut set = vec![(sustained(9), leveled(&probe, &gliding, true, 1.0))];
set.extend(filling(&quiet, 0, fillers, level));
declare(mixer, set);
};
let mut mixer = Mixer::new(MIX);
declaring(&mut mixer, MAX_VOICES as u32 - 1, 0.5);
assert_eq!(mixed(&mut mixer, 4), heard(&[0, 1, 2, 3]));
declaring(&mut mixer, MAX_VOICES as u32, 2.0);
assert_eq!(
mixed(&mut mixer, 4),
[3.0 / QUIET, 2.5 / QUIET, 1.5 / QUIET, 0.0],
"the frames it plays on through, at a level falling over the glide"
);
assert_eq!(mixer.sounding(), MAX_VOICES, "and then its slot is free");
declaring(&mut mixer, MAX_VOICES as u32, 0.5);
assert_eq!(
mixed(&mut mixer, 2),
heard(&[8, 9]),
"voiced again where the fade played it to"
);
}
}