Skip to content

Commit

Permalink
Merge pull request #417 from b-ma/fix/scheduled-source-node-start-stop
Browse files Browse the repository at this point in the history
Assert `AudioScheduledSourceNode` node start and stop arguments are valid
  • Loading branch information
orottier authored Jan 2, 2024
2 parents 585f8aa + b5613c8 commit 44ec8ef
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 31 deletions.
45 changes: 43 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ pub(crate) fn assert_valid_number_of_channels(number_of_channels: usize) {
}
}

/// Assert that the given channel number is valid according the number of channel
/// of an Audio asset (e.g. [`AudioBuffer`])
/// Assert that the given channel number is valid according to the number of channels
/// of an Audio asset (e.g. [`AudioBuffer`]).
///
/// # Panics
///
Expand All @@ -204,6 +204,29 @@ pub(crate) fn assert_valid_channel_number(channel_number: usize, number_of_chann
}
}

/// Assert that the given value number is a valid time information, i.e. greater
/// than or equal to zero and finite.
///
/// # Panics
///
/// This function will panic if:
/// - the given value is not finite and lower than zero
///
#[track_caller]
#[inline(always)]
pub(crate) fn assert_valid_time_value(value: f64) {
if !value.is_finite() {
panic!("TypeError - The provided time value is non-finite.");
}

if value < 0. {
panic!(
"RangeError - The provided time value ({:?}) cannot be negative",
value
);
}
}

pub(crate) trait AudioBufferIter: Iterator<Item = FallibleBuffer> + Send + 'static {}

impl<M: Iterator<Item = FallibleBuffer> + Send + 'static> AudioBufferIter for M {}
Expand Down Expand Up @@ -265,4 +288,22 @@ mod tests {
assert_valid_number_of_channels(1);
assert_valid_number_of_channels(32);
}

#[test]
#[should_panic]
fn test_invalid_time_value_non_finite() {
assert_valid_time_value(f64::NAN);
}

#[test]
#[should_panic]
fn test_invalid_time_value_negative() {
assert_valid_time_value(-1.);
}

#[test]
fn test_valid_time_value() {
assert_valid_time_value(0.);
assert_valid_time_value(1.);
}
}
25 changes: 16 additions & 9 deletions src/node/audio_buffer_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::buffer::AudioBuffer;
use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
use crate::{AtomicF64, RENDER_QUANTUM_SIZE};
use crate::{assert_valid_time_value, AtomicF64, RENDER_QUANTUM_SIZE};

use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};

Expand Down Expand Up @@ -147,6 +147,7 @@ impl AudioScheduledSourceNode for AudioBufferSourceNode {
}

