From 4c29057a76b94ecbda888f625b5445eb11d01869 Mon Sep 17 00:00:00 2001 From: Andrew Minnich Date: Sun, 15 Dec 2024 05:31:09 -0500 Subject: [PATCH] use buffered processing todo: - re-enable delay effect - figure out if clocks need more precision --- crates/benchmarks/benches/benchmarks.rs | 12 +- crates/kira/src/effect.rs | 9 +- crates/kira/src/effect/compressor.rs | 69 ++++++----- crates/kira/src/effect/delay.rs | 8 +- crates/kira/src/effect/distortion.rs | 35 +++--- crates/kira/src/effect/eq_filter.rs | 23 ++-- crates/kira/src/effect/filter.rs | 37 +++--- crates/kira/src/effect/panning_control.rs | 8 +- crates/kira/src/effect/reverb.rs | 50 ++++---- crates/kira/src/effect/volume_control.rs | 8 +- crates/kira/src/lib.rs | 2 + .../backend/cpal/desktop/stream_manager.rs | 18 +-- crates/kira/src/manager/backend/cpal/wasm.rs | 10 +- crates/kira/src/manager/backend/mock.rs | 12 +- crates/kira/src/manager/backend/renderer.rs | 47 ++++++-- .../src/manager/backend/resources/mixer.rs | 23 +++- crates/kira/src/sound.rs | 14 ++- crates/kira/src/sound/static_sound/sound.rs | 33 ++--- .../kira/src/sound/static_sound/sound/test.rs | 108 ++++++++--------- crates/kira/src/sound/streaming/sound.rs | 70 ++++++----- crates/kira/src/sound/streaming/sound/test.rs | 114 +++++++++--------- crates/kira/src/track/main.rs | 19 +-- crates/kira/src/track/main/builder.rs | 3 +- crates/kira/src/track/send.rs | 23 ++-- crates/kira/src/track/send/builder.rs | 4 +- crates/kira/src/track/sub.rs | 46 ++++--- crates/kira/src/track/sub/builder.rs | 3 +- crates/kira/src/track/sub/spatial_builder.rs | 3 +- crates/kira/tests/change_sample_rate.rs | 7 +- .../tests/streaming_sound_stops_on_error.rs | 2 +- crates/kira/tests/tick_accuracy.rs | 5 +- 31 files changed, 462 insertions(+), 363 deletions(-) diff --git a/crates/benchmarks/benches/benchmarks.rs b/crates/benchmarks/benches/benchmarks.rs index 8e6984ca..545e23b9 100644 --- a/crates/benchmarks/benches/benchmarks.rs +++ b/crates/benchmarks/benches/benchmarks.rs @@ -48,7 +48,13 @@ fn sounds(c: &mut Criterion) { manager.play(sound_data.clone()).unwrap(); } manager.backend_mut().on_start_processing(); - b.iter(|| manager.backend_mut().process()); + let mut num_iterations = 0; + b.iter(|| { + if num_iterations % 128 == 0 { + manager.backend_mut().process(); + } + num_iterations += 1 + }); }); // similar to "simple", but also periodically calls the @@ -78,7 +84,9 @@ fn sounds(c: &mut Criterion) { if num_iterations % 1000 == 0 { manager.backend_mut().on_start_processing(); } - let _ = manager.backend_mut().process(); + if num_iterations % 128 == 0 { + manager.backend_mut().process(); + } num_iterations += 1; }); }); diff --git a/crates/kira/src/effect.rs b/crates/kira/src/effect.rs index bfbc5b1c..cb70603c 100644 --- a/crates/kira/src/effect.rs +++ b/crates/kira/src/effect.rs @@ -10,7 +10,7 @@ and [`Effect`] traits. */ pub mod compressor; -pub mod delay; +// pub mod delay; pub mod distortion; pub mod eq_filter; pub mod filter; @@ -48,9 +48,8 @@ pub trait Effect: Send { /// but not for every single audio sample. fn on_start_processing(&mut self) {} - /// Transforms an input [`Frame`]. + /// Transforms a slice of input [`Frame`]s. /// - /// `dt` is the time that's elapsed since the previous round of - /// processing (in seconds). - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame; + /// `dt` is the time between each frame (in seconds). + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info); } diff --git a/crates/kira/src/effect/compressor.rs b/crates/kira/src/effect/compressor.rs index 56c4ff64..3ed77f04 100644 --- a/crates/kira/src/effect/compressor.rs +++ b/crates/kira/src/effect/compressor.rs @@ -70,46 +70,49 @@ impl Effect for Compressor { ); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.threshold.update(dt, info); - self.ratio.update(dt, info); - self.attack_duration.update(dt, info); - self.release_duration.update(dt, info); - self.makeup_gain.update(dt, info); - self.mix.update(dt, info); + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.threshold.update(dt * input.len() as f64, info); + self.ratio.update(dt * input.len() as f64, info); + self.attack_duration.update(dt * input.len() as f64, info); + self.release_duration.update(dt * input.len() as f64, info); + self.makeup_gain.update(dt * input.len() as f64, info); + self.mix.update(dt * input.len() as f64, info); let threshold = self.threshold.value() as f32; let ratio = self.ratio.value() as f32; let attack_duration = self.attack_duration.value(); let release_duration = self.release_duration.value(); - let input_decibels = [ - 20.0 * input.left.abs().log10(), - 20.0 * input.right.abs().log10(), - ]; - let over_decibels = input_decibels.map(|input| (input - threshold).max(0.0)); - for (i, envelope_follower) in self.envelope_follower.iter_mut().enumerate() { - let duration = if *envelope_follower > over_decibels[i] { - release_duration - } else { - attack_duration - }; - let speed = (-1.0 / (duration.as_secs_f64() / dt)).exp(); - *envelope_follower = - over_decibels[i] + speed as f32 * (*envelope_follower - over_decibels[i]); - } - let gain_reduction = self - .envelope_follower - .map(|envelope_follower| envelope_follower * ((1.0 / ratio) - 1.0)); - let amplitude = gain_reduction.map(|gain_reduction| 10.0f32.powf(gain_reduction / 20.0)); - let makeup_gain_linear = 10.0f32.powf(self.makeup_gain.value().0 / 20.0); - let output = Frame { - left: amplitude[0] * input.left, - right: amplitude[1] * input.right, - } * makeup_gain_linear; + for frame in input { + let input_decibels = [ + 20.0 * frame.left.abs().log10(), + 20.0 * frame.right.abs().log10(), + ]; + let over_decibels = input_decibels.map(|input| (input - threshold).max(0.0)); + for (i, envelope_follower) in self.envelope_follower.iter_mut().enumerate() { + let duration = if *envelope_follower > over_decibels[i] { + release_duration + } else { + attack_duration + }; + let speed = (-1.0 / (duration.as_secs_f64() / dt)).exp(); + *envelope_follower = + over_decibels[i] + speed as f32 * (*envelope_follower - over_decibels[i]); + } + let gain_reduction = self + .envelope_follower + .map(|envelope_follower| envelope_follower * ((1.0 / ratio) - 1.0)); + let amplitude = + gain_reduction.map(|gain_reduction| 10.0f32.powf(gain_reduction / 20.0)); + let makeup_gain_linear = 10.0f32.powf(self.makeup_gain.value().0 / 20.0); + let output = Frame { + left: amplitude[0] * frame.left, + right: amplitude[1] * frame.right, + } * makeup_gain_linear; - let mix = self.mix.value().0; - output * mix.sqrt() + input * (1.0 - mix).sqrt() + let mix = self.mix.value().0; + *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() + } } } diff --git a/crates/kira/src/effect/delay.rs b/crates/kira/src/effect/delay.rs index 24bfc639..f6153079 100644 --- a/crates/kira/src/effect/delay.rs +++ b/crates/kira/src/effect/delay.rs @@ -95,16 +95,16 @@ impl Effect for Delay { } } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { if let DelayState::Initialized { buffer, write_position, .. } = &mut self.state { - self.delay_time.update(dt, info); - self.feedback.update(dt, info); - self.mix.update(dt, info); + self.delay_time.update(dt * input.len() as f64, info); + self.feedback.update(dt * input.len() as f64, info); + self.mix.update(dt * input.len() as f64, info); // get the read position (in samples) let mut read_position = *write_position as f32 - (self.delay_time.value() / dt) as f32; diff --git a/crates/kira/src/effect/distortion.rs b/crates/kira/src/effect/distortion.rs index daadb360..c221d7ec 100644 --- a/crates/kira/src/effect/distortion.rs +++ b/crates/kira/src/effect/distortion.rs @@ -55,24 +55,27 @@ impl Effect for Distortion { read_commands_into_parameters!(self, drive, mix); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.drive.update(dt, info); - self.mix.update(dt, info); + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.drive.update(dt * input.len() as f64, info); + self.mix.update(dt * input.len() as f64, info); let drive = self.drive.value().as_amplitude(); - let mut output = input * drive; - output = match self.kind { - DistortionKind::HardClip => { - Frame::new(output.left.clamp(-1.0, 1.0), output.right.clamp(-1.0, 1.0)) - } - DistortionKind::SoftClip => Frame::new( - output.left / (1.0 + output.left.abs()), - output.right / (1.0 + output.right.abs()), - ), - }; - output /= drive; - let mix = self.mix.value().0; - output * mix.sqrt() + input * (1.0 - mix).sqrt() + for frame in input { + let mut output = *frame * drive; + output = match self.kind { + DistortionKind::HardClip => { + Frame::new(output.left.clamp(-1.0, 1.0), output.right.clamp(-1.0, 1.0)) + } + DistortionKind::SoftClip => Frame::new( + output.left / (1.0 + output.left.abs()), + output.right / (1.0 + output.right.abs()), + ), + }; + output /= drive; + + let mix = self.mix.value().0; + *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() + } } } diff --git a/crates/kira/src/effect/eq_filter.rs b/crates/kira/src/effect/eq_filter.rs index 39eaf234..8f4f5593 100644 --- a/crates/kira/src/effect/eq_filter.rs +++ b/crates/kira/src/effect/eq_filter.rs @@ -123,10 +123,10 @@ impl Effect for EqFilter { read_commands_into_parameters!(self, frequency, gain, q); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.frequency.update(dt, info); - self.gain.update(dt, info); - self.q.update(dt, info); + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.frequency.update(dt * input.len() as f64, info); + self.gain.update(dt * input.len() as f64, info); + self.q.update(dt * input.len() as f64, info); let Coefficients { a1, a2, @@ -135,12 +135,15 @@ impl Effect for EqFilter { m1, m2, } = self.calculate_coefficients(dt); - let v3 = input - self.ic2eq; - let v1 = self.ic1eq * (a1 as f32) + v3 * (a2 as f32); - let v2 = self.ic2eq + self.ic1eq * (a2 as f32) + v3 * (a3 as f32); - self.ic1eq = v1 * 2.0 - self.ic1eq; - self.ic2eq = v2 * 2.0 - self.ic2eq; - input * (m0 as f32) + v1 * (m1 as f32) + v2 * (m2 as f32) + + for frame in input { + let v3 = *frame - self.ic2eq; + let v1 = self.ic1eq * (a1 as f32) + v3 * (a2 as f32); + let v2 = self.ic2eq + self.ic1eq * (a2 as f32) + v3 * (a3 as f32); + self.ic1eq = v1 * 2.0 - self.ic1eq; + self.ic2eq = v2 * 2.0 - self.ic2eq; + *frame = *frame * (m0 as f32) + v1 * (m1 as f32) + v2 * (m2 as f32) + } } } diff --git a/crates/kira/src/effect/filter.rs b/crates/kira/src/effect/filter.rs index 68284599..6f779796 100644 --- a/crates/kira/src/effect/filter.rs +++ b/crates/kira/src/effect/filter.rs @@ -69,29 +69,32 @@ impl Effect for Filter { read_commands_into_parameters!(self, cutoff, resonance, mix); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.cutoff.update(dt, info); - self.resonance.update(dt, info); - self.mix.update(dt, info); + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.cutoff.update(dt * input.len() as f64, info); + self.resonance.update(dt * input.len() as f64, info); + self.mix.update(dt * input.len() as f64, info); let sample_rate = 1.0 / dt; let g = (PI * (self.cutoff.value() / sample_rate)).tan(); let k = 2.0 - (1.9 * self.resonance.value().clamp(0.0, 1.0)); let a1 = 1.0 / (1.0 + (g * (g + k))); let a2 = g * a1; let a3 = g * a2; - let v3 = input - self.ic2eq; - let v1 = (self.ic1eq * (a1 as f32)) + (v3 * (a2 as f32)); - let v2 = self.ic2eq + (self.ic1eq * (a2 as f32)) + (v3 * (a3 as f32)); - self.ic1eq = (v1 * 2.0) - self.ic1eq; - self.ic2eq = (v2 * 2.0) - self.ic2eq; - let output = match self.mode { - FilterMode::LowPass => v2, - FilterMode::BandPass => v1, - FilterMode::HighPass => input - v1 * (k as f32) - v2, - FilterMode::Notch => input - v1 * (k as f32), - }; - let mix = self.mix.value().0; - output * mix.sqrt() + input * (1.0 - mix).sqrt() + + for frame in input { + let v3 = *frame - self.ic2eq; + let v1 = (self.ic1eq * (a1 as f32)) + (v3 * (a2 as f32)); + let v2 = self.ic2eq + (self.ic1eq * (a2 as f32)) + (v3 * (a3 as f32)); + self.ic1eq = (v1 * 2.0) - self.ic1eq; + self.ic2eq = (v2 * 2.0) - self.ic2eq; + let output = match self.mode { + FilterMode::LowPass => v2, + FilterMode::BandPass => v1, + FilterMode::HighPass => *frame - v1 * (k as f32) - v2, + FilterMode::Notch => *frame - v1 * (k as f32), + }; + let mix = self.mix.value().0; + *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() + } } } diff --git a/crates/kira/src/effect/panning_control.rs b/crates/kira/src/effect/panning_control.rs index 4ef796d1..edf3a9cb 100644 --- a/crates/kira/src/effect/panning_control.rs +++ b/crates/kira/src/effect/panning_control.rs @@ -37,9 +37,11 @@ impl Effect for PanningControl { read_commands_into_parameters!(self, panning); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.panning.update(dt, info); - input.panned(self.panning.value()) + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.panning.update(dt * input.len() as f64, info); + for frame in input { + *frame = frame.panned(self.panning.value()) + } } } diff --git a/crates/kira/src/effect/reverb.rs b/crates/kira/src/effect/reverb.rs index 2eb9c8b5..1c287df1 100644 --- a/crates/kira/src/effect/reverb.rs +++ b/crates/kira/src/effect/reverb.rs @@ -138,41 +138,43 @@ impl Effect for Reverb { read_commands_into_parameters!(self, feedback, damping, stereo_width, mix); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { if let ReverbState::Initialized { comb_filters, all_pass_filters, } = &mut self.state { - self.feedback.update(dt, info); - self.damping.update(dt, info); - self.stereo_width.update(dt, info); - self.mix.update(dt, info); + self.feedback.update(dt * input.len() as f64, info); + self.damping.update(dt * input.len() as f64, info); + self.stereo_width.update(dt * input.len() as f64, info); + self.mix.update(dt * input.len() as f64, info); let feedback = self.feedback.value() as f32; let damping = self.damping.value() as f32; let stereo_width = self.stereo_width.value() as f32; - let mut output = Frame::ZERO; - let mono_input = (input.left + input.right) * GAIN; - // accumulate comb filters in parallel - for comb_filter in comb_filters { - output.left += comb_filter.0.process(mono_input, feedback, damping); - output.right += comb_filter.1.process(mono_input, feedback, damping); + for frame in input { + let mut output = Frame::ZERO; + let mono_input = (frame.left + frame.right) * GAIN; + // accumulate comb filters in parallel + for comb_filter in comb_filters.iter_mut() { + output.left += comb_filter.0.process(mono_input, feedback, damping); + output.right += comb_filter.1.process(mono_input, feedback, damping); + } + // feed through all-pass filters in series + for all_pass_filter in all_pass_filters.iter_mut() { + output.left = all_pass_filter.0.process(output.left); + output.right = all_pass_filter.1.process(output.right); + } + let wet_1 = stereo_width / 2.0 + 0.5; + let wet_2 = (1.0 - stereo_width) / 2.0; + let output = Frame::new( + output.left * wet_1 + output.right * wet_2, + output.right * wet_1 + output.left * wet_2, + ); + let mix = self.mix.value().0; + *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() } - // feed through all-pass filters in series - for all_pass_filter in all_pass_filters { - output.left = all_pass_filter.0.process(output.left); - output.right = all_pass_filter.1.process(output.right); - } - let wet_1 = stereo_width / 2.0 + 0.5; - let wet_2 = (1.0 - stereo_width) / 2.0; - let output = Frame::new( - output.left * wet_1 + output.right * wet_2, - output.right * wet_1 + output.left * wet_2, - ); - let mix = self.mix.value().0; - output * mix.sqrt() + input * (1.0 - mix).sqrt() } else { panic!("Reverb should be initialized before the first process call") } diff --git a/crates/kira/src/effect/volume_control.rs b/crates/kira/src/effect/volume_control.rs index 874014fa..70e32602 100644 --- a/crates/kira/src/effect/volume_control.rs +++ b/crates/kira/src/effect/volume_control.rs @@ -37,9 +37,11 @@ impl Effect for VolumeControl { read_commands_into_parameters!(self, volume); } - fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.volume.update(dt, info); - input * self.volume.value().as_amplitude() + fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { + self.volume.update(dt * input.len() as f64, info); + for frame in input { + *frame *= self.volume.value().as_amplitude(); + } } } diff --git a/crates/kira/src/lib.rs b/crates/kira/src/lib.rs index 624e73c4..7b25a3d4 100644 --- a/crates/kira/src/lib.rs +++ b/crates/kira/src/lib.rs @@ -245,6 +245,8 @@ and compile times for games. #![warn(missing_docs)] #![allow(clippy::tabs_in_doc_comments)] +const INTERNAL_BUFFER_SIZE: usize = 128; + mod arena; pub mod clock; pub mod command; diff --git a/crates/kira/src/manager/backend/cpal/desktop/stream_manager.rs b/crates/kira/src/manager/backend/cpal/desktop/stream_manager.rs index a14f45c4..3b218074 100644 --- a/crates/kira/src/manager/backend/cpal/desktop/stream_manager.rs +++ b/crates/kira/src/manager/backend/cpal/desktop/stream_manager.rs @@ -198,21 +198,5 @@ fn device_name(device: &Device) -> String { fn process_renderer(renderer_wrapper: &mut RendererWrapper, data: &mut [f32], channels: u16) { renderer_wrapper.on_start_processing(); - for frame in data.chunks_exact_mut(channels as usize) { - let out = renderer_wrapper.process(); - if channels == 1 { - frame[0] = (out.left + out.right) / 2.0; - } else { - frame[0] = out.left; - frame[1] = out.right; - /* - if there's more channels, send silence to them. if we don't, - we might get bad sounds outputted to those channels. - (https://github.com/tesselode/kira/issues/50) - */ - for channel in frame.iter_mut().skip(2) { - *channel = 0.0; - } - } - } + renderer_wrapper.process(data, channels); } diff --git a/crates/kira/src/manager/backend/cpal/wasm.rs b/crates/kira/src/manager/backend/cpal/wasm.rs index 3c93d70a..bd4231bc 100644 --- a/crates/kira/src/manager/backend/cpal/wasm.rs +++ b/crates/kira/src/manager/backend/cpal/wasm.rs @@ -56,15 +56,7 @@ impl Backend for CpalBackend { &config, move |data: &mut [f32], _| { renderer.on_start_processing(); - for frame in data.chunks_exact_mut(channels as usize) { - let out = renderer.process(); - if channels == 1 { - frame[0] = (out.left + out.right) / 2.0; - } else { - frame[0] = out.left; - frame[1] = out.right; - } - } + renderer.process(data, channels); }, move |_| {}, None, diff --git a/crates/kira/src/manager/backend/mock.rs b/crates/kira/src/manager/backend/mock.rs index 9f186412..a422421d 100644 --- a/crates/kira/src/manager/backend/mock.rs +++ b/crates/kira/src/manager/backend/mock.rs @@ -2,7 +2,7 @@ use std::sync::Mutex; -use crate::frame::Frame; +use crate::INTERNAL_BUFFER_SIZE; use super::{Backend, Renderer}; @@ -32,6 +32,7 @@ impl Default for MockBackendSettings { pub struct MockBackend { sample_rate: u32, state: State, + frames: Vec, } impl MockBackend { @@ -60,10 +61,12 @@ impl MockBackend { } /// Calls the [`process`](Renderer::process) callback of the [`Renderer`]. - #[must_use] - pub fn process(&mut self) -> Frame { + pub fn process(&mut self) { if let State::Initialized { renderer } = &mut self.state { - renderer.get_mut().expect("mutex poisoned").process() + renderer + .get_mut() + .expect("mutex poisoned") + .process(&mut self.frames, 2) } else { panic!("backend is not initialized") } @@ -80,6 +83,7 @@ impl Backend for MockBackend { Self { sample_rate: settings.sample_rate, state: State::Uninitialized, + frames: vec![0.0; INTERNAL_BUFFER_SIZE * 2], }, settings.sample_rate, )) diff --git a/crates/kira/src/manager/backend/renderer.rs b/crates/kira/src/manager/backend/renderer.rs index 65506d28..899772db 100644 --- a/crates/kira/src/manager/backend/renderer.rs +++ b/crates/kira/src/manager/backend/renderer.rs @@ -3,7 +3,7 @@ use std::sync::{ Arc, }; -use crate::Frame; +use crate::{Frame, INTERNAL_BUFFER_SIZE}; use super::resources::Resources; @@ -30,6 +30,7 @@ pub struct Renderer { dt: f64, shared: Arc, resources: Resources, + temp_buffer: Vec, } impl Renderer { @@ -39,6 +40,7 @@ impl Renderer { dt: 1.0 / shared.sample_rate.load(Ordering::SeqCst) as f64, shared, resources, + temp_buffer: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], } } @@ -59,29 +61,58 @@ impl Renderer { self.resources.modulators.on_start_processing(); } - /// Produces the next [`Frame`] of audio. - #[must_use] - pub fn process(&mut self) -> Frame { + /// Produces the next [`Frame`]s of audio. + pub fn process(&mut self, out: &mut [f32], num_channels: u16) { + for chunk in out.chunks_mut(INTERNAL_BUFFER_SIZE * num_channels as usize) { + self.process_chunk(chunk, num_channels); + } + } + + fn process_chunk(&mut self, chunk: &mut [f32], num_channels: u16) { + let num_frames = chunk.len() / num_channels as usize; + self.resources.modulators.process( - self.dt, + self.dt * num_frames as f64, &self.resources.clocks, &self.resources.listeners, ); self.resources.clocks.update( - self.dt, + self.dt * num_frames as f64, &self.resources.modulators, &self.resources.listeners, ); self.resources.listeners.update( - self.dt, + self.dt * num_frames as f64, &self.resources.clocks, &self.resources.modulators, ); + self.resources.mixer.process( + &mut self.temp_buffer[..num_frames], self.dt, &self.resources.clocks, &self.resources.modulators, &self.resources.listeners, - ) + ); + + // convert from frames to requested number of channels + for (i, channels) in chunk.chunks_mut(num_channels.into()).enumerate() { + let frame = self.temp_buffer[i]; + if num_channels == 1 { + channels[0] = (frame.left + frame.right) / 2.0; + } else { + channels[0] = frame.left; + channels[1] = frame.right; + /* + if there's more channels, send silence to them. if we don't, + we might get bad sounds outputted to those channels. + (https://github.com/tesselode/kira/issues/50) + */ + for channel in channels.iter_mut().skip(2) { + *channel = 0.0; + } + } + } + self.temp_buffer.fill(Frame::ZERO); } } diff --git a/crates/kira/src/manager/backend/resources/mixer.rs b/crates/kira/src/manager/backend/resources/mixer.rs index 6cb153e1..ec712054 100644 --- a/crates/kira/src/manager/backend/resources/mixer.rs +++ b/crates/kira/src/manager/backend/resources/mixer.rs @@ -2,6 +2,7 @@ use crate::{ frame::Frame, info::Info, track::{MainTrack, MainTrackBuilder, MainTrackHandle, SendTrack, Track}, + INTERNAL_BUFFER_SIZE, }; use super::{ @@ -13,6 +14,7 @@ pub(crate) struct Mixer { main_track: MainTrack, sub_tracks: ResourceStorage, send_tracks: ResourceStorage, + temp_buffer: Vec, } impl Mixer { @@ -37,6 +39,7 @@ impl Mixer { main_track, sub_tracks, send_tracks, + temp_buffer: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], }, sub_track_controller, send_track_controller, @@ -68,17 +71,17 @@ impl Mixer { self.main_track.on_start_processing(); } - #[must_use] pub fn process( &mut self, + out: &mut [Frame], dt: f64, clocks: &Clocks, modulators: &Modulators, listeners: &Listeners, - ) -> Frame { - let mut main_track_input = Frame::ZERO; + ) { for (_, track) in &mut self.sub_tracks { - main_track_input += track.process( + track.process( + &mut self.temp_buffer[..out.len()], dt, clocks, modulators, @@ -86,6 +89,10 @@ impl Mixer { None, &mut self.send_tracks, ); + for (summed_out, sound_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { + *summed_out += sound_out; + } + self.temp_buffer.fill(Frame::ZERO); } let info = Info::new( &clocks.0.resources, @@ -94,8 +101,12 @@ impl Mixer { None, ); for (_, track) in &mut self.send_tracks { - main_track_input += track.process(dt, &info); + track.process(&mut self.temp_buffer[..out.len()], dt, &info); + for (summed_out, sound_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { + *summed_out += sound_out; + } + self.temp_buffer.fill(Frame::ZERO); } - self.main_track.process(main_track_input, dt, &info) + self.main_track.process(out, dt, &info); } } diff --git a/crates/kira/src/sound.rs b/crates/kira/src/sound.rs index a6f12cbe..d8055aba 100644 --- a/crates/kira/src/sound.rs +++ b/crates/kira/src/sound.rs @@ -65,11 +65,17 @@ pub trait Sound: Send { /// but not for every single audio sample. fn on_start_processing(&mut self) {} - /// Produces the next [`Frame`] of audio. + /// Produces the next [`Frame`]s of audio. This should overwrite + /// the entire `out` slice with new audio. /// - /// `dt` is the time that's elapsed since the previous round of - /// processing (in seconds). - fn process(&mut self, dt: f64, info: &Info) -> Frame; + /// `dt` is the time between each frame (in seconds). + fn process(&mut self, out: &mut [Frame], dt: f64, info: &Info); + + fn process_one(&mut self, dt: f64, info: &Info) -> Frame { + let mut out = [Frame::ZERO]; + self.process(&mut out, dt, info); + out[0] + } /// Returns `true` if the sound is finished and can be unloaded. /// diff --git a/crates/kira/src/sound/static_sound/sound.rs b/crates/kira/src/sound/static_sound/sound.rs index 2e55d381..62e9775f 100644 --- a/crates/kira/src/sound/static_sound/sound.rs +++ b/crates/kira/src/sound/static_sound/sound.rs @@ -207,34 +207,39 @@ impl Sound for StaticSound { self.read_commands(); } - fn process(&mut self, dt: f64, info: &Info) -> Frame { + fn process(&mut self, out: &mut [Frame], dt: f64, info: &Info) { // update parameters - self.volume.update(dt, info); - self.playback_rate.update(dt, info); - self.panning.update(dt, info); - let changed_playback_state = self.playback_state_manager.update(dt, info); + self.volume.update(dt * out.len() as f64, info); + self.playback_rate.update(dt * out.len() as f64, info); + self.panning.update(dt * out.len() as f64, info); + let changed_playback_state = self + .playback_state_manager + .update(dt * out.len() as f64, info); if changed_playback_state { self.update_shared_playback_state(); } - let will_never_start = self.start_time.update(dt, info); + let will_never_start = self.start_time.update(dt * out.len() as f64, info); if will_never_start { self.playback_state_manager.mark_as_stopped(); self.update_shared_playback_state(); } if self.start_time != StartTime::Immediate { - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } // play back audio - let out = self.resampler.get(self.fractional_position as f32); - self.fractional_position += - self.sample_rate as f64 * self.playback_rate.value().0.abs() * dt; - while self.fractional_position >= 1.0 { - self.fractional_position -= 1.0; - self.update_position(); + for frame in out { + let out = self.resampler.get(self.fractional_position as f32); + self.fractional_position += + self.sample_rate as f64 * self.playback_rate.value().0.abs() * dt; + while self.fractional_position >= 1.0 { + self.fractional_position -= 1.0; + self.update_position(); + } + *frame = out; } - out } fn finished(&self) -> bool { diff --git a/crates/kira/src/sound/static_sound/sound/test.rs b/crates/kira/src/sound/static_sound/sound/test.rs index a8b44937..a7b26448 100644 --- a/crates/kira/src/sound/static_sound/sound/test.rs +++ b/crates/kira/src/sound/static_sound/sound/test.rs @@ -33,7 +33,7 @@ fn plays_all_samples() { for i in 1..=3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); assert!(!sound.finished()); @@ -43,7 +43,7 @@ fn plays_all_samples() { // get silent output. for _ in 0..10 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); } @@ -73,7 +73,7 @@ fn reports_playback_state() { handle.state(), sound.playback_state_manager.playback_state() ); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); } } @@ -95,7 +95,7 @@ fn reports_playback_position() { handle.position(), i.clamp(0, 10) as f64 / sound.sample_rate as f64 ); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); sound.on_start_processing(); } } @@ -113,7 +113,7 @@ fn pauses_and_resumes_with_fades() { }; let (mut sound, mut handle) = data.split(); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); assert_eq!( sound.playback_state_manager.playback_state(), PlaybackState::Playing @@ -136,11 +136,11 @@ fn pauses_and_resumes_with_fades() { &mut sound, ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-45.0).as_amplitude()).panned(Panning::CENTER) ); @@ -148,7 +148,7 @@ fn pauses_and_resumes_with_fades() { let position = handle.position(); for _ in 0..10 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -176,7 +176,7 @@ fn pauses_and_resumes_with_fades() { PlaybackState::Playing ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( @@ -184,7 +184,7 @@ fn pauses_and_resumes_with_fades() { PlaybackState::Playing ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-15.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( @@ -194,7 +194,7 @@ fn pauses_and_resumes_with_fades() { for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); assert_eq!( @@ -216,7 +216,7 @@ fn stops_with_fade_out() { }; let (mut sound, mut handle) = data.split(); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); assert_eq!( sound.playback_state_manager.playback_state(), PlaybackState::Playing @@ -239,11 +239,11 @@ fn stops_with_fade_out() { &mut sound, ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-45.0).as_amplitude()).panned(Panning::CENTER) ); @@ -251,7 +251,7 @@ fn stops_with_fade_out() { let position = handle.position(); for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -287,7 +287,7 @@ fn waits_for_start_time() { // the sound should not be playing yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -300,7 +300,7 @@ fn waits_for_start_time() { // the sound is set to start at tick 2, so it should not // play yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -313,7 +313,7 @@ fn waits_for_start_time() { // a different clock reached tick 2, so the sound should // not play yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -326,7 +326,7 @@ fn waits_for_start_time() { // the sound should start playing now for i in 1..10 { assert_eq!( - sound.process(1.0, &info), + sound.process_one(1.0, &info), Frame::from_mono(i as f32).panned(Panning::CENTER) ); } @@ -356,14 +356,14 @@ fn stops_if_waiting_on_missing_clock() { }; let (mut sound, handle) = data.split(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); // the clock is removed let info = MockInfoBuilder::new().build(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Stopped); assert!(sound.finished()); @@ -389,7 +389,7 @@ fn continues_when_clock_stops() { }; let (mut sound, _) = data.split(); - assert_eq!(sound.process(1.0, &info), Frame::from_mono(1.0),); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(1.0),); let info = { let mut builder = MockInfoBuilder::new(); @@ -397,7 +397,7 @@ fn continues_when_clock_stops() { builder.build() }; - assert_eq!(sound.process(1.0, &info), Frame::from_mono(1.0),); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(1.0),); } /// Tests that a `StaticSound` can be paused, resumed, and stopped immediately @@ -425,7 +425,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Paused); @@ -434,7 +434,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); @@ -443,7 +443,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Stopped); } @@ -468,7 +468,7 @@ fn resume_at() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Paused); @@ -484,7 +484,7 @@ fn resume_at() { }, ); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::WaitingToResume); @@ -493,10 +493,10 @@ fn resume_at() { builder.add_clock(true, 1, 0.0); builder.build() }; - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Resuming); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); } @@ -516,7 +516,7 @@ fn start_position() { for i in 3..=6 { assert_eq!(handle.position(), i as f64); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -535,7 +535,7 @@ fn out_of_bounds_start_position() { slice: None, }; let (mut sound, _) = data.split(); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); } /// Tests that a `StaticSound` properly obeys looping behavior when @@ -553,25 +553,25 @@ fn loops_forward() { for i in 0..6 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); } assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(3.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(5.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(4.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(3.0).panned(Panning::CENTER) ); } @@ -589,7 +589,7 @@ fn volume() { let (mut sound, _) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-6.0).as_amplitude()).panned(Panning::CENTER) ); } @@ -607,7 +607,7 @@ fn set_volume() { let (mut sound, mut handle) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); handle.set_volume( @@ -637,7 +637,7 @@ fn panning() { let (mut sound, _) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::LEFT) ); } @@ -655,7 +655,7 @@ fn set_panning() { let (mut sound, mut handle) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); handle.set_panning( @@ -682,11 +682,11 @@ fn playback_rate() { let (mut sound, _) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(2.0).panned(Panning::CENTER) ); } @@ -705,11 +705,11 @@ fn set_playback_rate() { let (mut sound, mut handle) = data.split(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); @@ -723,11 +723,11 @@ fn set_playback_rate() { sound.on_start_processing(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(2.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(4.0).panned(Panning::CENTER) ); } @@ -753,15 +753,15 @@ fn interpolates_samples() { let (mut sound, _) = data.split(); assert_eq!( - sound.process(0.5, &MockInfoBuilder::new().build()), + sound.process_one(0.5, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); // at sample 0.5, the output should be somewhere between 0 and 1. // i don't care what exactly, that's up the to the interpolation algorithm. - let frame = sound.process(5.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(5.0, &MockInfoBuilder::new().build()); assert!(frame.left > 0.0 && frame.left < 1.0); // at sample 5.5, the output should be between 1 and -10. - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); assert!(frame.left < 0.0 && frame.left > -10.0); } @@ -776,10 +776,10 @@ fn interpolates_samples_when_looping() { slice: None, }; let (mut sound, _) = data.split(); - sound.process(1.5, &MockInfoBuilder::new().build()); + sound.process_one(1.5, &MockInfoBuilder::new().build()); // because we're looping back to the first sample, which is 10.0, // the interpolated sample should be be tween 9.0 and 10.0 - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); assert!(frame.left > 9.0 && frame.left < 10.0); } @@ -829,7 +829,7 @@ fn reverse() { for i in (4..=9).rev() { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); } @@ -838,7 +838,7 @@ fn reverse() { fn expect_frame_soon(expected_frame: Frame, sound: &mut StaticSound) { const NUM_SAMPLES_TO_WAIT: usize = 10; for _ in 0..NUM_SAMPLES_TO_WAIT { - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); if frame == expected_frame { return; } diff --git a/crates/kira/src/sound/streaming/sound.rs b/crates/kira/src/sound/streaming/sound.rs index e44ac578..b7998a51 100644 --- a/crates/kira/src/sound/streaming/sound.rs +++ b/crates/kira/src/sound/streaming/sound.rs @@ -200,61 +200,71 @@ impl Sound for StreamingSound { self.read_commands(); } - fn process(&mut self, dt: f64, info: &Info) -> Frame { + fn process(&mut self, out: &mut [Frame], dt: f64, info: &Info) { if self.shared.encountered_error() { self.playback_state_manager.mark_as_stopped(); self.update_shared_playback_state(); - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } // update parameters - self.volume.update(dt, info); - self.playback_rate.update(dt, info); - self.panning.update(dt, info); - let changed_playback_state = self.playback_state_manager.update(dt, info); + self.volume.update(dt * out.len() as f64, info); + self.playback_rate.update(dt * out.len() as f64, info); + self.panning.update(dt * out.len() as f64, info); + let changed_playback_state = self + .playback_state_manager + .update(dt * out.len() as f64, info); if changed_playback_state { self.update_shared_playback_state(); } - let will_never_start = self.start_time.update(dt, info); + let will_never_start = self.start_time.update(dt * out.len() as f64, info); if will_never_start { self.playback_state_manager.mark_as_stopped(); self.update_shared_playback_state(); } if self.start_time != StartTime::Immediate { - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } if !self.playback_state_manager.playback_state().is_advancing() { - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } // pause playback while waiting for audio data. the first frame // in the ringbuffer is the previous frame, so we need to make // sure there's at least 2 before we continue playing. if self.frame_consumer.slots() < 2 && !self.shared.reached_end() { - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } - let next_frames = self.next_frames(); - let out = interpolate_frame( - next_frames[0], - next_frames[1], - next_frames[2], - next_frames[3], - self.fractional_position as f32, - ); - self.fractional_position += - self.sample_rate as f64 * self.playback_rate.value().0.max(0.0) * dt; - while self.fractional_position >= 1.0 { - self.fractional_position -= 1.0; - self.frame_consumer.pop().ok(); - } - if self.shared.reached_end() && self.frame_consumer.is_empty() { - self.playback_state_manager.mark_as_stopped(); - self.update_shared_playback_state(); + + for frame in out { + let next_frames = self.next_frames(); + let out = interpolate_frame( + next_frames[0], + next_frames[1], + next_frames[2], + next_frames[3], + self.fractional_position as f32, + ); + self.fractional_position += + self.sample_rate as f64 * self.playback_rate.value().0.max(0.0) * dt; + while self.fractional_position >= 1.0 { + self.fractional_position -= 1.0; + self.frame_consumer.pop().ok(); + } + if self.shared.reached_end() && self.frame_consumer.is_empty() { + self.playback_state_manager.mark_as_stopped(); + self.update_shared_playback_state(); + } + *frame = (out + * self.playback_state_manager.fade_volume().as_amplitude() + * self.volume.value().as_amplitude()) + .panned(self.panning.value()); } - (out * self.playback_state_manager.fade_volume().as_amplitude() - * self.volume.value().as_amplitude()) - .panned(self.panning.value()) } fn finished(&self) -> bool { diff --git a/crates/kira/src/sound/streaming/sound/test.rs b/crates/kira/src/sound/streaming/sound/test.rs index 9a8779ab..f2df3b03 100644 --- a/crates/kira/src/sound/streaming/sound/test.rs +++ b/crates/kira/src/sound/streaming/sound/test.rs @@ -33,7 +33,7 @@ fn plays_all_samples() { for i in 1..=3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); assert!(!sound.finished()); @@ -43,7 +43,7 @@ fn plays_all_samples() { // get silent output. for _ in 0..10 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); } @@ -72,7 +72,7 @@ fn waits_for_samples() { for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::ZERO.panned(Panning::CENTER) ); sound.on_start_processing(); @@ -86,7 +86,7 @@ fn waits_for_samples() { for i in 1..=3 { assert_eq!(handle.position(), (i - 1) as f64); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -94,7 +94,7 @@ fn waits_for_samples() { for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::ZERO.panned(Panning::CENTER) ); sound.on_start_processing(); @@ -119,7 +119,7 @@ fn reports_playback_state() { handle.state(), sound.playback_state_manager.playback_state() ); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); } } @@ -138,7 +138,7 @@ fn reports_playback_position() { for i in 0..20 { assert_eq!(handle.position(), i.clamp(0, 9) as f64); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); sound.on_start_processing(); } } @@ -156,7 +156,7 @@ fn pauses_and_resumes_with_fades() { let (mut sound, mut handle, mut scheduler) = data.split().unwrap(); while matches!(scheduler.run().unwrap(), NextStep::Continue) {} - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); assert_eq!( sound.playback_state_manager.playback_state(), PlaybackState::Playing @@ -179,20 +179,20 @@ fn pauses_and_resumes_with_fades() { &mut sound, ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-45.0).as_amplitude()).panned(Panning::CENTER) ); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); sound.on_start_processing(); let position = handle.position(); for _ in 0..10 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -220,7 +220,7 @@ fn pauses_and_resumes_with_fades() { PlaybackState::Resuming ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( @@ -228,7 +228,7 @@ fn pauses_and_resumes_with_fades() { PlaybackState::Resuming ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-15.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( @@ -238,7 +238,7 @@ fn pauses_and_resumes_with_fades() { for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); assert_eq!( @@ -260,7 +260,7 @@ fn stops_with_fade_out() { let (mut sound, mut handle, mut scheduler) = data.split().unwrap(); while matches!(scheduler.run().unwrap(), NextStep::Continue) {} - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); assert_eq!( sound.playback_state_manager.playback_state(), PlaybackState::Playing @@ -283,20 +283,20 @@ fn stops_with_fade_out() { &mut sound, ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-30.0).as_amplitude()).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-45.0).as_amplitude()).panned(Panning::CENTER) ); - sound.process(1.0, &MockInfoBuilder::new().build()); + sound.process_one(1.0, &MockInfoBuilder::new().build()); sound.on_start_processing(); let position = handle.position(); for _ in 0..3 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -334,7 +334,7 @@ fn waits_for_start_time() { // the sound should not be playing yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -347,7 +347,7 @@ fn waits_for_start_time() { // the sound is set to start at tick 2, so it should not // play yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -360,7 +360,7 @@ fn waits_for_start_time() { // a different clock reached tick 2, so the sound should // not play yet for _ in 0..3 { - assert_eq!(sound.process(1.0, &info), Frame::from_mono(0.0)); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(0.0)); } let info = { @@ -373,7 +373,7 @@ fn waits_for_start_time() { // the sound should start playing now for i in 1..10 { assert_eq!( - sound.process(1.0, &info), + sound.process_one(1.0, &info), Frame::from_mono(i as f32).panned(Panning::CENTER) ); } @@ -405,14 +405,14 @@ fn stops_if_waiting_on_missing_clock() { let (mut sound, handle, mut scheduler) = data.split().unwrap(); while matches!(scheduler.run().unwrap(), NextStep::Continue) {} - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); // the clock is removed let info = MockInfoBuilder::new().build(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Stopped); assert!(sound.finished()); @@ -440,7 +440,7 @@ fn continues_when_clock_stops() { let (mut sound, _, mut scheduler) = data.split().unwrap(); while matches!(scheduler.run().unwrap(), NextStep::Continue) {} - assert_eq!(sound.process(1.0, &info), Frame::from_mono(1.0),); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(1.0),); let info = { let mut builder = MockInfoBuilder::new(); @@ -448,7 +448,7 @@ fn continues_when_clock_stops() { builder.build() }; - assert_eq!(sound.process(1.0, &info), Frame::from_mono(1.0),); + assert_eq!(sound.process_one(1.0, &info), Frame::from_mono(1.0),); } /// Tests that a `StreamingSound` can be paused, resumed, and stopped immediately @@ -478,7 +478,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Paused); @@ -487,7 +487,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); @@ -496,7 +496,7 @@ fn immediate_playback_state_change_with_start_time() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Stopped); } @@ -523,7 +523,7 @@ fn resume_at() { ..Default::default() }); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Paused); @@ -539,7 +539,7 @@ fn resume_at() { }, ); sound.on_start_processing(); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::WaitingToResume); @@ -548,10 +548,10 @@ fn resume_at() { builder.add_clock(true, 1, 0.0); builder.build() }; - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Resuming); - sound.process(1.0, &info); + sound.process_one(1.0, &info); sound.on_start_processing(); assert_eq!(handle.state(), PlaybackState::Playing); } @@ -573,7 +573,7 @@ fn start_position() { for i in 3..=6 { assert_eq!(handle.position(), i as f64); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); sound.on_start_processing(); @@ -597,25 +597,25 @@ fn loops_forward() { for i in 0..6 { assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(i as f32).panned(Panning::CENTER) ); } assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(3.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(5.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(4.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(2.0, &MockInfoBuilder::new().build()), + sound.process_one(2.0, &MockInfoBuilder::new().build()), Frame::from_mono(3.0).panned(Panning::CENTER) ); } @@ -633,7 +633,7 @@ fn volume() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(Decibels(-6.0).as_amplitude()).panned(Panning::CENTER) ); } @@ -651,7 +651,7 @@ fn set_volume() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); handle.set_volume( @@ -681,7 +681,7 @@ fn panning() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::LEFT) ); } @@ -699,7 +699,7 @@ fn set_panning() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); handle.set_panning( @@ -728,11 +728,11 @@ fn playback_rate() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(2.0).panned(Panning::CENTER) ); } @@ -753,11 +753,11 @@ fn set_playback_rate() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(1.0).panned(Panning::CENTER) ); @@ -771,11 +771,11 @@ fn set_playback_rate() { sound.on_start_processing(); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(2.0).panned(Panning::CENTER) ); assert_eq!( - sound.process(1.0, &MockInfoBuilder::new().build()), + sound.process_one(1.0, &MockInfoBuilder::new().build()), Frame::from_mono(4.0).panned(Panning::CENTER) ); } @@ -801,15 +801,15 @@ fn interpolates_samples() { while matches!(scheduler.run().unwrap(), NextStep::Continue) {} assert_eq!( - sound.process(0.5, &MockInfoBuilder::new().build()), + sound.process_one(0.5, &MockInfoBuilder::new().build()), Frame::from_mono(0.0).panned(Panning::CENTER) ); // at sample 0.5, the output should be somewhere between 0 and 1. // i don't care what exactly, that's up the to the interpolation algorithm. - let frame = sound.process(5.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(5.0, &MockInfoBuilder::new().build()); assert!(frame.left > 0.0 && frame.left < 1.0); // at sample 5.5, the output should be between 1 and -10. - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); assert!(frame.left < 0.0 && frame.left > -10.0); } @@ -828,10 +828,10 @@ fn interpolates_samples_when_looping() { let (mut sound, _, mut scheduler) = data.split().unwrap(); while matches!(scheduler.run().unwrap(), NextStep::Continue) {} - sound.process(1.5, &MockInfoBuilder::new().build()); + sound.process_one(1.5, &MockInfoBuilder::new().build()); // because we're looping back to the first sample, which is 10.0, // the interpolated sample should be be tween 9.0 and 10.0 - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); assert!(frame.left > 9.0 && frame.left < 10.0); } @@ -873,7 +873,7 @@ fn seek_by() { fn expect_frame_soon(expected_frame: Frame, sound: &mut StreamingSound) { const NUM_SAMPLES_TO_WAIT: usize = 10; for _ in 0..NUM_SAMPLES_TO_WAIT { - let frame = sound.process(1.0, &MockInfoBuilder::new().build()); + let frame = sound.process_one(1.0, &MockInfoBuilder::new().build()); if frame == expected_frame { return; } diff --git a/crates/kira/src/track/main.rs b/crates/kira/src/track/main.rs index 1ee6513c..6ba66557 100644 --- a/crates/kira/src/track/main.rs +++ b/crates/kira/src/track/main.rs @@ -19,6 +19,7 @@ pub(crate) struct MainTrack { set_volume_command_reader: CommandReader>, sounds: ResourceStorage>, effects: Vec>, + temp_buffer: Vec, } impl MainTrack { @@ -46,16 +47,20 @@ impl MainTrack { } } - pub fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame { - self.volume.update(dt, info); - let mut output = input; + pub fn process(&mut self, out: &mut [Frame], dt: f64, info: &Info) { + self.volume.update(dt * out.len() as f64, info); for (_, sound) in &mut self.sounds { - output += sound.process(dt, info); + sound.process(&mut self.temp_buffer[..out.len()], dt, info); + for (summed_out, sound_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { + *summed_out += sound_out; + } + self.temp_buffer.fill(Frame::ZERO); } for effect in &mut self.effects { - output = effect.process(output, dt, info); + effect.process(out, dt, info); + } + for frame in out { + *frame *= self.volume.value().as_amplitude(); } - output *= self.volume.value().as_amplitude(); - output } } diff --git a/crates/kira/src/track/main/builder.rs b/crates/kira/src/track/main/builder.rs index 7fb93e3c..811c7e77 100644 --- a/crates/kira/src/track/main/builder.rs +++ b/crates/kira/src/track/main/builder.rs @@ -3,7 +3,7 @@ use crate::{ effect::EffectBuilder, manager::backend::resources::ResourceStorage, tween::{Parameter, Value}, - Decibels, + Decibels, Frame, INTERNAL_BUFFER_SIZE, }; use super::{Effect, MainTrack, MainTrackHandle}; @@ -153,6 +153,7 @@ impl MainTrackBuilder { set_volume_command_reader, sounds, effects: self.effects, + temp_buffer: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], }; let handle = MainTrackHandle { set_volume_command_writer, diff --git a/crates/kira/src/track/send.rs b/crates/kira/src/track/send.rs index 97b9d395..0c957f6a 100644 --- a/crates/kira/src/track/send.rs +++ b/crates/kira/src/track/send.rs @@ -32,7 +32,7 @@ pub(crate) struct SendTrack { volume: Parameter, set_volume_command_reader: CommandReader>, effects: Vec>, - input: Frame, + input: Vec, } impl SendTrack { @@ -53,8 +53,10 @@ impl SendTrack { self.shared.clone() } - pub fn add_input(&mut self, input: Frame) { - self.input += input; + pub fn add_input(&mut self, input: &[Frame], volume: Decibels) { + for (input, added) in self.input.iter_mut().zip(input.iter().copied()) { + *input += added * volume.as_amplitude(); + } } pub fn on_start_processing(&mut self) { @@ -65,13 +67,18 @@ impl SendTrack { } } - pub fn process(&mut self, dt: f64, info: &Info) -> Frame { - self.volume.update(dt, info); - let mut output = std::mem::replace(&mut self.input, Frame::ZERO); + pub fn process(&mut self, out: &mut [Frame], dt: f64, info: &Info) { + self.volume.update(dt * out.len() as f64, info); + for (out_frame, input_frame) in out.iter_mut().zip(self.input.iter().copied()) { + *out_frame += input_frame; + } + self.input.fill(Frame::ZERO); for effect in &mut self.effects { - output = effect.process(output, dt, info); + effect.process(out, dt, info); + } + for frame in out { + *frame *= self.volume.value().as_amplitude(); } - output * self.volume.value().as_amplitude() } } diff --git a/crates/kira/src/track/send/builder.rs b/crates/kira/src/track/send/builder.rs index f5820f1d..6e02ee20 100644 --- a/crates/kira/src/track/send/builder.rs +++ b/crates/kira/src/track/send/builder.rs @@ -5,7 +5,7 @@ use crate::{ effect::EffectBuilder, frame::Frame, tween::{Parameter, Value}, - Decibels, + Decibels, INTERNAL_BUFFER_SIZE, }; use super::{Effect, SendTrack, SendTrackHandle, SendTrackId, TrackShared}; @@ -180,7 +180,7 @@ impl SendTrackBuilder { volume: Parameter::new(self.volume, Decibels::IDENTITY), set_volume_command_reader, effects: self.effects, - input: Frame::ZERO, + input: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], }; let handle = SendTrackHandle { id, diff --git a/crates/kira/src/track/sub.rs b/crates/kira/src/track/sub.rs index d2e3780e..b5d03608 100644 --- a/crates/kira/src/track/sub.rs +++ b/crates/kira/src/track/sub.rs @@ -53,6 +53,7 @@ pub(crate) struct Track { persist_until_sounds_finish: bool, spatial_data: Option, playback_state_manager: PlaybackStateManager, + temp_buffer: Vec, } impl Track { @@ -128,13 +129,14 @@ impl Track { pub fn process( &mut self, + out: &mut [Frame], dt: f64, clocks: &Clocks, modulators: &Modulators, listeners: &Listeners, parent_spatial_track_info: Option, send_tracks: &mut ResourceStorage, - ) -> Frame { + ) { let spatial_track_info = self .spatial_data .as_ref() @@ -149,22 +151,25 @@ impl Track { &listeners.0.resources, spatial_track_info, ); - self.volume.update(dt, &info); + self.volume.update(dt * out.len() as f64, &info); for (_, route) in &mut self.sends { - route.volume.update(dt, &info); + route.volume.update(dt * out.len() as f64, &info); } - let changed_playback_state = self.playback_state_manager.update(dt, &info); + let changed_playback_state = self + .playback_state_manager + .update(dt * out.len() as f64, &info); if changed_playback_state { self.update_shared_playback_state(); } if !self.playback_state_manager.playback_state().is_advancing() { - return Frame::ZERO; + out.fill(Frame::ZERO); + return; } - let mut output = Frame::ZERO; for (_, sub_track) in &mut self.sub_tracks { - output += sub_track.process( + sub_track.process( + &mut self.temp_buffer[..out.len()], dt, clocks, modulators, @@ -172,32 +177,43 @@ impl Track { spatial_track_info, send_tracks, ); + for (summed_out, track_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { + *summed_out += track_out; + } + self.temp_buffer.fill(Frame::ZERO); } for (_, sound) in &mut self.sounds { - output += sound.process(dt, &info); + sound.process(&mut self.temp_buffer[..out.len()], dt, &info); + for (summed_out, sound_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { + *summed_out += sound_out; + } + self.temp_buffer.fill(Frame::ZERO); } for effect in &mut self.effects { - output = effect.process(output, dt, &info); + effect.process(out, dt, &info); } if let Some(spatial_data) = &mut self.spatial_data { - spatial_data.position.update(dt, &info); + spatial_data.position.update(dt * out.len() as f64, &info); if let Some(ListenerInfo { position, orientation, }) = info.listener_info() { - output = spatial_data.spatialize(output, position.into(), orientation.into()); + for frame in out.iter_mut() { + *frame = spatial_data.spatialize(*frame, position.into(), orientation.into()); + } } } - output *= self.volume.value().as_amplitude() - * self.playback_state_manager.fade_volume().as_amplitude(); + for frame in out.iter_mut() { + *frame *= self.volume.value().as_amplitude() + * self.playback_state_manager.fade_volume().as_amplitude(); + } for (send_track_id, SendTrackRoute { volume, .. }) in &self.sends { let Some(send_track) = send_tracks.get_mut(send_track_id.0) else { continue; }; - send_track.add_input(output * volume.value().as_amplitude()); + send_track.add_input(out, volume.value()); } - output } fn read_commands(&mut self) { diff --git a/crates/kira/src/track/sub/builder.rs b/crates/kira/src/track/sub/builder.rs index 19114f50..9761d545 100644 --- a/crates/kira/src/track/sub/builder.rs +++ b/crates/kira/src/track/sub/builder.rs @@ -6,7 +6,7 @@ use crate::{ manager::backend::{resources::ResourceStorage, RendererShared}, playback_state_manager::PlaybackStateManager, tween::{Parameter, Value}, - Decibels, + Decibels, Frame, INTERNAL_BUFFER_SIZE, }; use super::{ @@ -256,6 +256,7 @@ impl TrackBuilder { persist_until_sounds_finish: self.persist_until_sounds_finish, spatial_data: None, playback_state_manager: PlaybackStateManager::new(None), + temp_buffer: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], }; let handle = TrackHandle { renderer_shared, diff --git a/crates/kira/src/track/sub/spatial_builder.rs b/crates/kira/src/track/sub/spatial_builder.rs index bb72eae7..fe8b2713 100644 --- a/crates/kira/src/track/sub/spatial_builder.rs +++ b/crates/kira/src/track/sub/spatial_builder.rs @@ -9,7 +9,7 @@ use crate::{ manager::backend::{resources::ResourceStorage, RendererShared}, playback_state_manager::PlaybackStateManager, tween::{Easing, Parameter, Value}, - Decibels, + Decibels, Frame, INTERNAL_BUFFER_SIZE, }; use super::{ @@ -312,6 +312,7 @@ impl SpatialTrackBuilder { enable_spatialization: self.enable_spatialization, }), playback_state_manager: PlaybackStateManager::new(None), + temp_buffer: vec![Frame::ZERO; INTERNAL_BUFFER_SIZE], }; let handle = SpatialTrackHandle { renderer_shared, diff --git a/crates/kira/tests/change_sample_rate.rs b/crates/kira/tests/change_sample_rate.rs index 73d4658c..d7b466f1 100644 --- a/crates/kira/tests/change_sample_rate.rs +++ b/crates/kira/tests/change_sample_rate.rs @@ -29,9 +29,8 @@ impl Effect for TestEffect { self.sample_rate.store(sample_rate, Ordering::SeqCst); } - fn process(&mut self, _input: Frame, dt: f64, _info: &Info) -> Frame { + fn process(&mut self, _input: &mut [Frame], dt: f64, _info: &Info) { self.dt_producer.push(dt).unwrap(); - Frame::ZERO } } @@ -79,10 +78,10 @@ fn change_sample_rate() { let backend = manager.backend_mut(); backend.on_start_processing(); assert_eq!(effect_handle.sample_rate.load(Ordering::SeqCst), 100); - let _ = backend.process(); + backend.process(); assert_eq!(effect_handle.dt_consumer.pop(), Ok(1.0 / 100.0)); backend.set_sample_rate(200); assert_eq!(effect_handle.sample_rate.load(Ordering::SeqCst), 200); - let _ = backend.process(); + backend.process(); assert_eq!(effect_handle.dt_consumer.pop(), Ok(1.0 / 200.0)); } diff --git a/crates/kira/tests/streaming_sound_stops_on_error.rs b/crates/kira/tests/streaming_sound_stops_on_error.rs index 6eb391d0..4807e8c0 100644 --- a/crates/kira/tests/streaming_sound_stops_on_error.rs +++ b/crates/kira/tests/streaming_sound_stops_on_error.rs @@ -41,7 +41,7 @@ fn streaming_sound_stops_on_error() { let mut sound = manager.play(data).unwrap(); manager.backend_mut().on_start_processing(); std::thread::sleep(Duration::from_secs(1)); - let _ = manager.backend_mut().process(); + manager.backend_mut().process(); manager.backend_mut().on_start_processing(); assert_eq!(sound.state(), PlaybackState::Stopped); assert_eq!(sound.pop_error(), Some(MockDecoderError)); diff --git a/crates/kira/tests/tick_accuracy.rs b/crates/kira/tests/tick_accuracy.rs index 8cc9c5ab..6e4e500e 100644 --- a/crates/kira/tests/tick_accuracy.rs +++ b/crates/kira/tests/tick_accuracy.rs @@ -30,7 +30,7 @@ struct TestEffect { } impl Effect for TestEffect { - fn process(&mut self, input: Frame, _dt: f64, info: &Info) -> Frame { + fn process(&mut self, _input: &mut [Frame], _dt: f64, info: &Info) { self.frames += 1; if self.frames == 24000 { println!("asdf"); @@ -46,7 +46,6 @@ impl Effect for TestEffect { ); self.ticks += EVENT_TIME_TICKS; } - input } } @@ -100,6 +99,6 @@ fn tick_accuracy() { if i % 512 == 0 { backend.on_start_processing(); } - let _ = backend.process(); + backend.process(); } }