From 790192580f0fad8db386e5d0f0e04f3c241cca8c Mon Sep 17 00:00:00 2001 From: Otto Date: Thu, 16 May 2024 08:53:55 +0200 Subject: [PATCH 01/11] Experiment with thread local channel data pool --- Cargo.toml | 2 +- src/render/quantum.rs | 33 ++++++++++++++------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49ee6e1b..57193819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ include = [ "LICENSE", "README.md", ] -rust-version = "1.71" +rust-version = "1.73" [dependencies] arc-swap = "1.6" diff --git a/src/render/quantum.rs b/src/render/quantum.rs index fdc0e6eb..84dab0a2 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -14,23 +14,20 @@ pub(crate) struct Alloc { } #[derive(Debug)] -struct AllocInner { - pool: RefCell>>, - zeroes: Rc<[f32; RENDER_QUANTUM_SIZE]>, +struct AllocInner {} + +thread_local! { + static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); + static ZEROES: Rc<[f32; RENDER_QUANTUM_SIZE]> = Rc::new([0.; RENDER_QUANTUM_SIZE]); } impl Alloc { pub fn with_capacity(n: usize) -> Self { let pool: Vec<_> = (0..n).map(|_| Rc::new([0.; RENDER_QUANTUM_SIZE])).collect(); - let zeroes = Rc::new([0.; RENDER_QUANTUM_SIZE]); - - let inner = AllocInner { - pool: RefCell::new(pool), - zeroes, - }; + POOL.set(pool); Self { - inner: Rc::new(inner), + inner: Rc::new(AllocInner {}), } } @@ -44,20 +41,20 @@ impl Alloc { pub fn silence(&self) -> AudioRenderQuantumChannel { AudioRenderQuantumChannel { - data: Rc::clone(&self.inner.zeroes), + data: ZEROES.with(Rc::clone), alloc: Rc::clone(&self.inner), } } #[cfg(test)] pub fn pool_size(&self) -> usize { - self.inner.pool.borrow().len() + POOL.with_borrow(|p| p.len()) } } impl AllocInner { fn allocate(&self) -> Rc<[f32; RENDER_QUANTUM_SIZE]> { - if let Some(rc) = self.pool.borrow_mut().pop() { + if let Some(rc) = POOL.with_borrow_mut(|p| p.pop()) { // reuse from pool rc } else { @@ -67,9 +64,7 @@ impl AllocInner { } fn push(&self, data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { - self.pool - .borrow_mut() // infallible when single threaded - .push(data); + POOL.with_borrow_mut(|p| p.push(data)); } } @@ -107,7 +102,7 @@ impl AudioRenderQuantumChannel { /// /// If this function returns false, it is still possible for all samples to be zero. pub(crate) fn is_silent(&self) -> bool { - Rc::ptr_eq(&self.data, &self.alloc.zeroes) + ZEROES.with(|z| Rc::ptr_eq(&self.data, z)) } /// Sum two channels @@ -121,7 +116,7 @@ impl AudioRenderQuantumChannel { pub(crate) fn silence(&self) -> Self { Self { - data: Rc::clone(&self.alloc.zeroes), + data: ZEROES.with(Rc::clone), alloc: Rc::clone(&self.alloc), } } @@ -152,7 +147,7 @@ impl AsRef<[f32]> for AudioRenderQuantumChannel { impl std::ops::Drop for AudioRenderQuantumChannel { fn drop(&mut self) { if Rc::strong_count(&self.data) == 1 { - let zeroes = Rc::clone(&self.alloc.zeroes); + let zeroes = ZEROES.with(Rc::clone); let rc = std::mem::replace(&mut self.data, zeroes); self.alloc.push(rc); } From b3b33eab53ca13c9f42ca39e045e4f57861598c8 Mon Sep 17 00:00:00 2001 From: Otto Date: Thu, 16 May 2024 09:00:14 +0200 Subject: [PATCH 02/11] Actually remove the Alloc reference in channel data --- src/render/quantum.rs | 48 ++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index 84dab0a2..1e40a8a2 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -9,12 +9,7 @@ use crate::assert_valid_number_of_channels; use crate::{MAX_CHANNELS, RENDER_QUANTUM_SIZE}; // object pool for `AudioRenderQuantumChannel`s, only allocate if the pool is empty -pub(crate) struct Alloc { - inner: Rc, -} - -#[derive(Debug)] -struct AllocInner {} +pub(crate) struct Alloc; thread_local! { static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); @@ -23,26 +18,17 @@ thread_local! { impl Alloc { pub fn with_capacity(n: usize) -> Self { - let pool: Vec<_> = (0..n).map(|_| Rc::new([0.; RENDER_QUANTUM_SIZE])).collect(); - POOL.set(pool); - - Self { - inner: Rc::new(AllocInner {}), - } + Self {} } #[cfg(test)] pub fn allocate(&self) -> AudioRenderQuantumChannel { - AudioRenderQuantumChannel { - data: self.inner.allocate(), - alloc: Rc::clone(&self.inner), - } + AudioRenderQuantumChannel { data: allocate() } } pub fn silence(&self) -> AudioRenderQuantumChannel { AudioRenderQuantumChannel { data: ZEROES.with(Rc::clone), - alloc: Rc::clone(&self.inner), } } @@ -52,20 +38,18 @@ impl Alloc { } } -impl AllocInner { - fn allocate(&self) -> Rc<[f32; RENDER_QUANTUM_SIZE]> { - if let Some(rc) = POOL.with_borrow_mut(|p| p.pop()) { - // reuse from pool - rc - } else { - // allocate - Rc::new([0.; RENDER_QUANTUM_SIZE]) - } +fn allocate() -> Rc<[f32; RENDER_QUANTUM_SIZE]> { + if let Some(rc) = POOL.with_borrow_mut(|p| p.pop()) { + // reuse from pool + rc + } else { + // allocate + Rc::new([0.; RENDER_QUANTUM_SIZE]) } +} - fn push(&self, data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { - POOL.with_borrow_mut(|p| p.push(data)); - } +fn push(data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { + POOL.with_borrow_mut(|p| p.push(data)); } /// Render thread channel buffer @@ -84,13 +68,12 @@ impl AllocInner { #[derive(Clone, Debug)] pub struct AudioRenderQuantumChannel { data: Rc<[f32; RENDER_QUANTUM_SIZE]>, - alloc: Rc, } impl AudioRenderQuantumChannel { fn make_mut(&mut self) -> &mut [f32; RENDER_QUANTUM_SIZE] { if Rc::strong_count(&self.data) != 1 { - let mut new = self.alloc.allocate(); + let mut new = allocate(); Rc::make_mut(&mut new).copy_from_slice(self.data.deref()); self.data = new; } @@ -117,7 +100,6 @@ impl AudioRenderQuantumChannel { pub(crate) fn silence(&self) -> Self { Self { data: ZEROES.with(Rc::clone), - alloc: Rc::clone(&self.alloc), } } } @@ -149,7 +131,7 @@ impl std::ops::Drop for AudioRenderQuantumChannel { if Rc::strong_count(&self.data) == 1 { let zeroes = ZEROES.with(Rc::clone); let rc = std::mem::replace(&mut self.data, zeroes); - self.alloc.push(rc); + push(rc); } } } From d9159441e9e8f16630e3e4c1540f327a4799cfee Mon Sep 17 00:00:00 2001 From: Otto Date: Thu, 16 May 2024 19:34:49 +0200 Subject: [PATCH 03/11] Change the way we model the silent channel - Option> is free! --- src/render/quantum.rs | 82 ++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index 1e40a8a2..d9147231 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -13,23 +13,15 @@ pub(crate) struct Alloc; thread_local! { static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); - static ZEROES: Rc<[f32; RENDER_QUANTUM_SIZE]> = Rc::new([0.; RENDER_QUANTUM_SIZE]); } impl Alloc { - pub fn with_capacity(n: usize) -> Self { + pub fn with_capacity(_n: usize) -> Self { Self {} } - #[cfg(test)] - pub fn allocate(&self) -> AudioRenderQuantumChannel { - AudioRenderQuantumChannel { data: allocate() } - } - pub fn silence(&self) -> AudioRenderQuantumChannel { - AudioRenderQuantumChannel { - data: ZEROES.with(Rc::clone), - } + AudioRenderQuantumChannel { data: None } } #[cfg(test)] @@ -67,25 +59,35 @@ fn push(data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { /// mutate it from there. #[derive(Clone, Debug)] pub struct AudioRenderQuantumChannel { - data: Rc<[f32; RENDER_QUANTUM_SIZE]>, + data: Option>, } impl AudioRenderQuantumChannel { fn make_mut(&mut self) -> &mut [f32; RENDER_QUANTUM_SIZE] { - if Rc::strong_count(&self.data) != 1 { - let mut new = allocate(); - Rc::make_mut(&mut new).copy_from_slice(self.data.deref()); - self.data = new; + if self.data.is_none() { + self.data = Some(allocate()); + Rc::make_mut(self.data.as_mut().unwrap()).fill(0.); } - Rc::make_mut(&mut self.data) + match &mut self.data { + Some(data) => { + if Rc::strong_count(data) != 1 { + let mut new = allocate(); + Rc::make_mut(&mut new).copy_from_slice(&data[..]); + *data = new; + } + + return Rc::make_mut(data); + } + None => unreachable!(), + } } /// `O(1)` check if this buffer is equal to the 'silence buffer' /// /// If this function returns false, it is still possible for all samples to be zero. pub(crate) fn is_silent(&self) -> bool { - ZEROES.with(|z| Rc::ptr_eq(&self.data, z)) + self.data.is_none() } /// Sum two channels @@ -98,9 +100,7 @@ impl AudioRenderQuantumChannel { } pub(crate) fn silence(&self) -> Self { - Self { - data: ZEROES.with(Rc::clone), - } + Self { data: None } } } @@ -110,7 +110,10 @@ impl Deref for AudioRenderQuantumChannel { type Target = [f32]; fn deref(&self) -> &Self::Target { - self.data.as_slice() + match &self.data { + Some(data) => data.as_slice(), + None => &[0.; RENDER_QUANTUM_SIZE], + } } } @@ -122,16 +125,22 @@ impl DerefMut for AudioRenderQuantumChannel { impl AsRef<[f32]> for AudioRenderQuantumChannel { fn as_ref(&self) -> &[f32] { - &self.data[..] + match &self.data { + Some(data) => data.as_slice(), + None => &[0.; RENDER_QUANTUM_SIZE], + } } } impl std::ops::Drop for AudioRenderQuantumChannel { fn drop(&mut self) { - if Rc::strong_count(&self.data) == 1 { - let zeroes = ZEROES.with(Rc::clone); - let rc = std::mem::replace(&mut self.data, zeroes); - push(rc); + let data = match self.data.take() { + None => return, + Some(data) => data, + }; + + if Rc::strong_count(&data) == 1 { + push(data); } } } @@ -626,8 +635,15 @@ impl AudioRenderQuantum { let mut channels = self.channels.iter(); let first = channels.next().unwrap(); for c in channels { - if !Rc::ptr_eq(&first.data, &c.data) { - return false; + match (&first.data, &c.data) { + (None, None) => (), + (None, _) => return false, + (_, None) => return false, + (Some(d1), Some(d2)) => { + if !Rc::ptr_eq(d1, d2) { + return false; + } + } } } @@ -650,7 +666,7 @@ mod tests { alloc_counter::deny_alloc(|| { { // take a buffer out of the pool - let a = alloc.allocate(); + let a = alloc.silence(); assert_float_eq!(&a[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); assert_eq!(alloc.pool_size(), 1); @@ -675,12 +691,12 @@ mod tests { assert_eq!(alloc.pool_size(), 2); let c = { - let a = alloc.allocate(); - let b = alloc.allocate(); + let a = alloc.silence(); + let b = alloc.silence(); let c = alloc_counter::allow_alloc(|| { // we can allocate beyond the pool size - let c = alloc.allocate(); + let c = alloc.silence(); assert_eq!(alloc.pool_size(), 0); c }); @@ -757,7 +773,7 @@ mod tests { let mut signal1 = alloc.silence(); signal1.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut signal2 = alloc.allocate(); + let mut signal2 = alloc.silence(); signal2.copy_from_slice(&[2.; RENDER_QUANTUM_SIZE]); // test add silence to signal From 60bff7bfeb2e12575a7221aa33f4eb44729dee90 Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 17 May 2024 20:01:13 +0200 Subject: [PATCH 04/11] Cleanup the Alloc struct, fix the tests --- src/param.rs | 25 ++-- src/render/graph.rs | 13 ++- src/render/quantum.rs | 261 +++++++++++++++--------------------------- 3 files changed, 108 insertions(+), 191 deletions(-) diff --git a/src/param.rs b/src/param.rs index 2590020e..98513e6c 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1658,10 +1658,9 @@ pub(crate) fn audio_param_pair( mod tests { use float_eq::assert_float_eq; - use crate::context::{BaseAudioContext, OfflineAudioContext}; - use crate::render::Alloc; - use super::*; + use crate::context::{BaseAudioContext, OfflineAudioContext}; + use crate::render::AudioRenderQuantumChannel; #[test] #[should_panic] @@ -3397,8 +3396,6 @@ mod tests { #[test] fn test_varying_param_size_modulated() { - let alloc = Alloc::with_capacity(1); - // buffer length is 1 and input is silence (no modulation) { let context = OfflineAudioContext::new(1, 1, 48000.); @@ -3417,10 +3414,10 @@ mod tests { assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.); // mix to output step, input is silence - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let input = AudioRenderQuantum::from(signal); - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut output = AudioRenderQuantum::from(signal); render.mix_to_output(&input, &mut output); @@ -3447,11 +3444,11 @@ mod tests { assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.); // mix to output step, input is not silence - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut input = AudioRenderQuantum::from(signal); input.channel_data_mut(0)[0] = 1.; - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut output = AudioRenderQuantum::from(signal); render.mix_to_output(&input, &mut output); @@ -3466,7 +3463,6 @@ mod tests { #[test] fn test_k_rate_makes_input_single_valued() { - let alloc = Alloc::with_capacity(1); let context = OfflineAudioContext::new(1, 1, 48000.); let opts = AudioParamDescriptor { @@ -3483,13 +3479,13 @@ mod tests { assert_float_eq!(vs, &[0.; 1][..], abs_all <= 0.); // mix to output step, input is not silence - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut input = AudioRenderQuantum::from(signal); input.channel_data_mut(0)[0] = 1.; input.channel_data_mut(0)[1] = 2.; input.channel_data_mut(0)[2] = 3.; - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut output = AudioRenderQuantum::from(signal); render.mix_to_output(&input, &mut output); @@ -3501,7 +3497,6 @@ mod tests { #[test] fn test_full_render_chain() { - let alloc = Alloc::with_capacity(1); // prevent regression between the different processing stage let context = OfflineAudioContext::new(1, 1, 48000.); @@ -3528,10 +3523,10 @@ mod tests { } assert_float_eq!(intrinsic_values, &expected[..], abs_all <= 0.); - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut input = AudioRenderQuantum::from(signal); input.channel_data_mut(0)[0] = f32::NAN; - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut output = AudioRenderQuantum::from(signal); render.mix_to_output(&input, &mut output); diff --git a/src/render/graph.rs b/src/render/graph.rs index 8e227d9e..121f40a4 100644 --- a/src/render/graph.rs +++ b/src/render/graph.rs @@ -10,7 +10,9 @@ use std::panic::{self, AssertUnwindSafe}; use crate::context::AudioNodeId; use smallvec::{smallvec, SmallVec}; -use super::{Alloc, AudioParamValues, AudioProcessor, AudioRenderQuantum, NodeCollection}; +use super::{ + AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioRenderQuantumChannel, NodeCollection, +}; use crate::node::{ChannelConfigInner, ChannelCountMode, ChannelInterpretation}; use crate::render::AudioWorkletGlobalScope; @@ -122,8 +124,6 @@ impl Node { pub(crate) struct Graph { /// Processing Nodes nodes: NodeCollection, - /// Allocator for audio buffers - alloc: Alloc, /// Message channel to notify control thread of reclaimable AudioNodeIds reclaim_id_channel: llq::Producer, /// Topological ordering of the nodes @@ -151,7 +151,6 @@ impl Graph { pub fn new(reclaim_id_channel: llq::Producer) -> Self { Graph { nodes: NodeCollection::new(), - alloc: Alloc::with_capacity(64), reclaim_id_channel, ordered: vec![], marked: vec![], @@ -180,8 +179,10 @@ impl Graph { // set input and output buffers to single channel of silence, will be upmixed when // necessary - let inputs = vec![AudioRenderQuantum::from(self.alloc.silence()); number_of_inputs]; - let outputs = vec![AudioRenderQuantum::from(self.alloc.silence()); number_of_outputs]; + let inputs = + vec![AudioRenderQuantum::from(AudioRenderQuantumChannel::silence()); number_of_inputs]; + let outputs = + vec![AudioRenderQuantum::from(AudioRenderQuantumChannel::silence()); number_of_outputs]; self.nodes.insert( index, diff --git a/src/render/quantum.rs b/src/render/quantum.rs index d9147231..05de8fdc 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -8,26 +8,13 @@ use crate::node::{ChannelConfigInner, ChannelCountMode, ChannelInterpretation}; use crate::assert_valid_number_of_channels; use crate::{MAX_CHANNELS, RENDER_QUANTUM_SIZE}; -// object pool for `AudioRenderQuantumChannel`s, only allocate if the pool is empty -pub(crate) struct Alloc; - thread_local! { static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); } -impl Alloc { - pub fn with_capacity(_n: usize) -> Self { - Self {} - } - - pub fn silence(&self) -> AudioRenderQuantumChannel { - AudioRenderQuantumChannel { data: None } - } - - #[cfg(test)] - pub fn pool_size(&self) -> usize { - POOL.with_borrow(|p| p.len()) - } +#[cfg(test)] +fn pool_size() -> usize { + POOL.with_borrow(|p| p.len()) } fn allocate() -> Rc<[f32; RENDER_QUANTUM_SIZE]> { @@ -99,7 +86,7 @@ impl AudioRenderQuantumChannel { } } - pub(crate) fn silence(&self) -> Self { + pub(crate) fn silence() -> Self { Self { data: None } } } @@ -275,7 +262,7 @@ impl AudioRenderQuantum { ) { // cf. https://www.w3.org/TR/webaudio/#channel-up-mixing-and-down-mixing assert_valid_number_of_channels(computed_number_of_channels); - let silence = self.channels[0].silence(); + let silence = AudioRenderQuantumChannel::silence(); // Handle discrete interpretation or speaker layouts where the initial or desired number of // channels is larger than 6 (undefined by the specification) @@ -568,9 +555,7 @@ impl AudioRenderQuantum { /// `O(1)` operation to convert this buffer to the 'silence buffer' which will enable some /// optimizations in the graph rendering. pub fn make_silent(&mut self) { - let silence = self.channels[0].silence(); - - self.channels[0] = silence; + self.channels[0] = AudioRenderQuantumChannel::silence(); self.channels.truncate(1); } @@ -659,87 +644,47 @@ mod tests { #[test] fn test_pool() { - // Create pool of size 2 - let alloc = Alloc::with_capacity(2); - assert_eq!(alloc.pool_size(), 2); - - alloc_counter::deny_alloc(|| { - { - // take a buffer out of the pool - let a = alloc.silence(); - - assert_float_eq!(&a[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - assert_eq!(alloc.pool_size(), 1); - - // mutating this buffer will not allocate - let mut a = a; - a.iter_mut().for_each(|v| *v += 1.); - assert_float_eq!(&a[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - assert_eq!(alloc.pool_size(), 1); - - // clone this buffer, should not allocate - #[allow(clippy::redundant_clone)] - let mut b: AudioRenderQuantumChannel = a.clone(); - assert_eq!(alloc.pool_size(), 1); - - // mutate cloned buffer, this will allocate - b.iter_mut().for_each(|v| *v += 1.); - assert_eq!(alloc.pool_size(), 0); - } - - // all buffers are reclaimed - assert_eq!(alloc.pool_size(), 2); - - let c = { - let a = alloc.silence(); - let b = alloc.silence(); - - let c = alloc_counter::allow_alloc(|| { - // we can allocate beyond the pool size - let c = alloc.silence(); - assert_eq!(alloc.pool_size(), 0); - c - }); - - // dirty allocations - assert_float_eq!(&a[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - assert_float_eq!(&b[..], &[2.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - assert_float_eq!(&c[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - - c - }; - - // dropping c will cause a re-allocation: the pool capacity is extended - alloc_counter::allow_alloc(move || { - std::mem::drop(c); - }); - - // pool size is now 3 due to extra allocations - assert_eq!(alloc.pool_size(), 3); - - { - // silence will not allocate at first - let mut a = alloc.silence(); - assert!(a.is_silent()); - assert_eq!(alloc.pool_size(), 3); + { + // allocate and drop + let mut a = AudioRenderQuantumChannel::silence(); + let _a = a.make_mut(); + let mut b = AudioRenderQuantumChannel::silence(); + let _b = b.make_mut(); + } - // deref mut will allocate - let a_vals = a.deref_mut(); - assert_eq!(alloc.pool_size(), 2); + let pool_size_start = dbg!(pool_size()); + assert!(pool_size_start >= 2); // due to allocation, pool size is nonzero - // but should be silent, even though a dirty buffer is taken - assert_float_eq!(a_vals, &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + { + // take a buffer out of the pool + let a = AudioRenderQuantumChannel::silence(); + + assert_float_eq!(&a[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + assert_eq!(pool_size(), pool_size_start); + + // mutating this buffer will take from the pool + let mut a = a; + a.iter_mut().for_each(|v| *v += 1.); + assert_float_eq!(&a[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + assert_eq!(pool_size(), pool_size_start - 1); + + // clone this buffer, should not allocate + #[allow(clippy::redundant_clone)] + let mut b: AudioRenderQuantumChannel = a.clone(); + assert_eq!(pool_size(), pool_size_start - 1); + + // mutate cloned buffer, this will allocate + b.iter_mut().for_each(|v| *v += 1.); + assert_eq!(pool_size(), pool_size_start - 2); + } - // is_silent is a superficial ptr check - assert!(!a.is_silent()); - } - }); + // all buffers are reclaimed + assert_eq!(pool_size(), pool_size_start); } #[test] fn test_silence() { - let alloc = Alloc::with_capacity(1); - let silence = alloc.silence(); + let silence = AudioRenderQuantumChannel::silence(); assert_float_eq!(&silence[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); assert!(silence.is_silent()); @@ -751,29 +696,19 @@ mod tests { assert!(!changed.is_silent()); // but should not alter new silence - let silence = alloc.silence(); + let silence = AudioRenderQuantumChannel::silence(); assert_float_eq!(&silence[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); assert!(silence.is_silent()); - - // can also create silence from AudioRenderQuantumChannel - let from_channel = silence.silence(); - assert_float_eq!( - &from_channel[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert!(from_channel.is_silent()); } #[test] fn test_channel_add() { - let alloc = Alloc::with_capacity(1); - let silence = alloc.silence(); + let silence = AudioRenderQuantumChannel::silence(); - let mut signal1 = alloc.silence(); + let mut signal1 = AudioRenderQuantumChannel::silence(); signal1.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut signal2 = alloc.silence(); + let mut signal2 = AudioRenderQuantumChannel::silence(); signal2.copy_from_slice(&[2.; RENDER_QUANTUM_SIZE]); // test add silence to signal @@ -781,7 +716,7 @@ mod tests { assert_float_eq!(&signal1[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); // test add signal to silence - let mut silence = alloc.silence(); + let mut silence = AudioRenderQuantumChannel::silence(); silence.add(&signal1); assert_float_eq!(&silence[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); @@ -792,8 +727,7 @@ mod tests { #[test] fn test_audiobuffer_channels() { - let alloc = Alloc::with_capacity(1); - let silence = alloc.silence(); + let silence = AudioRenderQuantumChannel::silence(); let buffer = AudioRenderQuantum::from(silence); assert_eq!(buffer.number_of_channels(), 1); @@ -809,9 +743,7 @@ mod tests { #[test] fn test_audiobuffer_mix_discrete() { - let alloc = Alloc::with_capacity(1); - - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); @@ -851,8 +783,7 @@ mod tests { #[test] fn test_audiobuffer_mix_speakers_all() { - let alloc = Alloc::with_capacity(1); - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut buffer = AudioRenderQuantum::from(signal); for i in 1..MAX_CHANNELS { @@ -867,11 +798,9 @@ mod tests { #[test] fn test_audiobuffer_upmix_speakers() { - let alloc = Alloc::with_capacity(1); - { // 1 -> 2 - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); @@ -903,7 +832,7 @@ mod tests { { // 1 -> 4 - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); @@ -936,7 +865,7 @@ mod tests { { // 1 -> 6 - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); @@ -979,9 +908,9 @@ mod tests { { // 2 -> 4 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1025,9 +954,9 @@ mod tests { { // 2 -> 5.1 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1081,13 +1010,13 @@ mod tests { { // 4 -> 5.1 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[0.25; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.75; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1154,13 +1083,11 @@ mod tests { #[test] fn test_audiobuffer_downmix_speakers() { - let alloc = Alloc::with_capacity(1); - { // 2 -> 1 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1189,13 +1116,13 @@ mod tests { { // 4 -> 1 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.75; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[0.25; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1236,17 +1163,17 @@ mod tests { { // 6 -> 1 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.9; RENDER_QUANTUM_SIZE]); - let mut center_signal = alloc.silence(); + let mut center_signal = AudioRenderQuantumChannel::silence(); center_signal.copy_from_slice(&[0.8; RENDER_QUANTUM_SIZE]); - let mut low_freq_signal = alloc.silence(); + let mut low_freq_signal = AudioRenderQuantumChannel::silence(); low_freq_signal.copy_from_slice(&[0.7; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.6; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1301,13 +1228,13 @@ mod tests { { // 4 -> 2 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[0.25; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.75; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1353,17 +1280,17 @@ mod tests { { // 6 -> 2 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.9; RENDER_QUANTUM_SIZE]); - let mut center_signal = alloc.silence(); + let mut center_signal = AudioRenderQuantumChannel::silence(); center_signal.copy_from_slice(&[0.8; RENDER_QUANTUM_SIZE]); - let mut low_freq_signal = alloc.silence(); + let mut low_freq_signal = AudioRenderQuantumChannel::silence(); low_freq_signal.copy_from_slice(&[0.7; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.6; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1424,17 +1351,17 @@ mod tests { { // 6 -> 4 - let mut left_signal = alloc.silence(); + let mut left_signal = AudioRenderQuantumChannel::silence(); left_signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); - let mut right_signal = alloc.silence(); + let mut right_signal = AudioRenderQuantumChannel::silence(); right_signal.copy_from_slice(&[0.9; RENDER_QUANTUM_SIZE]); - let mut center_signal = alloc.silence(); + let mut center_signal = AudioRenderQuantumChannel::silence(); center_signal.copy_from_slice(&[0.8; RENDER_QUANTUM_SIZE]); - let mut low_freq_signal = alloc.silence(); + let mut low_freq_signal = AudioRenderQuantumChannel::silence(); low_freq_signal.copy_from_slice(&[0.7; RENDER_QUANTUM_SIZE]); - let mut s_left_signal = alloc.silence(); + let mut s_left_signal = AudioRenderQuantumChannel::silence(); s_left_signal.copy_from_slice(&[0.6; RENDER_QUANTUM_SIZE]); - let mut s_right_signal = alloc.silence(); + let mut s_right_signal = AudioRenderQuantumChannel::silence(); s_right_signal.copy_from_slice(&[0.5; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(left_signal); @@ -1506,14 +1433,12 @@ mod tests { #[test] fn test_audiobuffer_add() { - let alloc = Alloc::with_capacity(1); - - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); buffer.mix(2, ChannelInterpretation::Speakers); - let mut signal2 = alloc.silence(); + let mut signal2 = AudioRenderQuantumChannel::silence(); signal2.copy_from_slice(&[2.; RENDER_QUANTUM_SIZE]); let buffer2 = AudioRenderQuantum::from(signal2); @@ -1540,10 +1465,8 @@ mod tests { #[test] fn test_is_silent_quantum() { - let alloc = Alloc::with_capacity(1); - // create 2 channel silent buffer - let signal = alloc.silence(); + let signal = AudioRenderQuantumChannel::silence(); let mut buffer = AudioRenderQuantum::from(signal); buffer.mix(2, ChannelInterpretation::Speakers); @@ -1563,10 +1486,8 @@ mod tests { #[test] fn test_is_not_silent_quantum() { - let alloc = Alloc::with_capacity(1); - // create 2 channel silent buffer - let mut signal = alloc.silence(); + let mut signal = AudioRenderQuantumChannel::silence(); signal.copy_from_slice(&[1.; RENDER_QUANTUM_SIZE]); let mut buffer = AudioRenderQuantum::from(signal); buffer.mix(2, ChannelInterpretation::Discrete); From 8a63d65e1d4cfe16e877e5f2e7f5870ffaf572f8 Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 17 May 2024 20:39:10 +0200 Subject: [PATCH 05/11] Introduce ZEROES constant [0.; 128] for readability --- src/render/quantum.rs | 130 +++++++++--------------------------------- 1 file changed, 28 insertions(+), 102 deletions(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index 05de8fdc..387915d1 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -12,6 +12,8 @@ thread_local! { static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); } +const ZEROES: [f32; RENDER_QUANTUM_SIZE] = [0.; RENDER_QUANTUM_SIZE]; + #[cfg(test)] fn pool_size() -> usize { POOL.with_borrow(|p| p.len()) @@ -23,7 +25,7 @@ fn allocate() -> Rc<[f32; RENDER_QUANTUM_SIZE]> { rc } else { // allocate - Rc::new([0.; RENDER_QUANTUM_SIZE]) + Rc::new(ZEROES) } } @@ -53,7 +55,7 @@ impl AudioRenderQuantumChannel { fn make_mut(&mut self) -> &mut [f32; RENDER_QUANTUM_SIZE] { if self.data.is_none() { self.data = Some(allocate()); - Rc::make_mut(self.data.as_mut().unwrap()).fill(0.); + Rc::make_mut(self.data.as_mut().unwrap()).copy_from_slice(&ZEROES); } match &mut self.data { @@ -99,7 +101,7 @@ impl Deref for AudioRenderQuantumChannel { fn deref(&self) -> &Self::Target { match &self.data { Some(data) => data.as_slice(), - None => &[0.; RENDER_QUANTUM_SIZE], + None => &ZEROES, } } } @@ -114,7 +116,7 @@ impl AsRef<[f32]> for AudioRenderQuantumChannel { fn as_ref(&self) -> &[f32] { match &self.data { Some(data) => data.as_slice(), - None => &[0.; RENDER_QUANTUM_SIZE], + None => &ZEROES, } } } @@ -659,7 +661,7 @@ mod tests { // take a buffer out of the pool let a = AudioRenderQuantumChannel::silence(); - assert_float_eq!(&a[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + assert_float_eq!(&a[..], &ZEROES[..], abs_all <= 0.); assert_eq!(pool_size(), pool_size_start); // mutating this buffer will take from the pool @@ -686,7 +688,7 @@ mod tests { fn test_silence() { let silence = AudioRenderQuantumChannel::silence(); - assert_float_eq!(&silence[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + assert_float_eq!(&silence[..], &ZEROES[..], abs_all <= 0.); assert!(silence.is_silent()); // changing silence is possible @@ -697,7 +699,7 @@ mod tests { // but should not alter new silence let silence = AudioRenderQuantumChannel::silence(); - assert_float_eq!(&silence[..], &[0.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); + assert_float_eq!(&silence[..], &ZEROES[..], abs_all <= 0.); assert!(silence.is_silent()); } @@ -766,11 +768,7 @@ mod tests { &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(1)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(1)[..], &ZEROES[..], abs_all <= 0.); buffer.mix(1, ChannelInterpretation::Discrete); assert_eq!(buffer.number_of_channels(), 1); @@ -851,16 +849,8 @@ mod tests { &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(2)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(3)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(2)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(3)[..], &ZEROES[..], abs_all <= 0.); } { @@ -874,36 +864,16 @@ mod tests { assert_eq!(buffer.number_of_channels(), 6); // left and right equal - assert_float_eq!( - &buffer.channel_data(0)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(1)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(0)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(1)[..], &ZEROES[..], abs_all <= 0.); assert_float_eq!( &buffer.channel_data(2)[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(3)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(4)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(5)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(3)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(4)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(5)[..], &ZEROES[..], abs_all <= 0.); } { @@ -940,16 +910,8 @@ mod tests { &[0.5; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(2)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(3)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(2)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(3)[..], &ZEROES[..], abs_all <= 0.); } { @@ -986,26 +948,10 @@ mod tests { &[0.5; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(2)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(3)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(4)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(5)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(2)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(3)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(4)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(5)[..], &ZEROES[..], abs_all <= 0.); } { @@ -1058,16 +1004,8 @@ mod tests { &[0.5; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(2)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(3)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(2)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(3)[..], &ZEROES[..], abs_all <= 0.); assert_float_eq!( &buffer.channel_data(4)[..], &[0.75; RENDER_QUANTUM_SIZE][..], @@ -1470,16 +1408,8 @@ mod tests { let mut buffer = AudioRenderQuantum::from(signal); buffer.mix(2, ChannelInterpretation::Speakers); - assert_float_eq!( - &buffer.channel_data(0)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); - assert_float_eq!( - &buffer.channel_data(1)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(0)[..], &ZEROES[..], abs_all <= 0.); + assert_float_eq!(&buffer.channel_data(1)[..], &ZEROES[..], abs_all <= 0.); assert!(buffer.is_silent()); } @@ -1497,11 +1427,7 @@ mod tests { &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0. ); - assert_float_eq!( - &buffer.channel_data(1)[..], - &[0.; RENDER_QUANTUM_SIZE][..], - abs_all <= 0. - ); + assert_float_eq!(&buffer.channel_data(1)[..], &ZEROES[..], abs_all <= 0.); assert!(!buffer.is_silent()); } } From 8d66beb8c3f34dd9de076fce827c11e40ce8f4dc Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 17 May 2024 20:56:08 +0200 Subject: [PATCH 06/11] Tuck the render quantum pool in a separate mod --- src/render/quantum.rs | 67 +++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index 387915d1..eea3c12e 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -1,6 +1,5 @@ //! Optimized audio signal data structures, used in `AudioProcessors` use arrayvec::ArrayVec; -use std::cell::RefCell; use std::rc::Rc; use crate::node::{ChannelConfigInner, ChannelCountMode, ChannelInterpretation}; @@ -8,29 +7,41 @@ use crate::node::{ChannelConfigInner, ChannelCountMode, ChannelInterpretation}; use crate::assert_valid_number_of_channels; use crate::{MAX_CHANNELS, RENDER_QUANTUM_SIZE}; -thread_local! { - static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); -} - +/// A sample slice containing only zeroes const ZEROES: [f32; RENDER_QUANTUM_SIZE] = [0.; RENDER_QUANTUM_SIZE]; -#[cfg(test)] -fn pool_size() -> usize { - POOL.with_borrow(|p| p.len()) -} +mod pool { + //! Thread local allocator for sample slices -fn allocate() -> Rc<[f32; RENDER_QUANTUM_SIZE]> { - if let Some(rc) = POOL.with_borrow_mut(|p| p.pop()) { - // reuse from pool - rc - } else { - // allocate - Rc::new(ZEROES) + use super::*; + use std::cell::RefCell; + + thread_local! { + /// Thread local pool of pre-allocated sample slice that can be reused after being dropped + static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); } -} -fn push(data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { - POOL.with_borrow_mut(|p| p.push(data)); + #[cfg(test)] + /// Number of pre-allocated items available in the pool + pub(super) fn size() -> usize { + POOL.with_borrow(|p| p.len()) + } + + /// Obtain a new sample slice, either reused from the pool, or newly allocated + pub(super) fn allocate() -> Rc<[f32; RENDER_QUANTUM_SIZE]> { + if let Some(rc) = POOL.with_borrow_mut(|p| p.pop()) { + // reuse from pool + rc + } else { + // allocate + Rc::new(ZEROES) + } + } + + /// Reclaim a sample slice into the thread local pool + pub(super) fn push(data: Rc<[f32; RENDER_QUANTUM_SIZE]>) { + POOL.with_borrow_mut(|p| p.push(data)); + } } /// Render thread channel buffer @@ -54,14 +65,14 @@ pub struct AudioRenderQuantumChannel { impl AudioRenderQuantumChannel { fn make_mut(&mut self) -> &mut [f32; RENDER_QUANTUM_SIZE] { if self.data.is_none() { - self.data = Some(allocate()); + self.data = Some(pool::allocate()); Rc::make_mut(self.data.as_mut().unwrap()).copy_from_slice(&ZEROES); } match &mut self.data { Some(data) => { if Rc::strong_count(data) != 1 { - let mut new = allocate(); + let mut new = pool::allocate(); Rc::make_mut(&mut new).copy_from_slice(&data[..]); *data = new; } @@ -129,7 +140,7 @@ impl std::ops::Drop for AudioRenderQuantumChannel { }; if Rc::strong_count(&data) == 1 { - push(data); + pool::push(data); } } } @@ -654,7 +665,7 @@ mod tests { let _b = b.make_mut(); } - let pool_size_start = dbg!(pool_size()); + let pool_size_start = pool::size(); assert!(pool_size_start >= 2); // due to allocation, pool size is nonzero { @@ -662,26 +673,26 @@ mod tests { let a = AudioRenderQuantumChannel::silence(); assert_float_eq!(&a[..], &ZEROES[..], abs_all <= 0.); - assert_eq!(pool_size(), pool_size_start); + assert_eq!(pool::size(), pool_size_start); // mutating this buffer will take from the pool let mut a = a; a.iter_mut().for_each(|v| *v += 1.); assert_float_eq!(&a[..], &[1.; RENDER_QUANTUM_SIZE][..], abs_all <= 0.); - assert_eq!(pool_size(), pool_size_start - 1); + assert_eq!(pool::size(), pool_size_start - 1); // clone this buffer, should not allocate #[allow(clippy::redundant_clone)] let mut b: AudioRenderQuantumChannel = a.clone(); - assert_eq!(pool_size(), pool_size_start - 1); + assert_eq!(pool::size(), pool_size_start - 1); // mutate cloned buffer, this will allocate b.iter_mut().for_each(|v| *v += 1.); - assert_eq!(pool_size(), pool_size_start - 2); + assert_eq!(pool::size(), pool_size_start - 2); } // all buffers are reclaimed - assert_eq!(pool_size(), pool_size_start); + assert_eq!(pool::size(), pool_size_start); } #[test] From f5681ea5f83215a54604a9d8bd7c930888d34b0e Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 17 May 2024 21:15:12 +0200 Subject: [PATCH 07/11] Sync the MSRV from cargo.toml to github workflow --- .github/workflows/msrv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/msrv.yaml b/.github/workflows/msrv.yaml index 6b6de740..883af7a5 100644 --- a/.github/workflows/msrv.yaml +++ b/.github/workflows/msrv.yaml @@ -25,7 +25,7 @@ jobs: - name: Install Rust toolchain # Aligned with `rust-version` in `Cargo.toml` - uses: dtolnay/rust-toolchain@1.71 + uses: dtolnay/rust-toolchain@1.73 - name: Check out repository uses: actions/checkout@v3 From 17631feb9148d16b823161e0ef19f33a74f9b91e Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 22 May 2024 17:56:50 +0200 Subject: [PATCH 08/11] Add a log message when filling the thread local audio quanta pool It is still a bit magical to me and I want to see when a new pool is initialized. --- src/render/quantum.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index eea3c12e..74c7a113 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -18,7 +18,10 @@ mod pool { thread_local! { /// Thread local pool of pre-allocated sample slice that can be reused after being dropped - static POOL: RefCell>> = RefCell::new(Vec::with_capacity(32)); + static POOL: RefCell>> = { + log::debug!("Setting up a new thread local pool of audio quanta"); + RefCell::new(Vec::with_capacity(32)) + }; } #[cfg(test)] From 068e7ea3ce24b4e2a57e7daffab29c34424bf01b Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 24 May 2024 16:46:58 +0200 Subject: [PATCH 09/11] Pre-allocate 128 render quantum channels in each thread --- src/render/quantum.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/render/quantum.rs b/src/render/quantum.rs index 74c7a113..ce20cd3d 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -20,7 +20,10 @@ mod pool { /// Thread local pool of pre-allocated sample slice that can be reused after being dropped static POOL: RefCell>> = { log::debug!("Setting up a new thread local pool of audio quanta"); - RefCell::new(Vec::with_capacity(32)) + + const SIZE: usize = 128; // TODO - leak more memory or risk allocations at runtime? + let pool: Vec<_> = (0..SIZE).map(|_| Rc::new(ZEROES)).collect(); + RefCell::new(pool) }; } From 5e5e43c0d5b153cb606a91ececb2f15e1f87a9e2 Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 24 May 2024 17:12:09 +0200 Subject: [PATCH 10/11] Delay renderer: check ring buffer size is no longer needed With the new design of 'silent quantum' we can also instantiate silent buffers on the control thread. --- src/node/delay.rs | 48 +++++-------------------------------------- src/render/quantum.rs | 11 ++++++++++ 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/src/node/delay.rs b/src/node/delay.rs index 1697f7b2..09ce4f16 100644 --- a/src/node/delay.rs +++ b/src/node/delay.rs @@ -7,7 +7,7 @@ use crate::RENDER_QUANTUM_SIZE; use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelInterpretation}; -use std::cell::{Cell, RefCell, RefMut}; +use std::cell::{Cell, RefCell}; use std::rc::Rc; /// Options for constructing a [`DelayNode`] @@ -299,7 +299,9 @@ impl DelayNode { let max_delay_time = options.max_delay_time; let num_quanta = (max_delay_time * sample_rate / RENDER_QUANTUM_SIZE as f64).ceil() as usize; - let ring_buffer = Vec::with_capacity(num_quanta + 1); + + let quantum = AudioRenderQuantum::new(); + let ring_buffer = vec![quantum; num_quanta + 1]; let shared_ring_buffer = Rc::new(RefCell::new(ring_buffer)); let shared_ring_buffer_clone = Rc::clone(&shared_ring_buffer); @@ -386,26 +388,6 @@ struct DelayWriter { #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for DelayWriter {} -trait RingBufferChecker { - fn ring_buffer_mut(&self) -> RefMut<'_, Vec>; - - // This step guarantees the ring buffer is filled with silence buffers, - // This allow to simplify the code in both Writer and Reader as we know - // `len() == capacity()` and all inner buffers are initialized with zeros. - #[inline(always)] - fn check_ring_buffer_size(&self, render_quantum: &AudioRenderQuantum) { - let mut ring_buffer = self.ring_buffer_mut(); - - if ring_buffer.len() < ring_buffer.capacity() { - let len = ring_buffer.capacity(); - let mut silence = render_quantum.clone(); - silence.make_silent(); - - ring_buffer.resize(len, silence); - } - } -} - impl Drop for DelayWriter { fn drop(&mut self) { let last_written_index = if self.index == 0 { @@ -418,13 +400,6 @@ impl Drop for DelayWriter { } } -impl RingBufferChecker for DelayWriter { - #[inline(always)] - fn ring_buffer_mut(&self) -> RefMut<'_, Vec> { - self.ring_buffer.borrow_mut() - } -} - impl AudioProcessor for DelayWriter { fn process( &mut self, @@ -437,9 +412,6 @@ impl AudioProcessor for DelayWriter { let input = inputs[0].clone(); let output = &mut outputs[0]; - // We must perform this check on both Writer and Reader as the order of - // the rendering between them is not guaranteed. - self.check_ring_buffer_size(&input); // `check_ring_buffer_up_down_mix` can only be done on the Writer // side as Reader do not access the "real" input self.check_ring_buffer_up_down_mix(&input); @@ -476,7 +448,7 @@ impl DelayWriter { // they MUST be upmixed or downmixed before being combined with newly received // input so that all internal delay-line mixing takes place using the single // prevailing channel layout. - let mut ring_buffer = self.ring_buffer_mut(); + let mut ring_buffer = self.ring_buffer.borrow_mut(); let buffer_number_of_channels = ring_buffer[0].number_of_channels(); let input_number_of_channels = input.number_of_channels(); @@ -505,13 +477,6 @@ struct DelayReader { #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for DelayReader {} -impl RingBufferChecker for DelayReader { - #[inline(always)] - fn ring_buffer_mut(&self) -> RefMut<'_, Vec> { - self.ring_buffer.borrow_mut() - } -} - impl AudioProcessor for DelayReader { fn process( &mut self, @@ -522,9 +487,6 @@ impl AudioProcessor for DelayReader { ) -> bool { // single input/output node let output = &mut outputs[0]; - // We must perform the checks (buffer size and up/down mix) on both Writer - // and Reader as the order of processing between them is not guaranteed. - self.check_ring_buffer_size(output); let ring_buffer = self.ring_buffer.borrow(); diff --git a/src/render/quantum.rs b/src/render/quantum.rs index ce20cd3d..6a1cc53b 100644 --- a/src/render/quantum.rs +++ b/src/render/quantum.rs @@ -176,6 +176,17 @@ pub struct AudioRenderQuantum { } impl AudioRenderQuantum { + /// Create a new `AudioRenderQuantum` with a single channel of silence + pub(crate) fn new() -> Self { + let mut channels = ArrayVec::new(); + channels.push(AudioRenderQuantumChannel::silence()); + + Self { + channels, + single_valued: false, + } + } + /// Create a new `AudioRenderQuantum` from a single channel buffer pub(crate) fn from(channel: AudioRenderQuantumChannel) -> Self { let mut channels = ArrayVec::new(); From 0d2be3e92d3707eb19a7f9ccf88de22278da5ab6 Mon Sep 17 00:00:00 2001 From: Otto Date: Fri, 24 May 2024 17:16:30 +0200 Subject: [PATCH 11/11] DelayNode: use boxed slice for delay buffer to denote immutable length --- src/node/delay.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/node/delay.rs b/src/node/delay.rs index 09ce4f16..47d1797e 100644 --- a/src/node/delay.rs +++ b/src/node/delay.rs @@ -301,7 +301,7 @@ impl DelayNode { (max_delay_time * sample_rate / RENDER_QUANTUM_SIZE as f64).ceil() as usize; let quantum = AudioRenderQuantum::new(); - let ring_buffer = vec![quantum; num_quanta + 1]; + let ring_buffer = vec![quantum; num_quanta + 1].into_boxed_slice(); let shared_ring_buffer = Rc::new(RefCell::new(ring_buffer)); let shared_ring_buffer_clone = Rc::clone(&shared_ring_buffer); @@ -376,7 +376,7 @@ impl DelayNode { } struct DelayWriter { - ring_buffer: Rc>>, + ring_buffer: Rc>>, index: usize, latest_frame_written: Rc>, last_written_index: Rc>>, @@ -391,7 +391,7 @@ unsafe impl Send for DelayWriter {} impl Drop for DelayWriter { fn drop(&mut self) { let last_written_index = if self.index == 0 { - self.ring_buffer.borrow().capacity() - 1 + self.ring_buffer.borrow().len() - 1 } else { self.index - 1 }; @@ -421,7 +421,7 @@ impl AudioProcessor for DelayWriter { buffer[self.index] = input; // increment cursor and last written frame - self.index = (self.index + 1) % buffer.capacity(); + self.index = (self.index + 1) % buffer.len(); self.latest_frame_written.set(scope.current_frame); // The writer end does not produce output, @@ -462,7 +462,7 @@ impl DelayWriter { struct DelayReader { delay_time: AudioParamId, - ring_buffer: Rc>>, + ring_buffer: Rc>>, index: usize, latest_frame_written: Rc>, in_cycle: bool, @@ -639,7 +639,7 @@ impl AudioProcessor for DelayReader { self.last_written_index_checked = last_written_index; } // increment ring buffer cursor - self.index = (self.index + 1) % ring_buffer.capacity(); + self.index = (self.index + 1) % ring_buffer.len(); true }