Skip to content

Commit 4bb48b7

Browse files
committed
docs: generalize note about AudioNodeOptions not useful in source nodes
1 parent fbba66f commit 4bb48b7

File tree

6 files changed

+124
-64
lines changed

6 files changed

+124
-64
lines changed

src/node/audio_buffer_source.rs

Lines changed: 98 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::any::Any;
2-
use std::cell::{OnceCell, RefCell};
2+
use std::cell::OnceCell;
3+
use std::sync::atomic::{AtomicBool, Ordering};
34

45
use crate::buffer::AudioBuffer;
56
use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
67
use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
78
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
9+
use crate::AtomicF64;
810
use crate::RENDER_QUANTUM_SIZE;
911

1012
use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
@@ -18,6 +20,10 @@ use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
1820
// double loopStart = 0;
1921
// float playbackRate = 1;
2022
// };
23+
//
24+
// @note - Does not extend AudioNodeOptions because AudioNodeOptions are
25+
// useless for source nodes as they instruct how to upmix the inputs.
26+
// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472
2127
#[derive(Clone, Debug)]
2228
pub struct AudioBufferSourceOptions {
2329
pub buffer: Option<AudioBuffer>,
@@ -47,15 +53,54 @@ struct PlaybackInfo {
4753
k: f32,
4854
}
4955

50-
#[derive(Debug, Clone)]
51-
struct LoopState {
52-
pub is_looping: bool,
53-
pub start: f64,
54-
pub end: f64,
56+
// The strategy for loop parameters is as follow: store the given values
57+
// in the `loop_control` thread safe instance which only lives in control
58+
// thread(s) and send a message to the render thread which stores the raw values.
59+
// Values between control and render side might be desynchronised for little while
60+
// but the developer experience will appear more logical, i.e.
61+
// ```no_run
62+
// node.set_loop(true);
63+
// println!("{:?}", node.loop_());
64+
// > true // is guaranteed
65+
// ```
66+
// Note that this seems to be the strategy used by Firefox
67+
#[derive(Debug)]
68+
struct LoopControl {
69+
loop_: AtomicBool,
70+
loop_start: AtomicF64,
71+
loop_end: AtomicF64,
72+
}
73+
74+
// Uses the canonical ordering for handover of values,
75+
// i.e. `Acquire` on load and `Release` on store.
76+
impl LoopControl {
77+
fn loop_(&self) -> bool {
78+
self.loop_.load(Ordering::Acquire)
79+
}
80+
81+
fn set_loop(&self, loop_: bool) {
82+
self.loop_.store(loop_, Ordering::Release);
83+
}
84+
85+
fn loop_start(&self) -> f64 {
86+
self.loop_start.load(Ordering::Acquire)
87+
}
88+
89+
fn set_loop_start(&self, loop_start: f64) {
90+
self.loop_start.store(loop_start, Ordering::Release);
91+
}
92+
93+
fn loop_end(&self) -> f64 {
94+
self.loop_end.load(Ordering::Acquire)
95+
}
96+
97+
fn set_loop_end(&self, loop_end: f64) {
98+
self.loop_end.store(loop_end, Ordering::Release);
99+
}
55100
}
56101

57102
/// Instructions to start or stop processing
58-
#[derive(Debug, Clone)]
103+
#[derive(Debug, Copy, Clone)]
59104
enum ControlMessage {
60105
StartWithOffsetAndDuration(f64, f64, f64),
61106
Stop(f64),
@@ -98,17 +143,12 @@ enum ControlMessage {
98143
///
99144
pub struct AudioBufferSourceNode {
100145
registration: AudioContextRegistration,
146+
loop_control: LoopControl,
101147
channel_config: ChannelConfig,
102148
detune: AudioParam, // has constraints, no a-rate
103149
playback_rate: AudioParam, // has constraints, no a-rate
104150
buffer: OnceCell<AudioBuffer>,
105-
inner_state: RefCell<InnerState>,
106-
}
107-
108-
#[derive(Debug, Clone)]
109-
struct InnerState {
110-
loop_state: LoopState,
111-
source_started: bool,
151+
source_started: AtomicBool,
112152
}
113153

114154
impl AudioNode for AudioBufferSourceNode {
@@ -145,10 +185,9 @@ impl AudioScheduledSourceNode for AudioBufferSourceNode {
145185
}
146186

147187
fn stop_at(&self, when: f64) {
148-
assert!(
149-
self.inner_state.borrow().source_started,
150-
"InvalidStateError cannot stop before start"
151-
);
188+
if !self.source_started.load(Ordering::SeqCst) {
189+
panic!("InvalidStateError cannot stop before start");
190+
}
152191

153192
self.registration.post_message(ControlMessage::Stop(when));
154193
}
@@ -191,36 +230,35 @@ impl AudioBufferSourceNode {
191230
pr_param.set_automation_rate_constrained(true);
192231
pr_param.set_value(playback_rate);
193232

194-
let loop_state = LoopState {
195-
is_looping: loop_,
196-
start: loop_start,
197-
end: loop_end,
233+
let loop_control = LoopControl {
234+
loop_: AtomicBool::new(loop_),
235+
loop_start: AtomicF64::new(loop_start),
236+
loop_end: AtomicF64::new(loop_end),
198237
};
199238

200239
let renderer = AudioBufferSourceRenderer {
201240
start_time: f64::MAX,
202241
stop_time: f64::MAX,
203242
duration: f64::MAX,
204243
offset: 0.,
244+
loop_,
245+
loop_start,
246+
loop_end,
205247
buffer: None,
206248
detune: d_proc,
207249
playback_rate: pr_proc,
208-
loop_state: loop_state.clone(),
209250
render_state: AudioBufferRendererState::default(),
210251
ended_triggered: false,
211252
};
212253

213-
let inner_state = InnerState {
214-
loop_state,
215-
source_started: false,
216-
};
217254
let node = Self {
218255
registration,
256+
loop_control,
219257
channel_config: ChannelConfig::default(),
220258
detune: d_param,
221259
playback_rate: pr_param,
222260
buffer: OnceCell::new(),
223-
inner_state: RefCell::new(inner_state),
261+
source_started: AtomicBool::new(false),
224262
};
225263

226264
if let Some(buf) = buffer {
@@ -246,12 +284,9 @@ impl AudioBufferSourceNode {
246284
///
247285
/// Panics if the source was already started
248286
pub fn start_at_with_offset_and_duration(&self, start: f64, offset: f64, duration: f64) {
249-
let source_started = &mut self.inner_state.borrow_mut().source_started;
250-
assert!(
251-
!*source_started,
252-
"InvalidStateError: Cannot call `start` twice"
253-
);
254-
*source_started = true;
287+
if self.source_started.swap(true, Ordering::SeqCst) {
288+
panic!("InvalidStateError: Cannot call `start` twice");
289+
}
255290

256291
let control = ControlMessage::StartWithOffsetAndDuration(start, offset, duration);
257292
self.registration.post_message(control);
@@ -298,32 +333,32 @@ impl AudioBufferSourceNode {
298333

299334
/// Defines if the playback the [`AudioBuffer`] should be looped
300335
pub fn loop_(&self) -> bool {
301-
self.inner_state.borrow().loop_state.is_looping
336+
self.loop_control.loop_()
302337
}
303338

304339
pub fn set_loop(&self, value: bool) {
305-
self.inner_state.borrow_mut().loop_state.is_looping = value;
340+
self.loop_control.set_loop(value);
306341
self.registration.post_message(ControlMessage::Loop(value));
307342
}
308343

309344
/// Defines the loop start point, in the time reference of the [`AudioBuffer`]
310345
pub fn loop_start(&self) -> f64 {
311-
self.inner_state.borrow().loop_state.start
346+
self.loop_control.loop_start()
312347
}
313348

314349
pub fn set_loop_start(&self, value: f64) {
315-
self.inner_state.borrow_mut().loop_state.start = value;
350+
self.loop_control.set_loop_start(value);
316351
self.registration
317352
.post_message(ControlMessage::LoopStart(value));
318353
}
319354

320355
/// Defines the loop end point, in the time reference of the [`AudioBuffer`]
321356
pub fn loop_end(&self) -> f64 {
322-
self.inner_state.borrow().loop_state.end
357+
self.loop_control.loop_end()
323358
}
324359

325360
pub fn set_loop_end(&self, value: f64) {
326-
self.inner_state.borrow_mut().loop_state.end = value;
361+
self.loop_control.set_loop_end(value);
327362
self.registration
328363
.post_message(ControlMessage::LoopEnd(value));
329364
}
@@ -354,26 +389,28 @@ struct AudioBufferSourceRenderer {
354389
stop_time: f64,
355390
offset: f64,
356391
duration: f64,
392+
loop_: bool,
393+
loop_start: f64,
394+
loop_end: f64,
357395
buffer: Option<AudioBuffer>,
358396
detune: AudioParamId,
359397
playback_rate: AudioParamId,
360-
loop_state: LoopState,
361398
render_state: AudioBufferRendererState,
362399
ended_triggered: bool,
363400
}
364401

365402
impl AudioBufferSourceRenderer {
366-
fn handle_control_message(&mut self, control: &ControlMessage) {
403+
fn handle_control_message(&mut self, control: ControlMessage) {
367404
match control {
368405
ControlMessage::StartWithOffsetAndDuration(when, offset, duration) => {
369-
self.start_time = *when;
370-
self.offset = *offset;
371-
self.duration = *duration;
406+
self.start_time = when;
407+
self.offset = offset;
408+
self.duration = duration;
372409
}
373-
ControlMessage::Stop(when) => self.stop_time = *when,
374-
ControlMessage::Loop(is_looping) => self.loop_state.is_looping = *is_looping,
375-
ControlMessage::LoopStart(loop_start) => self.loop_state.start = *loop_start,
376-
ControlMessage::LoopEnd(loop_end) => self.loop_state.end = *loop_end,
410+
ControlMessage::Stop(when) => self.stop_time = when,
411+
ControlMessage::Loop(loop_) => self.loop_ = loop_,
412+
ControlMessage::LoopStart(loop_start) => self.loop_start = loop_start,
413+
ControlMessage::LoopEnd(loop_end) => self.loop_end = loop_end,
377414
}
378415
}
379416
}
@@ -394,11 +431,10 @@ impl AudioProcessor for AudioBufferSourceRenderer {
394431
let block_duration = dt * RENDER_QUANTUM_SIZE as f64;
395432
let next_block_time = scope.current_time + block_duration;
396433

397-
let LoopState {
398-
is_looping,
399-
start: loop_start,
400-
end: loop_end,
401-
} = self.loop_state.clone();
434+
// grab all timing information
435+
let loop_ = self.loop_;
436+
let loop_start = self.loop_start;
437+
let loop_end = self.loop_end;
402438

403439
// these will only be used if `loop_` is true, so no need for `Option`
404440
let mut actual_loop_start = 0.;
@@ -453,7 +489,7 @@ impl AudioProcessor for AudioBufferSourceRenderer {
453489
}
454490

455491
// 3. the end of the buffer has been reached.
456-
if !is_looping {
492+
if !loop_ {
457493
if computed_playback_rate > 0. && self.render_state.buffer_time >= buffer_duration {
458494
output.make_silent(); // also converts to mono
459495
if !self.ended_triggered {
@@ -558,15 +594,15 @@ impl AudioProcessor for AudioBufferSourceRenderer {
558594
*o = if buffer_index < end_index {
559595
buffer_channel[buffer_index]
560596
} else {
561-
if is_looping && buffer_index == end_index {
597+
if loop_ && buffer_index == end_index {
562598
loop_point_index = Some(index);
563599
// reset values for the rest of the block
564600
start_index = 0;
565601
offset = index;
566602
buffer_index = 0;
567603
}
568604

569-
if is_looping {
605+
if loop_ {
570606
buffer_channel[buffer_index]
571607
} else {
572608
0.
@@ -607,7 +643,7 @@ impl AudioProcessor for AudioBufferSourceRenderer {
607643
// ---------------------------------------------------------------
608644
// Slow track
609645
// ---------------------------------------------------------------
610-
if is_looping {
646+
if loop_ {
611647
if loop_start >= 0. && loop_end > 0. && loop_start < loop_end {
612648
actual_loop_start = loop_start;
613649
actual_loop_end = loop_end.min(buffer_duration);
@@ -639,19 +675,19 @@ impl AudioProcessor for AudioBufferSourceRenderer {
639675
if !self.render_state.started {
640676
self.offset += current_time - self.start_time;
641677

642-
if is_looping && computed_playback_rate >= 0. && self.offset >= actual_loop_end {
678+
if loop_ && computed_playback_rate >= 0. && self.offset >= actual_loop_end {
643679
self.offset = actual_loop_end;
644680
}
645681

646-
if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start {
682+
if loop_ && computed_playback_rate < 0. && self.offset < actual_loop_start {
647683
self.offset = actual_loop_start;
648684
}
649685

650686
self.render_state.buffer_time = self.offset;
651687
self.render_state.started = true;
652688
}
653689

654-
if is_looping {
690+
if loop_ {
655691
if !self.render_state.entered_loop {
656692
// playback began before or within loop, and playhead is now past loop start
657693
if self.offset < actual_loop_end
@@ -740,7 +776,7 @@ impl AudioProcessor for AudioBufferSourceRenderer {
740776

741777
fn onmessage(&mut self, msg: &mut dyn Any) {
742778
if let Some(control) = msg.downcast_ref::<ControlMessage>() {
743-
self.handle_control_message(control);
779+
self.handle_control_message(*control);
744780
return;
745781
};
746782

src/node/constant_source.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
1111
// dictionary ConstantSourceOptions {
1212
// float offset = 1;
1313
// };
14+
//
15+
// @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
1418
#[derive(Clone, Debug)]
1519
pub struct ConstantSourceOptions {
1620
pub offset: f32,

src/node/media_element_source.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ use crate::RENDER_QUANTUM_SIZE;
66
use super::{AudioNode, ChannelConfig, MediaStreamRenderer};
77

88
/// Options for constructing a [`MediaElementAudioSourceNode`]
9+
// dictionary MediaElementAudioSourceOptions {
10+
// required HTMLMediaElement mediaElement;
11+
// };
12+
//
13+
// @note - Does not extend AudioNodeOptions because AudioNodeOptions are
14+
// useless for source nodes as they instruct how to upmix the inputs.
15+
// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472
916
pub struct MediaElementAudioSourceOptions<'a> {
1017
pub media_element: &'a mut MediaElement,
1118
}

src/node/media_stream_source.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ use super::{AudioNode, ChannelConfig, MediaStreamRenderer};
99
// dictionary MediaStreamAudioSourceOptions {
1010
// required MediaStream mediaStream;
1111
// };
12+
//
13+
// @note - Does not extend AudioNodeOptions because AudioNodeOptions are
14+
// useless for source nodes as they instruct how to upmix the inputs.
15+
// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472
1216
pub struct MediaStreamAudioSourceOptions<'a> {
1317
pub media_stream: &'a MediaStream,
1418
}

src/node/media_stream_track_source.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ use crate::RENDER_QUANTUM_SIZE;
66
use super::{AudioNode, ChannelConfig, MediaStreamRenderer};
77

88
/// Options for constructing a [`MediaStreamTrackAudioSourceNode`]
9-
// dictionary MediaStreamAudioSourceOptions {
10-
// required MediaStream mediaStream;
9+
// dictionary MediaStreamTrackAudioSourceOptions {
10+
// required MediaStreamTrack mediaStreamTrack;
1111
// };
12+
//
13+
// @note - Does not extend AudioNodeOptions because AudioNodeOptions are
14+
// useless for source nodes as they instruct how to upmix the inputs.
15+
// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472
1216
pub struct MediaStreamTrackAudioSourceOptions<'a> {
1317
pub media_stream_track: &'a MediaStreamTrack,
1418
}

0 commit comments

Comments
 (0)