Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment with thread local pool of audio render quanta #511

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion .github/workflows/msrv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include = [
"LICENSE",
"README.md",
]
rust-version = "1.71"
rust-version = "1.73"

[dependencies]
arc-swap = "1.6"
Expand Down
58 changes: 10 additions & 48 deletions src/node/delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand Down Expand Up @@ -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].into_boxed_slice();

let shared_ring_buffer = Rc::new(RefCell::new(ring_buffer));
let shared_ring_buffer_clone = Rc::clone(&shared_ring_buffer);
Expand Down Expand Up @@ -374,7 +376,7 @@ impl DelayNode {
}

struct DelayWriter {
ring_buffer: Rc<RefCell<Vec<AudioRenderQuantum>>>,
ring_buffer: Rc<RefCell<Box<[AudioRenderQuantum]>>>,
index: usize,
latest_frame_written: Rc<Cell<u64>>,
last_written_index: Rc<Cell<Option<usize>>>,
Expand All @@ -386,30 +388,10 @@ struct DelayWriter {
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for DelayWriter {}

trait RingBufferChecker {
fn ring_buffer_mut(&self) -> RefMut<'_, Vec<AudioRenderQuantum>>;

// 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 {
self.ring_buffer.borrow().capacity() - 1
self.ring_buffer.borrow().len() - 1
} else {
self.index - 1
};
Expand All @@ -418,13 +400,6 @@ impl Drop for DelayWriter {
}
}

impl RingBufferChecker for DelayWriter {
#[inline(always)]
fn ring_buffer_mut(&self) -> RefMut<'_, Vec<AudioRenderQuantum>> {
self.ring_buffer.borrow_mut()
}
}

impl AudioProcessor for DelayWriter {
fn process(
&mut self,
Expand All @@ -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);
Expand All @@ -449,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,
Expand All @@ -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();

Expand All @@ -490,7 +462,7 @@ impl DelayWriter {

struct DelayReader {
delay_time: AudioParamId,
ring_buffer: Rc<RefCell<Vec<AudioRenderQuantum>>>,
ring_buffer: Rc<RefCell<Box<[AudioRenderQuantum]>>>,
index: usize,
latest_frame_written: Rc<Cell<u64>>,
in_cycle: bool,
Expand All @@ -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<AudioRenderQuantum>> {
self.ring_buffer.borrow_mut()
}
}

impl AudioProcessor for DelayReader {
fn process(
&mut self,
Expand All @@ -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();

Expand Down Expand Up @@ -677,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
}
Expand Down
25 changes: 10 additions & 15 deletions src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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.);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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.);

Expand All @@ -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);
Expand Down
13 changes: 7 additions & 6 deletions src/render/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<AudioNodeId>,
/// Topological ordering of the nodes
Expand Down Expand Up @@ -151,7 +151,6 @@ impl Graph {
pub fn new(reclaim_id_channel: llq::Producer<AudioNodeId>) -> Self {
Graph {
nodes: NodeCollection::new(),
alloc: Alloc::with_capacity(64),
reclaim_id_channel,
ordered: vec![],
marked: vec![],
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading