Skip to content

Commit 98bf467

Browse files
authored
Merge pull request #337 from b-ma/refactor/message-passing
Refactor message passing
2 parents a56b888 + 13cda2c commit 98bf467

File tree

7 files changed

+354
-277
lines changed

7 files changed

+354
-277
lines changed

src/node/biquad_filter.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! The biquad filter control and renderer parts
2-
use num_complex::Complex;
2+
use std::any::Any;
33
use std::f64::consts::{PI, SQRT_2};
44
use std::sync::atomic::{AtomicU32, Ordering};
5-
use std::sync::Arc;
5+
6+
use num_complex::Complex;
67

78
use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
89
use crate::param::{AudioParam, AudioParamDescriptor};
@@ -309,7 +310,7 @@ pub struct BiquadFilterNode {
309310
/// filter, depends on the `BiquadFilterType`
310311
gain: AudioParam,
311312
/// `BiquadFilterType` represented as u32
312-
type_: Arc<AtomicU32>,
313+
type_: AtomicU32,
313314
}
314315

315316
impl AudioNode for BiquadFilterNode {
@@ -341,23 +342,32 @@ impl BiquadFilterNode {
341342
context.register(move |registration| {
342343
let sample_rate = context.sample_rate();
343344

344-
let q_options = AudioParamDescriptor {
345+
let BiquadFilterOptions {
346+
q,
347+
detune,
348+
frequency,
349+
gain,
350+
type_,
351+
channel_config,
352+
} = options;
353+
354+
let q_param_options = AudioParamDescriptor {
345355
min_value: f32::MIN,
346356
max_value: f32::MAX,
347357
default_value: 1.,
348358
automation_rate: crate::param::AutomationRate::A,
349359
};
350-
let (q_param, q_proc) = context.create_audio_param(q_options, &registration);
351-
q_param.set_value(options.q);
360+
let (q_param, q_proc) = context.create_audio_param(q_param_options, &registration);
361+
q_param.set_value(q);
352362

353-
let detune_options = AudioParamDescriptor {
363+
let detune_param_options = AudioParamDescriptor {
354364
min_value: -153_600.,
355365
max_value: 153_600.,
356366
default_value: 0.,
357367
automation_rate: crate::param::AutomationRate::A,
358368
};
359-
let (d_param, d_proc) = context.create_audio_param(detune_options, &registration);
360-
d_param.set_value(options.detune);
369+
let (d_param, d_proc) = context.create_audio_param(detune_param_options, &registration);
370+
d_param.set_value(detune);
361371

362372
let freq_options = AudioParamDescriptor {
363373
min_value: 0.,
@@ -366,7 +376,7 @@ impl BiquadFilterNode {
366376
automation_rate: crate::param::AutomationRate::A,
367377
};
368378
let (f_param, f_proc) = context.create_audio_param(freq_options, &registration);
369-
f_param.set_value(options.frequency);
379+
f_param.set_value(frequency);
370380

371381
let gain_options = AudioParamDescriptor {
372382
min_value: f32::MIN,
@@ -375,16 +385,14 @@ impl BiquadFilterNode {
375385
automation_rate: crate::param::AutomationRate::A,
376386
};
377387
let (g_param, g_proc) = context.create_audio_param(gain_options, &registration);
378-
g_param.set_value(options.gain);
379-
380-
let type_ = Arc::new(AtomicU32::new(options.type_ as u32));
388+
g_param.set_value(gain);
381389

382390
let renderer = BiquadFilterRenderer {
383391
gain: g_proc,
384392
detune: d_proc,
385393
frequency: f_proc,
386394
q: q_proc,
387-
type_: Arc::clone(&type_),
395+
type_,
388396
x1: Vec::with_capacity(MAX_CHANNELS),
389397
x2: Vec::with_capacity(MAX_CHANNELS),
390398
y1: Vec::with_capacity(MAX_CHANNELS),
@@ -393,8 +401,8 @@ impl BiquadFilterNode {
393401

394402
let node = Self {
395403
registration,
396-
channel_config: options.channel_config.into(),
397-
type_,
404+
channel_config: channel_config.into(),
405+
type_: AtomicU32::new(type_ as u32),
398406
q: q_param,
399407
detune: d_param,
400408
frequency: f_param,
@@ -432,7 +440,7 @@ impl BiquadFilterNode {
432440
/// Returns the biquad filter type
433441
#[must_use]
434442
pub fn type_(&self) -> BiquadFilterType {
435-
self.type_.load(Ordering::SeqCst).into()
443+
self.type_.load(Ordering::Acquire).into()
436444
}
437445

438446
/// biquad filter type setter
@@ -441,7 +449,8 @@ impl BiquadFilterNode {
441449
///
442450
/// * `type_` - the biquad filter type (lowpass, highpass,...)
443451
pub fn set_type(&self, type_: BiquadFilterType) {
444-
self.type_.store(type_ as u32, Ordering::SeqCst);
452+
self.type_.store(type_ as u32, Ordering::Release);
453+
self.registration.post_message(type_);
445454
}
446455

447456
/// Returns the frequency response for the specified frequencies
@@ -533,8 +542,8 @@ struct BiquadFilterRenderer {
533542
/// boost/attenuation (dB) - its impact on the frequency response of the filter
534543
/// depends on the `BiquadFilterType`
535544
gain: AudioParamId,
536-
/// `BiquadFilterType` represented as u32
537-
type_: Arc<AtomicU32>,
545+
/// `BiquadFilterType`
546+
type_: BiquadFilterType,
538547
// keep filter state for each channel
539548
x1: Vec<f64>,
540549
x2: Vec<f64>,
@@ -596,7 +605,7 @@ impl AudioProcessor for BiquadFilterRenderer {
596605
}
597606

598607
// get a-rate parameters
599-
let type_: BiquadFilterType = self.type_.load(Ordering::SeqCst).into();
608+
let type_ = self.type_;
600609
let frequency = params.get(&self.frequency);
601610
let detune = params.get(&self.detune);
602611
let q = params.get(&self.q);
@@ -671,6 +680,15 @@ impl AudioProcessor for BiquadFilterRenderer {
671680

672681
true
673682
}
683+
684+
fn onmessage(&mut self, msg: &mut dyn Any) {
685+
if let Some(&type_) = msg.downcast_ref::<BiquadFilterType>() {
686+
self.type_ = type_;
687+
return;
688+
}
689+
690+
log::warn!("BiquadFilterRenderer: Dropping incoming message {msg:?}");
691+
}
674692
}
675693

676694
#[cfg(test)]

src/node/constant_source.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
1111
// dictionary ConstantSourceOptions {
1212
// float offset = 1;
1313
// };
14+
// https://webaudio.github.io/web-audio-api/#ConstantSourceOptions
1415
//
1516
// @note - Does not extend AudioNodeOptions because AudioNodeOptions are
16-
// useless for source nodes as they instruct how to upmix the inputs.
17-
// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472
17+
// useless for source nodes, because they instruct how to upmix the inputs.
18+
// This is a common source of confusion, see e.g. mdn/content#18472
1819
#[derive(Clone, Debug)]
1920
pub struct ConstantSourceOptions {
21+
/// Initial parameter value of the constant signal
2022
pub offset: f32,
2123
}
2224

@@ -114,14 +116,16 @@ impl AudioScheduledSourceNode for ConstantSourceNode {
114116
impl ConstantSourceNode {
115117
pub fn new<C: BaseAudioContext>(context: &C, options: ConstantSourceOptions) -> Self {
116118
context.register(move |registration| {
117-
let param_opts = AudioParamDescriptor {
119+
let ConstantSourceOptions { offset } = options;
120+
121+
let param_options = AudioParamDescriptor {
118122
min_value: f32::MIN,
119123
max_value: f32::MAX,
120124
default_value: 1.,
121125
automation_rate: AutomationRate::A,
122126
};
123-
let (param, proc) = context.create_audio_param(param_opts, &registration);
124-
param.set_value(options.offset);
127+
let (param, proc) = context.create_audio_param(param_options, &registration);
128+
param.set_value(offset);
125129

126130
let render = ConstantSourceRenderer {
127131
offset: proc,

src/node/convolver.rs

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
use super::{AudioNode, ChannelConfig, ChannelConfigOptions, ChannelInterpretation};
1+
use std::any::Any;
2+
use std::sync::atomic::{AtomicBool, Ordering};
3+
use std::sync::{Arc, Mutex};
4+
5+
use realfft::{num_complex::Complex, ComplexToReal, RealFftPlanner, RealToComplex};
6+
27
use crate::buffer::AudioBuffer;
38
use crate::context::{AudioContextRegistration, BaseAudioContext};
49
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
510
use crate::RENDER_QUANTUM_SIZE;
611

7-
use crossbeam_channel::{Receiver, Sender};
8-
use realfft::{num_complex::Complex, ComplexToReal, RealFftPlanner, RealToComplex};
9-
use std::sync::{
10-
atomic::{AtomicBool, Ordering},
11-
Arc, Mutex,
12-
};
12+
use super::{AudioNode, ChannelConfig, ChannelConfigOptions, ChannelInterpretation};
1313

1414
/// Scale buffer by an equal-power normalization
15-
fn normalization(buffer: &AudioBuffer) -> f32 {
15+
// see - <https://webaudio.github.io/web-audio-api/#dom-convolvernode-normalize>
16+
fn normalize_buffer(buffer: &AudioBuffer) -> f32 {
1617
let gain_calibration = 0.00125;
1718
let gain_calibration_sample_rate = 44100.;
1819
let min_power = 0.000125;
@@ -116,8 +117,6 @@ pub struct ConvolverNode {
116117
normalize: AtomicBool,
117118
/// The response buffer, nullable
118119
buffer: Mutex<Option<AudioBuffer>>,
119-
/// Message bus to the renderer
120-
sender: Sender<ConvolverRendererInner>,
121120
}
122121

123122
impl AudioNode for ConvolverNode {
@@ -151,33 +150,31 @@ impl ConvolverNode {
151150
/// Panics when an AudioBuffer is provided via the `ConvolverOptions` with a sample rate
152151
/// different from the audio context sample rate.
153152
pub fn new<C: BaseAudioContext>(context: &C, options: ConvolverOptions) -> Self {
154-
context.base().register(move |registration| {
155-
let ConvolverOptions {
156-
buffer,
157-
disable_normalization,
158-
channel_config,
159-
} = options;
160-
161-
// Channel to send buffer channels references to the renderer. A capacity of 1
162-
// suffices, it will simply block the control thread when used concurrently
163-
let (sender, receiver) = crossbeam_channel::bounded(1);
153+
let ConvolverOptions {
154+
buffer,
155+
disable_normalization,
156+
channel_config,
157+
} = options;
164158

165-
let renderer = ConvolverRenderer::new(receiver);
159+
let node = context.base().register(move |registration| {
160+
let renderer = ConvolverRenderer { inner: None };
166161

167162
let node = Self {
168163
registration,
169164
channel_config: channel_config.into(),
170165
normalize: AtomicBool::new(!disable_normalization),
171-
sender,
172166
buffer: Mutex::new(None),
173167
};
174168

175-
if let Some(buffer) = buffer {
176-
node.set_buffer(buffer);
177-
}
178-
179169
(node, Box::new(renderer))
180-
})
170+
});
171+
172+
// renderer has been sent to render thread, we can send it messages
173+
if let Some(buffer) = buffer {
174+
node.set_buffer(buffer);
175+
}
176+
177+
node
181178
}
182179

183180
/// Get the current impulse response buffer
@@ -199,7 +196,7 @@ impl ConvolverNode {
199196

200197
// normalize before padding because the length of the buffer affects the scale
201198
let scale = if self.normalize() {
202-
normalization(&buffer)
199+
normalize_buffer(&buffer)
203200
} else {
204201
1.
205202
};
@@ -219,9 +216,9 @@ impl ConvolverNode {
219216
.collect();
220217

221218
let padded_buffer = AudioBuffer::from(samples, sample_rate);
222-
223219
let convolve = ConvolverRendererInner::new(padded_buffer);
224-
let _ = self.sender.send(convolve); // can fail when render thread shut down
220+
221+
self.registration.post_message(Some(convolve));
225222

226223
*self.buffer.lock().unwrap() = Some(buffer);
227224
}
@@ -304,20 +301,6 @@ impl Fft {
304301
}
305302
}
306303

307-
struct ConvolverRenderer {
308-
receiver: Receiver<ConvolverRendererInner>,
309-
inner: Option<ConvolverRendererInner>,
310-
}
311-
312-
impl ConvolverRenderer {
313-
fn new(receiver: Receiver<ConvolverRendererInner>) -> Self {
314-
Self {
315-
receiver,
316-
inner: None,
317-
}
318-
}
319-
}
320-
321304
struct ConvolverRendererInner {
322305
num_ir_blocks: usize,
323306
h: Vec<Complex<f32>>,
@@ -414,6 +397,10 @@ impl ConvolverRendererInner {
414397
}
415398
}
416399

400+
struct ConvolverRenderer {
401+
inner: Option<ConvolverRendererInner>,
402+
}
403+
417404
impl AudioProcessor for ConvolverRenderer {
418405
fn process(
419406
&mut self,
@@ -427,11 +414,6 @@ impl AudioProcessor for ConvolverRenderer {
427414
let output = &mut outputs[0];
428415
output.force_mono();
429416

430-
// handle new impulse response buffer, if any
431-
if let Ok(msg) = self.receiver.try_recv() {
432-
self.inner = Some(msg);
433-
}
434-
435417
let convolver = match &mut self.inner {
436418
None => {
437419
// no convolution buffer set, passthrough
@@ -455,6 +437,16 @@ impl AudioProcessor for ConvolverRenderer {
455437

456438
true
457439
}
440+
441+
fn onmessage(&mut self, msg: &mut dyn Any) {
442+
if let Some(convolver) = msg.downcast_mut::<Option<ConvolverRendererInner>>() {
443+
// Avoid deallocation in the render thread by swapping the convolver.
444+
std::mem::swap(&mut self.inner, convolver);
445+
return;
446+
}
447+
448+
log::warn!("ConvolverRenderer: Dropping incoming message {msg:?}");
449+
}
458450
}
459451

460452
#[cfg(test)]
@@ -473,6 +465,36 @@ mod tests {
473465
assert_eq!(&input, &[4, 5, 6, 7, 8, 9, 10, 0, 0, 0]);
474466
}
475467

468+
#[test]
469+
fn test_constructor_options_buffer() {
470+
let sample_rate = 44100.;
471+
let context = OfflineAudioContext::new(1, 10, sample_rate);
472+
473+
let ir = vec![1.];
474+
let calibration = 0.00125;
475+
let channel_data = vec![0., 1., 0., -1., 0.];
476+
let expected = vec![0., calibration, 0., -calibration, 0., 0., 0., 0., 0., 0.];
477+
478+
// identity ir
479+
let ir = AudioBuffer::from(vec![ir; 1], sample_rate);
480+
let options = ConvolverOptions {
481+
buffer: Some(ir),
482+
..ConvolverOptions::default()
483+
};
484+
let conv = ConvolverNode::new(&context, options);
485+
conv.connect(&context.destination());
486+
487+
let buffer = AudioBuffer::from(vec![channel_data; 1], sample_rate);
488+
let src = context.create_buffer_source();
489+
src.connect(&conv);
490+
src.set_buffer(buffer);
491+
src.start();
492+
493+
let output = context.start_rendering_sync();
494+
495+
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
496+
}
497+
476498
fn test_convolve(signal: &[f32], impulse_resp: Option<Vec<f32>>, length: usize) -> AudioBuffer {
477499
let sample_rate = 44100.;
478500
let context = OfflineAudioContext::new(1, length, sample_rate);

0 commit comments

Comments
 (0)