fn stop_at(&mut self, when: f64) {
assert_valid_time_value(when);
assert!(
self.source_started,
"InvalidStateError cannot stop before start"
Expand Down Expand Up @@ -251,10 +252,14 @@ impl AudioBufferSourceNode {
///
/// Panics if the source was already started
pub fn start_at_with_offset_and_duration(&mut self, start: f64, offset: f64, duration: f64) {
assert_valid_time_value(start);
assert_valid_time_value(offset);
assert_valid_time_value(duration);
assert!(
!self.source_started,
"InvalidStateError: Cannot call `start` twice"
);

self.source_started = true;

let control = ControlMessage::StartWithOffsetAndDuration(start, offset, duration);
Expand Down Expand Up @@ -1014,23 +1019,25 @@ mod tests {
}

#[test]
fn test_schedule_in_the_past() {
fn test_start_in_the_past() {
let sample_rate = 48000.;
let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
let mut context = OfflineAudioContext::new(1, 2 * RENDER_QUANTUM_SIZE, sample_rate);

let mut dirac = context.create_buffer(1, 1, sample_rate);
dirac.copy_to_channel(&[1.], 0);

let mut src = context.create_buffer_source();
src.connect(&context.destination());
src.set_buffer(dirac);
src.start_at(-1.);
context.suspend_sync((128. / sample_rate).into(), |context| {
let mut src = context.create_buffer_source();
src.connect(&context.destination());
src.set_buffer(dirac);
src.start_at(0.);
});

let result = context.start_rendering_sync();
let channel = result.get_channel_data(0);

let mut expected = vec![0.; RENDER_QUANTUM_SIZE];
expected[0] = 1.;
let mut expected = vec![0.; 2 * RENDER_QUANTUM_SIZE];
expected[128] = 1.;

assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
}
Expand Down
18 changes: 12 additions & 6 deletions src/node/constant_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::any::Any;
use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
use crate::RENDER_QUANTUM_SIZE;
use crate::{assert_valid_time_value, RENDER_QUANTUM_SIZE};

use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};

Expand Down Expand Up @@ -100,6 +100,7 @@ impl AudioScheduledSourceNode for ConstantSourceNode {
}

fn start_at(&mut self, when: f64) {
assert_valid_time_value(when);
self.registration.post_message(Schedule::Start(when));
}

Expand All @@ -109,6 +110,7 @@ impl AudioScheduledSourceNode for ConstantSourceNode {
}

fn stop_at(&mut self, when: f64) {
assert_valid_time_value(when);
self.registration.post_message(Schedule::Stop(when));
}
}
Expand Down Expand Up @@ -268,16 +270,20 @@ mod tests {

#[test]
fn test_start_in_the_past() {
let mut context = OfflineAudioContext::new(1, 128, 48000.);
let sample_rate = 48000.;
let mut context = OfflineAudioContext::new(1, 2 * 128, sample_rate);

let mut src = context.create_constant_source();
src.connect(&context.destination());
src.start_at(-1.);
context.suspend_sync((128. / sample_rate).into(), |context| {
let mut src = context.create_constant_source();
src.connect(&context.destination());
src.start_at(0.);
});

let buffer = context.start_rendering_sync();
let channel = buffer.get_channel_data(0);

// 1rst block should be silence
assert_float_eq!(channel[0..128], vec![1.; 128][..], abs_all <= 0.);
assert_float_eq!(channel[0..128], vec![0.; 128][..], abs_all <= 0.);
assert_float_eq!(channel[128..], vec![1.; 128][..], abs_all <= 0.);
}
}
29 changes: 19 additions & 10 deletions src/node/oscillator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
use crate::PeriodicWave;
use crate::RENDER_QUANTUM_SIZE;
use crate::{assert_valid_time_value, RENDER_QUANTUM_SIZE};

use super::{
precomputed_sine_table, AudioNode, AudioScheduledSourceNode, ChannelConfig,
Expand Down Expand Up @@ -160,6 +160,7 @@ impl AudioScheduledSourceNode for OscillatorNode {
}

fn start_at(&mut self, when: f64) {
assert_valid_time_value(when);
self.registration.post_message(Schedule::Start(when));
}

Expand All @@ -169,6 +170,7 @@ impl AudioScheduledSourceNode for OscillatorNode {
}

fn stop_at(&mut self, when: f64) {
assert_valid_time_value(when);
self.registration.post_message(Schedule::Stop(when));
}
}
Expand Down Expand Up @@ -1147,15 +1149,18 @@ mod tests {
}

#[test]
fn osc_schedule_in_past() {
fn test_start_in_the_past() {
let freq = 8910.1;
let sample_rate = 44_100;

let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
let mut osc = context.create_oscillator();
osc.connect(&context.destination());
osc.frequency().set_value(freq);
osc.start_at(-1.);

context.suspend_sync(128. / sample_rate as f64, move |context| {
let mut osc = context.create_oscillator();
osc.connect(&context.destination());
osc.frequency().set_value(freq);
osc.start_at(0.);
});

let output = context.start_rendering_sync();
let result = output.get_channel_data(0);
Expand All @@ -1164,10 +1169,14 @@ mod tests {
let mut phase: f64 = 0.;
let phase_incr = freq as f64 / sample_rate as f64;

for _i in 0..sample_rate {
let sample = (phase * 2. * PI).sin();
expected.push(sample as f32);
phase += phase_incr;
for i in 0..sample_rate {
if i < 128 {
expected.push(0.);
} else {
let sample = (phase * 2. * PI).sin();
expected.push(sample as f32);
phase += phase_incr;
}
}

assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
Expand Down
4 changes: 2 additions & 2 deletions src/node/panner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ enum ControlMessage {
#[inline(always)]
fn assert_valid_channel_count(count: usize) {
if count > 2 {
panic!("NotSupportedError: PannerNode channel count cannot be greater than two");
panic!("NotSupportedError - PannerNode channel count cannot be greater than two");
}
}

Expand All @@ -189,7 +189,7 @@ fn assert_valid_channel_count(count: usize) {
#[inline(always)]
fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
if mode == ChannelCountMode::Max {
panic!("NotSupportedError: PannerNode channel count mode cannot be set to max");
panic!("NotSupportedError - PannerNode channel count mode cannot be set to max");
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/node/stereo_panner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Default for StereoPannerOptions {
#[inline(always)]
fn assert_valid_channel_count(count: usize) {
if count > 2 {
panic!("NotSupportedError: StereoPannerNode channel count cannot be greater than two");
panic!("NotSupportedError - StereoPannerNode channel count cannot be greater than two");
}
}

Expand All @@ -59,7 +59,7 @@ fn assert_valid_channel_count(count: usize) {
#[inline(always)]
fn assert_valid_channel_count_mode(mode: ChannelCountMode) {
if mode == ChannelCountMode::Max {
panic!("NotSupportedError: StereoPannerNode channel count mode cannot be set to max");
panic!("NotSupportedError - StereoPannerNode channel count mode cannot be set to max");
}
}

Expand Down

0 comments on commit 44ec8ef

Please sign in to comment.