Skip to content

Commit f9a25ab

Browse files
authored
Merge pull request #311 from orottier/feature/prevent-out-of-order-arrival
Prevent out-of-order arrival of audio node settings
2 parents f86ec03 + 6eaac05 commit f9a25ab

File tree

14 files changed

+718
-699
lines changed

14 files changed

+718
-699
lines changed

src/context/base.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub trait BaseAudioContext {
2525
/// Construct a new pair of [`AudioNode`] and [`AudioProcessor`]
2626
///
2727
/// The `AudioNode` lives in the user-facing control thread. The Processor is sent to the render thread.
28+
///
29+
/// Check the `examples/worklet.rs` file for example usage of this method.
2830
fn register<
2931
T: AudioNode,
3032
F: FnOnce(AudioContextRegistration) -> (T, Box<dyn AudioProcessor>),

src/context/concrete_base.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::context::{
77
use crate::events::{EventDispatch, EventHandler, EventLoop, EventType};
88
use crate::message::ControlMessage;
99
use crate::node::{AudioDestinationNode, AudioNode, ChannelConfig, ChannelConfigOptions};
10-
use crate::param::{AudioParam, AudioParamEvent};
10+
use crate::param::AudioParam;
1111
use crate::render::AudioProcessor;
1212
use crate::spatial::AudioListenerParams;
1313

@@ -267,6 +267,9 @@ impl ConcreteBaseAudioContext {
267267

268268
/// Returns the `AudioListener` which is used for 3D spatialization
269269
pub(super) fn listener(&self) -> AudioListener {
270+
// instruct to BaseContext to add the AudioListener if it has not already
271+
self.base().ensure_audio_listener_present();
272+
270273
let mut ids = LISTENER_PARAM_IDS.map(|i| AudioContextRegistration {
271274
id: AudioNodeId(i),
272275
context: self.clone(),
@@ -372,22 +375,6 @@ impl ConcreteBaseAudioContext {
372375
self.send_control_msg(message).unwrap();
373376
}
374377

375-
/// Pass an `AudioParam::AudioParamEvent` to the render thread
376-
///
377-
/// This clunky setup (wrapping a Sender in a message sent by another Sender) ensures
378-
/// automation events will never be handled out of order.
379-
pub(crate) fn pass_audio_param_event(
380-
&self,
381-
to: &Sender<AudioParamEvent>,
382-
event: AudioParamEvent,
383-
) {
384-
let message = ControlMessage::AudioParamEvent {
385-
to: to.clone(),
386-
event,
387-
};
388-
self.send_control_msg(message).unwrap();
389-
}
390-
391378
/// Connect the `AudioListener` to a `PannerNode`
392379
pub(crate) fn connect_listener_to_panner(&self, panner: AudioNodeId) {
393380
self.connect(LISTENER_NODE_ID, panner, 0, usize::MAX);

src/context/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ impl AudioContextRegistration {
100100
pub(crate) fn context(&self) -> &ConcreteBaseAudioContext {
101101
&self.context
102102
}
103+
104+
/// Send a message to the corresponding audio processor of this node
105+
///
106+
/// The message will be handled by
107+
/// [`AudioProcessor::onmessage`](crate::render::AudioProcessor::onmessage).
108+
pub fn post_message<M: std::any::Any + Send + 'static>(&self, msg: M) {
109+
let wrapped = crate::message::ControlMessage::NodeMessage {
110+
id: self.id,
111+
msg: Box::new(msg),
112+
};
113+
let _ = self.context.send_control_msg(wrapped);
114+
}
103115
}
104116

105117
impl Drop for AudioContextRegistration {

src/control.rs

Lines changed: 64 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,105 @@
1-
//! Scheduler and Controller for precise timings
2-
1+
use crate::AtomicF64;
32
use std::sync::atomic::{AtomicBool, Ordering};
43
use std::sync::Arc;
54

6-
use crate::AtomicF64;
7-
8-
/// Helper struct to start and stop audio streams
9-
#[derive(Clone, Debug)]
10-
pub(crate) struct Scheduler {
11-
inner: Arc<SchedulerInner>,
12-
}
13-
14-
#[derive(Debug)]
15-
struct SchedulerInner {
16-
start: AtomicF64,
17-
stop: AtomicF64,
5+
/// Helper struct to control playback settings
6+
pub(crate) struct Controller {
7+
/// Raw values
8+
values: ControllerValues,
9+
/// Values to be shared with another thread
10+
shared: Arc<ControllerShared>,
1811
}
1912

20-
// Uses the canonical ordering for handover of values, i.e. `Acquire` on load and `Release` on
21-
// store.
22-
impl Scheduler {
23-
/// Create a new Scheduler. Initial playback state will be: inactive.
24-
pub fn new() -> Self {
25-
let inner = SchedulerInner {
26-
start: AtomicF64::new(f64::MAX),
27-
stop: AtomicF64::new(f64::MAX),
28-
};
13+
impl Default for Controller {
14+
/// Create a new Controller. It will not be active
15+
fn default() -> Self {
16+
let values = ControllerValues::default();
2917
Self {
30-
inner: Arc::new(inner),
18+
values: values.clone(),
19+
shared: Arc::new(values.into()),
3120
}
3221
}
22+
}
3323

34-
/// Retrieve playback start value
35-
pub fn get_start_at(&self) -> f64 {
36-
self.inner.start.load(Ordering::Acquire)
24+
impl Controller {
25+
pub fn shared(&self) -> &Arc<ControllerShared> {
26+
&self.shared
3727
}
3828

39-
/// Schedule playback start at this timestamp
40-
pub fn start_at(&self, start: f64) {
41-
// todo panic on invalid values, or when already called
42-
self.inner.start.store(start, Ordering::Release);
29+
pub fn loop_(&self) -> bool {
30+
self.values.loop_
4331
}
4432

45-
/// Retrieve playback stop value
46-
pub fn get_stop_at(&self) -> f64 {
47-
self.inner.stop.load(Ordering::Acquire)
33+
pub fn set_loop(&mut self, loop_: bool) {
34+
self.values.loop_ = loop_;
35+
self.shared.loop_.store(loop_, Ordering::Release);
4836
}
4937

50-
/// Stop playback at this timestamp
51-
pub fn stop_at(&self, stop: f64) {
52-
// todo panic on invalid values, or when already called
53-
self.inner.stop.store(stop, Ordering::Release);
38+
pub fn loop_start(&self) -> f64 {
39+
self.values.loop_start
5440
}
55-
}
5641

57-
impl Default for Scheduler {
58-
fn default() -> Self {
59-
Self::new()
42+
pub fn set_loop_start(&mut self, loop_start: f64) {
43+
self.values.loop_start = loop_start;
44+
self.shared.loop_start.store(loop_start, Ordering::Release);
45+
}
46+
47+
pub fn loop_end(&self) -> f64 {
48+
self.values.loop_end
49+
}
50+
51+
pub fn set_loop_end(&mut self, loop_end: f64) {
52+
self.values.loop_end = loop_end;
53+
self.shared.loop_end.store(loop_end, Ordering::Release);
6054
}
6155
}
6256

63-
/// Helper struct to control audio streams
64-
#[derive(Clone, Debug)]
65-
pub(crate) struct Controller {
66-
inner: Arc<ControllerInner>,
57+
#[derive(Debug, Clone)]
58+
pub(crate) struct ControllerValues {
59+
pub loop_: bool,
60+
pub loop_start: f64,
61+
pub loop_end: f64,
62+
}
63+
64+
impl Default for ControllerValues {
65+
fn default() -> Self {
66+
Self {
67+
loop_: false,
68+
loop_start: 0.,
69+
loop_end: f64::MAX,
70+
}
71+
}
6772
}
6873

6974
#[derive(Debug)]
70-
struct ControllerInner {
71-
scheduler: Scheduler,
75+
pub(crate) struct ControllerShared {
7276
loop_: AtomicBool,
7377
loop_start: AtomicF64,
7478
loop_end: AtomicF64,
75-
offset: AtomicF64,
76-
duration: AtomicF64,
7779
}
7880

79-
// Uses the canonical ordering for handover of values, i.e. `Acquire` on load and `Release` on
80-
// store.
81-
impl Controller {
82-
/// Create a new Controller. It will not be active
83-
pub fn new() -> Self {
84-
let inner = ControllerInner {
85-
scheduler: Scheduler::new(),
86-
loop_: AtomicBool::new(false),
87-
loop_start: AtomicF64::new(0.),
88-
loop_end: AtomicF64::new(f64::MAX),
89-
offset: AtomicF64::new(f64::MAX),
90-
duration: AtomicF64::new(f64::MAX),
91-
};
92-
81+
impl From<ControllerValues> for ControllerShared {
82+
fn from(values: ControllerValues) -> Self {
9383
Self {
94-
inner: Arc::new(inner),
84+
loop_: AtomicBool::new(values.loop_),
85+
loop_start: AtomicF64::new(values.loop_start),
86+
loop_end: AtomicF64::new(values.loop_end),
9587
}
9688
}
89+
}
9790

98-
pub fn scheduler(&self) -> &Scheduler {
99-
&self.inner.scheduler
100-
}
101-
91+
// Uses the canonical ordering for handover of values, i.e. `Acquire` on load and `Release` on
92+
// store.
93+
impl ControllerShared {
10294
pub fn loop_(&self) -> bool {
103-
self.inner.loop_.load(Ordering::Acquire)
104-
}
105-
106-
pub fn set_loop(&self, loop_: bool) {
107-
self.inner.loop_.store(loop_, Ordering::Release);
95+
self.loop_.load(Ordering::Acquire)
10896
}
10997

11098
pub fn loop_start(&self) -> f64 {
111-
self.inner.loop_start.load(Ordering::Acquire)
112-
}
113-
114-
pub fn set_loop_start(&self, loop_start: f64) {
115-
self.inner.loop_start.store(loop_start, Ordering::Release);
99+
self.loop_start.load(Ordering::Acquire)
116100
}
117101

118102
pub fn loop_end(&self) -> f64 {
119-
self.inner.loop_end.load(Ordering::Acquire)
120-
}
121-
122-
pub fn set_loop_end(&self, loop_end: f64) {
123-
self.inner.loop_end.store(loop_end, Ordering::Release);
124-
}
125-
126-
pub fn offset(&self) -> f64 {
127-
self.inner.offset.load(Ordering::Acquire)
128-
}
129-
130-
pub fn set_offset(&self, offset: f64) {
131-
self.inner.offset.store(offset, Ordering::Release);
132-
}
133-
134-
pub fn duration(&self) -> f64 {
135-
self.inner.duration.load(Ordering::Acquire)
136-
}
137-
138-
pub fn set_duration(&self, duration: f64) {
139-
self.inner.duration.store(duration, Ordering::Release)
140-
}
141-
}
142-
143-
impl Default for Controller {
144-
fn default() -> Self {
145-
Self::new()
146-
}
147-
}
148-
149-
#[cfg(test)]
150-
mod tests {
151-
use super::*;
152-
153-
#[test]
154-
fn test_controller() {
155-
let controller = Controller::new();
156-
157-
assert!(!controller.loop_());
158-
assert!(controller.loop_start() == 0.);
159-
assert!(controller.loop_end() == f64::MAX);
103+
self.loop_end.load(Ordering::Acquire)
160104
}
161105
}

src/message.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
//! Message passing from control to render node
22
3+
use crate::context::AudioNodeId;
34
use crate::node::ChannelConfig;
4-
use crate::param::AudioParamEvent;
55
use crate::render::graph::Graph;
66
use crate::render::AudioProcessor;
77

8-
use crate::context::AudioNodeId;
98
use crossbeam_channel::Sender;
109

1110
/// Commands from the control thread to the render thread
@@ -36,12 +35,6 @@ pub(crate) enum ControlMessage {
3635
/// Notify the render thread this node is dropped in the control thread
3736
FreeWhenFinished { id: AudioNodeId },
3837

39-
/// Pass an AudioParam AutomationEvent to the relevant node
40-
AudioParamEvent {
41-
to: Sender<AudioParamEvent>,
42-
event: AudioParamEvent,
43-
},
44-
4538
/// Mark node as a cycle breaker (DelayNode only)
4639
MarkCycleBreaker { id: AudioNodeId },
4740

@@ -50,4 +43,10 @@ pub(crate) enum ControlMessage {
5043

5144
/// Start rendering with given audio graph
5245
Startup { graph: Graph },
46+
47+
/// Generic message to be handled by AudioProcessor
48+
NodeMessage {
49+
id: AudioNodeId,
50+
msg: Box<dyn std::any::Any + Send + 'static>,
51+
},
5352
}

0 commit comments

Comments
 (0)