Skip to content

Commit 2367afe

Browse files
authored
Merge pull request #440 from orottier/feature/assertions-in-nodes
Fix in ChannelMergerNode channel config & run assertions in constructor of nodes
2 parents a78691e + a0a81bf commit 2367afe

File tree

4 files changed

+236
-20
lines changed

4 files changed

+236
-20
lines changed

src/node/channel_merger.rs

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ use super::{
77
AudioNode, ChannelConfig, ChannelConfigOptions, ChannelCountMode, ChannelInterpretation,
88
};
99

10+
/// Assert that the channel count is valid for the ChannelMergerNode
11+
/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints>
12+
///
13+
/// # Panics
14+
///
15+
/// This function panics if given count is greater than 2
16+
///
17+
#[track_caller]
18+
#[inline(always)]
19+
fn assert_valid_channel_count(count: usize) {
20+
assert!(
21+
count == 1,
22+
"InvalidStateError - channel count of ChannelMergerNode must be equal to 1"
23+
);
24+
}
25+
26+
/// Assert that the channel count mode is valid for the ChannelMergerNode
27+
/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
28+
///
29+
/// # Panics
30+
///
31+
/// This function panics if the mode is not equal to Explicit
32+
///
33+
#[track_caller]
34+
#[inline(always)]
35+
fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
36+
assert!(
37+
mode == ChannelCountMode::Explicit,
38+
"InvalidStateError - channel count of ChannelMergerNode must be set to Explicit"
39+
);
40+
}
41+
1042
/// Options for constructing a [`ChannelMergerNode`]
1143
// dictionary ChannelMergerOptions : AudioNodeOptions {
1244
// unsigned long numberOfInputs = 6;
@@ -34,6 +66,7 @@ impl Default for ChannelMergerOptions {
3466
pub struct ChannelMergerNode {
3567
registration: AudioContextRegistration,
3668
channel_config: ChannelConfig,
69+
number_of_inputs: usize,
3770
}
3871

3972
impl AudioNode for ChannelMergerNode {
@@ -45,16 +78,18 @@ impl AudioNode for ChannelMergerNode {
4578
&self.channel_config
4679
}
4780

48-
fn set_channel_count(&self, _v: usize) {
49-
panic!("InvalidStateError - Cannot edit channel count of ChannelMergerNode")
81+
fn set_channel_count(&self, count: usize) {
82+
assert_valid_channel_count(count);
83+
self.channel_config.set_count(count);
5084
}
5185

52-
fn set_channel_count_mode(&self, _v: ChannelCountMode) {
53-
panic!("InvalidStateError - Cannot edit channel count mode of ChannelMergerNode")
86+
fn set_channel_count_mode(&self, mode: ChannelCountMode) {
87+
assert_valid_channel_count_mode(mode);
88+
self.channel_config.set_count_mode(mode);
5489
}
5590

5691
fn number_of_inputs(&self) -> usize {
57-
self.channel_count()
92+
self.number_of_inputs
5893
}
5994

6095
fn number_of_outputs(&self) -> usize {
@@ -63,14 +98,17 @@ impl AudioNode for ChannelMergerNode {
6398
}
6499

65100
impl ChannelMergerNode {
66-
pub fn new<C: BaseAudioContext>(context: &C, mut options: ChannelMergerOptions) -> Self {
101+
pub fn new<C: BaseAudioContext>(context: &C, options: ChannelMergerOptions) -> Self {
67102
context.register(move |registration| {
68103
crate::assert_valid_number_of_channels(options.number_of_inputs);
69-
options.channel_config.count = options.number_of_inputs;
104+
105+
assert_valid_channel_count(options.channel_config.count);
106+
assert_valid_channel_count_mode(options.channel_config.count_mode);
70107

71108
let node = ChannelMergerNode {
72109
registration,
73110
channel_config: options.channel_config.into(),
111+
number_of_inputs: options.number_of_inputs,
74112
};
75113

76114
let render = ChannelMergerRenderer {};
@@ -111,3 +149,78 @@ impl AudioProcessor for ChannelMergerRenderer {
111149
false
112150
}
113151
}
152+
153+
#[cfg(test)]
154+
mod tests {
155+
use crate::context::{BaseAudioContext, OfflineAudioContext};
156+
use crate::node::{AudioNode, AudioScheduledSourceNode};
157+
158+
use float_eq::assert_float_eq;
159+
160+
#[test]
161+
fn test_merge() {
162+
let sample_rate = 48000.;
163+
let mut context = OfflineAudioContext::new(2, 128, sample_rate);
164+
165+
let merger = context.create_channel_merger(2);
166+
merger.connect(&context.destination());
167+
168+
let mut src1 = context.create_constant_source();
169+
src1.offset().set_value(2.);
170+
src1.connect_at(&merger, 0, 0);
171+
src1.start();
172+
173+
let mut src2 = context.create_constant_source();
174+
src2.offset().set_value(3.);
175+
src2.connect_at(&merger, 0, 1);
176+
src2.start();
177+
178+
let buffer = context.start_rendering_sync();
179+
180+
let left = buffer.get_channel_data(0);
181+
assert_float_eq!(left, &[2.; 128][..], abs_all <= 0.);
182+
183+
let right = buffer.get_channel_data(1);
184+
assert_float_eq!(right, &[3.; 128][..], abs_all <= 0.);
185+
}
186+
187+
#[test]
188+
fn test_merge_disconnect() {
189+
let sample_rate = 48000.;
190+
let length = 4 * 128;
191+
let disconnect_at = length as f64 / sample_rate as f64 / 2.;
192+
let mut context = OfflineAudioContext::new(2, length, sample_rate);
193+
194+
let merger = context.create_channel_merger(2);
195+
merger.connect(&context.destination());
196+
197+
let mut src1 = context.create_constant_source();
198+
src1.offset().set_value(2.);
199+
src1.connect_at(&merger, 0, 0);
200+
src1.start();
201+
202+
let mut src2 = context.create_constant_source();
203+
src2.offset().set_value(3.);
204+
src2.connect_at(&merger, 0, 1);
205+
src2.start();
206+
207+
context.suspend_sync(disconnect_at, move |_| src2.disconnect());
208+
209+
let buffer = context.start_rendering_sync();
210+
211+
let left = buffer.get_channel_data(0);
212+
assert_float_eq!(left, &vec![2.; length][..], abs_all <= 0.);
213+
214+
let right = buffer.get_channel_data(1);
215+
assert_float_eq!(
216+
&right[0..length / 2],
217+
&vec![3.; length / 2][..],
218+
abs_all <= 0.
219+
);
220+
assert_float_eq!(
221+
&right[length / 2..],
222+
&vec![0.; length / 2][..],
223+
abs_all <= 0.
224+
);
225+
}
226+
}

src/node/channel_splitter.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ use super::{
77
AudioNode, ChannelConfig, ChannelConfigOptions, ChannelCountMode, ChannelInterpretation,
88
};
99

10+
/// Assert that the channel count mode is valid for the ChannelSplitterNode
11+
/// see <https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints>
12+
///
13+
/// # Panics
14+
///
15+
/// This function panics if the mode is not equal to Explicit
16+
///
17+
#[track_caller]
18+
#[inline(always)]
19+
fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
20+
assert!(
21+
mode == ChannelCountMode::Explicit,
22+
"InvalidStateError - channel count of ChannelSplitterNode must be set to Explicit"
23+
);
24+
}
25+
26+
/// Assert that the channel interpretation is valid for the ChannelSplitterNode
27+
/// see <https://webaudio.github.io/web-audio-api/#audionode-channelinterpretation-constraints>
28+
///
29+
/// # Panics
30+
///
31+
/// This function panics if the mode is not equal to Explicit
32+
///
33+
#[track_caller]
34+
#[inline(always)]
35+
fn assert_valid_channel_interpretation(interpretation: ChannelInterpretation) {
36+
assert!(
37+
interpretation == ChannelInterpretation::Discrete,
38+
"InvalidStateError - channel interpretation of ChannelSplitterNode must be set to Discrete"
39+
);
40+
}
41+
1042
/// Options for constructing a [`ChannelSplitterNode`]
1143
// dictionary ChannelSplitterOptions : AudioNodeOptions {
1244
// unsigned long numberOfOutputs = 6;
@@ -45,16 +77,22 @@ impl AudioNode for ChannelSplitterNode {
4577
&self.channel_config
4678
}
4779

48-
fn set_channel_count(&self, _v: usize) {
49-
panic!("InvalidStateError - Cannot edit channel count of ChannelSplitterNode")
80+
fn set_channel_count(&self, count: usize) {
81+
assert_eq!(
82+
count,
83+
self.channel_count(),
84+
"InvalidStateError - Cannot edit channel count of ChannelSplitterNode"
85+
);
5086
}
5187

52-
fn set_channel_count_mode(&self, _v: ChannelCountMode) {
53-
panic!("InvalidStateError - Cannot edit channel count mode of ChannelSplitterNode")
88+
fn set_channel_count_mode(&self, mode: ChannelCountMode) {
89+
assert_valid_channel_count_mode(mode);
90+
self.channel_config.set_count_mode(mode);
5491
}
5592

56-
fn set_channel_interpretation(&self, _v: ChannelInterpretation) {
57-
panic!("InvalidStateError - Cannot edit channel interpretation of ChannelSplitterNode")
93+
fn set_channel_interpretation(&self, interpretation: ChannelInterpretation) {
94+
assert_valid_channel_interpretation(interpretation);
95+
self.channel_config.set_interpretation(interpretation);
5896
}
5997

6098
fn number_of_inputs(&self) -> usize {
@@ -72,6 +110,9 @@ impl ChannelSplitterNode {
72110
crate::assert_valid_number_of_channels(options.number_of_outputs);
73111
options.channel_config.count = options.number_of_outputs;
74112

113+
assert_valid_channel_count_mode(options.channel_config.count_mode);
114+
assert_valid_channel_interpretation(options.channel_config.interpretation);
115+
75116
let node = ChannelSplitterNode {
76117
registration,
77118
channel_config: options.channel_config.into(),
@@ -119,3 +160,35 @@ impl AudioProcessor for ChannelSplitterRenderer {
119160
false
120161
}
121162
}
163+
164+
#[cfg(test)]
165+
mod tests {
166+
use crate::context::{BaseAudioContext, OfflineAudioContext};
167+
use crate::node::{AudioNode, AudioScheduledSourceNode};
168+
use crate::AudioBuffer;
169+
170+
use float_eq::assert_float_eq;
171+
172+
#[test]
173+
fn test_splitter() {
174+
let sample_rate = 48000.;
175+
let mut context = OfflineAudioContext::new(1, 128, sample_rate);
176+
177+
let splitter = context.create_channel_splitter(2);
178+
// connect the 2nd output to the destination
179+
splitter.connect_at(&context.destination(), 1, 0);
180+
181+
// create buffer with sample value 1. left, value -1. right
182+
let audio_buffer = AudioBuffer::from(vec![vec![1.], vec![-1.]], 48000.);
183+
let mut src = context.create_buffer_source();
184+
src.set_buffer(audio_buffer);
185+
src.set_loop(true);
186+
src.start();
187+
src.connect(&splitter);
188+
189+
let buffer = context.start_rendering_sync();
190+
191+
let mono = buffer.get_channel_data(0);
192+
assert_float_eq!(mono, &[-1.; 128][..], abs_all <= 0.);
193+
}
194+
}

src/node/destination.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,16 @@ impl AudioNode for AudioDestinationNode {
6666
self.channel_config.set_count(v);
6767
}
6868

69-
fn set_channel_count_mode(&self, _v: ChannelCountMode) {
69+
fn set_channel_count_mode(&self, v: ChannelCountMode) {
7070
// [spec] If the AudioDestinationNode is the destination node of an
7171
// OfflineAudioContext, then the channel count mode cannot be changed.
7272
// An InvalidStateError exception MUST be thrown for any attempt to change the value.
7373
assert!(
7474
!self.registration.context().offline(),
7575
"InvalidStateError - AudioDestinationNode has channel count mode constraints",
7676
);
77+
78+
self.channel_config.set_count_mode(v);
7779
}
7880
}
7981

src/node/panner.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ use super::{
1515
AudioNode, ChannelConfig, ChannelConfigOptions, ChannelCountMode, ChannelInterpretation,
1616
};
1717

18+
/// Assert that the given value number is a valid value for coneOuterGain
19+
///
20+
/// # Panics
21+
///
22+
/// This function will panic if:
23+
/// - the given value is not finite and lower than zero
24+
#[track_caller]
25+
#[inline(always)]
26+
#[allow(clippy::manual_range_contains)]
27+
pub(crate) fn assert_valid_cone_outer_gain(value: f64) {
28+
assert!(
29+
value >= 0. && value <= 1.,
30+
"InvalidStateError - coneOuterGain must be in the range [0, 1]"
31+
);
32+
}
33+
1834
/// Load the HRTF processor for the given sample_rate
1935
///
2036
/// The included data contains the impulse responses at 44100 Hertz, so it needs to be resampled
@@ -393,6 +409,22 @@ impl PannerNode {
393409
panning_model,
394410
} = options;
395411

412+
assert!(
413+
ref_distance >= 0.,
414+
"RangeError - refDistance cannot be negative"
415+
);
416+
assert!(
417+
max_distance > 0.,
418+
"RangeError - maxDistance must be positive"
419+
);
420+
assert!(
421+
rolloff_factor >= 0.,
422+
"RangeError - rolloffFactor cannot be negative"
423+
);
424+
assert_valid_cone_outer_gain(cone_outer_gain);
425+
assert_valid_channel_count(channel_config.count);
426+
assert_valid_channel_count_mode(channel_config.count_mode);
427+
396428
// position params
397429
let (param_px, render_px) = context.create_audio_param(PARAM_OPTS, &registration);
398430
let (param_py, render_py) = context.create_audio_param(PARAM_OPTS, &registration);
@@ -540,7 +572,7 @@ impl PannerNode {
540572
///
541573
/// Panics if the provided value is negative.
542574
pub fn set_max_distance(&mut self, value: f64) {
543-
assert!(value >= 0., "RangeError - maxDistance cannot be negative");
575+
assert!(value > 0., "RangeError - maxDistance must be positive");
544576
self.max_distance = value;
545577
self.registration
546578
.post_message(ControlMessage::MaxDistance(value));
@@ -591,12 +623,8 @@ impl PannerNode {
591623
/// # Panics
592624
///
593625
/// Panics if the provided value is not in the range [0, 1]
594-
#[allow(clippy::manual_range_contains)]
595626
pub fn set_cone_outer_gain(&mut self, value: f64) {
596-
assert!(
597-
value >= 0. && value <= 1.,
598-
"InvalidStateError - coneOuterGain must be in the range [0, 1]"
599-
);
627+
assert_valid_cone_outer_gain(value);
600628
self.cone_outer_gain = value;
601629
self.registration
602630
.post_message(ControlMessage::ConeOuterGain(value));

0 commit comments

Comments
 (0)