From 3ad0dd2ddc678c5fc2e5a1aa37744a2f0bbcec56 Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 4 Sep 2024 16:49:08 +0200 Subject: [PATCH 1/6] fix: add failing test and fix loop edge case --- src/node/audio_buffer_source.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index 35bd3500..3bd469c8 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -549,6 +549,7 @@ impl AudioProcessor for AudioBufferSourceRenderer { } else { buffer.length() }; + // in case of a loop point in the middle of the block, this value // will be used to recompute `buffer_time` according // to the actual loop point. @@ -570,7 +571,7 @@ impl AudioProcessor for AudioBufferSourceRenderer { *o = if buffer_index < end_index { buffer_channel[buffer_index] } else { - if is_looping && buffer_index == end_index { + if is_looping && buffer_index >= end_index { loop_point_index = Some(index); // reset values for the rest of the block start_index = 0; @@ -1438,4 +1439,33 @@ mod tests { assert_float_eq!(channel[..], expected[..], abs_all <= 0.); } + + #[test] + fn test_loop_no_restart_fast_track() { + let sample_rate = 48_000.; + let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate); + + let mut buffer = context.create_buffer(1, 1, sample_rate); + let data = vec![1.; 1]; + buffer.copy_to_channel(&data, 0); + + let mut src = context.create_buffer_source(); + src.connect(&context.destination()); + src.set_buffer(buffer); + // play in fast track + src.start_at(0.); + + // set src loop to true after it should have stopped + context.suspend_sync(128. / sample_rate as f64, move |_| { + src.set_loop(true); + }); + + let result = context.start_rendering_sync(); + let channel = result.get_channel_data(0); + + let mut expected = [0.; 256]; + expected[0] = 1.; + + assert_float_eq!(channel[..], expected[..], abs_all <= 0.); + } } From 9ddb32118a55759d58f65c6ccbb414ff8c18451f Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 4 Sep 2024 18:29:17 +0200 Subject: [PATCH 2/6] fix: properly mark the rendering as ended so that it cannot be restarted by setting loop to true cf. https://github.com/ircam-ismm/node-web-audio-api/issues/135 --- src/node/audio_buffer_source.rs | 358 +++++++++++++++++++------------- 1 file changed, 216 insertions(+), 142 deletions(-) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index 3bd469c8..191875a6 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -216,7 +216,8 @@ impl AudioBufferSourceNode { playback_rate: pr_proc, loop_state, render_state: AudioBufferRendererState::default(), - ended_triggered: false, + ended: false, + onended_triggered: false, }; let node = Self { @@ -384,7 +385,8 @@ struct AudioBufferSourceRenderer { playback_rate: AudioParamId, loop_state: LoopState, render_state: AudioBufferRendererState, - ended_triggered: bool, + ended: bool, + onended_triggered: bool, } impl AudioBufferSourceRenderer { @@ -414,6 +416,17 @@ impl AudioProcessor for AudioBufferSourceRenderer { // Single output node let output = &mut outputs[0]; + if self.ended { + // make sure ended event s sent only once + if !self.onended_triggered { + scope.send_ended_event(); + self.onended_triggered = true; + } + + output.make_silent(); + return false; + } + let sample_rate = scope.sample_rate as f64; let dt = 1. / sample_rate; let block_duration = dt * RENDER_QUANTUM_SIZE as f64; @@ -464,28 +477,6 @@ impl AudioProcessor for AudioBufferSourceRenderer { // The render state has to be updated before leaving this method! let mut buffer_time = self.render_state.buffer_time.load(Ordering::Relaxed); - // The output must change to a single channel of silence at the beginning of a render - // quantum after the time at which any one of the following conditions holds: - // 1. the stop time has been reached. - // 2. the duration has been reached. - // 3. the end of the buffer has been reached. - if scope.current_time >= self.stop_time - || self.render_state.buffer_time_elapsed >= self.duration - || !is_looping - && (computed_playback_rate > 0. && buffer_time >= buffer_duration - || computed_playback_rate < 0. && buffer_time < 0.) - { - output.make_silent(); // also converts to mono - - // @note: we need this check because this is called a until the program - // ends, such as if the node was never removed from the graph - if !self.ended_triggered { - scope.send_ended_event(); - self.ended_triggered = true; - } - return false; - } - output.set_number_of_channels(buffer.number_of_channels()); // go through the algorithm described in the spec @@ -526,10 +517,10 @@ impl AudioProcessor for AudioBufferSourceRenderer { self.render_state.is_aligned = false; } - // --------------------------------------------------------------- - // Fast track - // --------------------------------------------------------------- if self.render_state.is_aligned { + // --------------------------------------------------------------- + // Fast track + // --------------------------------------------------------------- if self.start_time == block_time { self.render_state.started = true; } @@ -610,142 +601,150 @@ impl AudioProcessor for AudioBufferSourceRenderer { buffer_time += block_duration; } - // update render state - self.render_state - .buffer_time - .store(buffer_time, Ordering::Relaxed); self.render_state.buffer_time_elapsed += block_duration; - - return true; - } - - // --------------------------------------------------------------- - // Slow track - // --------------------------------------------------------------- - if is_looping { - if loop_start >= 0. && loop_end > 0. && loop_start < loop_end { - actual_loop_start = loop_start; - actual_loop_end = loop_end.min(buffer_duration); + } else { + // --------------------------------------------------------------- + // Slow track + // --------------------------------------------------------------- + if is_looping { + if loop_start >= 0. && loop_end > 0. && loop_start < loop_end { + actual_loop_start = loop_start; + actual_loop_end = loop_end.min(buffer_duration); + } else { + actual_loop_start = 0.; + actual_loop_end = buffer_duration; + } } else { - actual_loop_start = 0.; - actual_loop_end = buffer_duration; + self.render_state.entered_loop = false; } - } else { - self.render_state.entered_loop = false; - } - // internal buffer used to store playback infos to compute the samples - // according to the source buffer. (prev_sample_index, k) - let mut playback_infos = [None; RENDER_QUANTUM_SIZE]; + // internal buffer used to store playback infos to compute the samples + // according to the source buffer. (prev_sample_index, k) + let mut playback_infos = [None; RENDER_QUANTUM_SIZE]; - // compute position for each sample and store into `self.positions` - for (i, playback_info) in playback_infos.iter_mut().enumerate() { - let current_time = block_time + i as f64 * dt; + // compute position for each sample and store into `self.positions` + for (i, playback_info) in playback_infos.iter_mut().enumerate() { + let current_time = block_time + i as f64 * dt; - if current_time < self.start_time - || current_time >= self.stop_time - || self.render_state.buffer_time_elapsed >= self.duration - { - continue; // nothing more to do for this sample - } + if current_time < self.start_time + || current_time >= self.stop_time + || self.render_state.buffer_time_elapsed >= self.duration + { + continue; // nothing more to do for this sample + } - // we have now reached start time - if !self.render_state.started { - // handle that start time may be between last sample and this one - self.offset += current_time - self.start_time; + // we have now reached start time + if !self.render_state.started { + // handle that start time may be between last sample and this one + self.offset += current_time - self.start_time; - if is_looping && computed_playback_rate >= 0. && self.offset >= actual_loop_end { - self.offset = actual_loop_end; - } + if is_looping && computed_playback_rate >= 0. && self.offset >= actual_loop_end { + self.offset = actual_loop_end; + } - if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start { - self.offset = actual_loop_start; + if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start { + self.offset = actual_loop_start; + } + + buffer_time = self.offset; + self.render_state.started = true; } - buffer_time = self.offset; - self.render_state.started = true; - } + if is_looping { + if !self.render_state.entered_loop { + // playback began before or within loop, and playhead is now past loop start + if self.offset < actual_loop_end && buffer_time >= actual_loop_start { + self.render_state.entered_loop = true; + } - if is_looping { - if !self.render_state.entered_loop { - // playback began before or within loop, and playhead is now past loop start - if self.offset < actual_loop_end && buffer_time >= actual_loop_start { - self.render_state.entered_loop = true; + // playback began after loop, and playhead is now prior to the loop end + // @note - only possible when playback_rate < 0 (?) + if self.offset >= actual_loop_end && buffer_time < actual_loop_end { + self.render_state.entered_loop = true; + } } - // playback began after loop, and playhead is now prior to the loop end - // @note - only possible when playback_rate < 0 (?) - if self.offset >= actual_loop_end && buffer_time < actual_loop_end { - self.render_state.entered_loop = true; + // check loop boundaries + if self.render_state.entered_loop { + while buffer_time >= actual_loop_end { + buffer_time -= actual_loop_end - actual_loop_start; + } + + while buffer_time < actual_loop_start { + buffer_time += actual_loop_end - actual_loop_start; + } } } - // check loop boundaries - if self.render_state.entered_loop { - while buffer_time >= actual_loop_end { - buffer_time -= actual_loop_end - actual_loop_start; - } + if buffer_time >= 0. && buffer_time < buffer_duration { + let position = buffer_time * sampling_ratio; + let playhead = position * sample_rate; + let playhead_floored = playhead.floor(); + let prev_frame_index = playhead_floored as usize; // can't be < 0. + let k = (playhead - playhead_floored) as f32; - while buffer_time < actual_loop_start { - buffer_time += actual_loop_end - actual_loop_start; - } + *playback_info = Some(PlaybackInfo { + prev_frame_index, + k, + }); } - } - if buffer_time >= 0. && buffer_time < buffer_duration { - let position = buffer_time * sampling_ratio; - let playhead = position * sample_rate; - let playhead_floored = playhead.floor(); - let prev_frame_index = playhead_floored as usize; // can't be < 0. - let k = (playhead - playhead_floored) as f32; + let time_incr = dt * computed_playback_rate; + buffer_time += time_incr; + self.render_state.buffer_time_elapsed += time_incr; + } - *playback_info = Some(PlaybackInfo { - prev_frame_index, - k, + // fill output according to computed positions + buffer + .channels() + .iter() + .zip(output.channels_mut().iter_mut()) + .for_each(|(buffer_channel, output_channel)| { + let buffer_channel = buffer_channel.as_slice(); + + playback_infos + .iter() + .zip(output_channel.iter_mut()) + .for_each(|(playhead, o)| { + *o = match playhead { + Some(PlaybackInfo { + prev_frame_index, + k, + }) => { + // `prev_frame_index` cannot be out of bounds + let prev_sample = buffer_channel[*prev_frame_index]; + let next_sample = match buffer_channel.get(prev_frame_index + 1) { + Some(val) => *val, + None => 0., + }; + + (1. - k).mul_add(prev_sample, k * next_sample) + } + None => 0., + }; + }); }); - } - let time_incr = dt * computed_playback_rate; - buffer_time += time_incr; - self.render_state.buffer_time_elapsed += time_incr; } - // fill output according to computed positions - buffer - .channels() - .iter() - .zip(output.channels_mut().iter_mut()) - .for_each(|(buffer_channel, output_channel)| { - let buffer_channel = buffer_channel.as_slice(); - - playback_infos - .iter() - .zip(output_channel.iter_mut()) - .for_each(|(playhead, o)| { - *o = match playhead { - Some(PlaybackInfo { - prev_frame_index, - k, - }) => { - // `prev_frame_index` cannot be out of bounds - let prev_sample = buffer_channel[*prev_frame_index]; - let next_sample = match buffer_channel.get(prev_frame_index + 1) { - Some(val) => *val, - None => 0., - }; - - (1. - k).mul_add(prev_sample, k * next_sample) - } - None => 0., - }; - }); - }); - - // update render state + // Update render state self.render_state .buffer_time .store(buffer_time, Ordering::Relaxed); + // The buffer has ended within this block, if one of the following conditions holds: + // 1. the stop time has been reached. + // 2. the duration has been reached. + // 3. the end of the buffer has been reached. + if next_block_time >= self.stop_time + || self.render_state.buffer_time_elapsed >= self.duration + || !is_looping + && (computed_playback_rate > 0. && buffer_time >= buffer_duration + || computed_playback_rate < 0. && buffer_time < 0.) + { + self.ended = true; + } + true } @@ -774,9 +773,9 @@ impl AudioProcessor for AudioBufferSourceRenderer { } fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) { - if !self.ended_triggered && scope.current_time >= self.start_time { + if !self.ended && scope.current_time >= self.start_time { scope.send_ended_event(); - self.ended_triggered = true; + self.ended = true; } } } @@ -1441,9 +1440,10 @@ mod tests { } #[test] - fn test_loop_no_restart_fast_track() { + fn test_loop_no_restart_suspend() { let sample_rate = 48_000.; - let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate); + let result_size = RENDER_QUANTUM_SIZE * 2; + let mut context = OfflineAudioContext::new(1, result_size, sample_rate); let mut buffer = context.create_buffer(1, 1, sample_rate); let data = vec![1.; 1]; @@ -1452,20 +1452,94 @@ mod tests { let mut src = context.create_buffer_source(); src.connect(&context.destination()); src.set_buffer(buffer); - // play in fast track src.start_at(0.); - // set src loop to true after it should have stopped - context.suspend_sync(128. / sample_rate as f64, move |_| { + context.suspend_sync(RENDER_QUANTUM_SIZE as f64 / sample_rate as f64, move |_| { src.set_loop(true); }); let result = context.start_rendering_sync(); let channel = result.get_channel_data(0); - let mut expected = [0.; 256]; + let mut expected = vec![0.; result_size]; + expected[0] = 1.; + + assert_float_eq!(channel[..], expected[..], abs_all <= 0.); + } + + #[test] + fn test_loop_no_restart_onended_fast_track() { + use std::sync::Mutex; + + let sample_rate = 48_000.; + // ended event is send on second render quantum, let's take a few more to be sure + let result_size = RENDER_QUANTUM_SIZE * 4; + let mut context = OfflineAudioContext::new(1, result_size, sample_rate); + + let mut buffer = context.create_buffer(1, 1, sample_rate); + let data = vec![1.; 1]; + buffer.copy_to_channel(&data, 0); + + let src = Arc::new(Mutex::new(context.create_buffer_source())); + let mut guard = src.lock().unwrap(); + guard.connect(&context.destination()); + guard.set_buffer(buffer); + // play in fast track + guard.start_at(0.); + + let clone = src.clone(); + + guard.set_onended(move |_| { + let mut guard = clone.lock().unwrap(); + guard.set_loop(true); + }); + + drop(guard); + + let result = context.start_rendering_sync(); + let channel = result.get_channel_data(0); + + let mut expected = vec![0.; result_size]; expected[0] = 1.; assert_float_eq!(channel[..], expected[..], abs_all <= 0.); } + + #[test] + fn test_loop_no_restart_onended_slow_track() { + use std::sync::Mutex; + + let sample_rate = 48_000.; + // ended event is send on second render quantum, let's take a few more to be sure + let result_size = RENDER_QUANTUM_SIZE * 4; + let mut context = OfflineAudioContext::new(1, result_size, sample_rate); + + let mut buffer = context.create_buffer(1, 1, sample_rate); + let data = vec![1.; 1]; + buffer.copy_to_channel(&data, 0); + + let src = Arc::new(Mutex::new(context.create_buffer_source())); + let mut guard = src.lock().unwrap(); + guard.connect(&context.destination()); + guard.set_buffer(buffer); + // play in slow track + guard.start_at(1. / sample_rate as f64); + + let clone = src.clone(); + + guard.set_onended(move |_| { + let mut guard = clone.lock().unwrap(); + guard.set_loop(true); + }); + + drop(guard); + + let result = context.start_rendering_sync(); + let channel = result.get_channel_data(0); + + let mut expected = vec![0.; result_size]; + expected[1] = 1.; + + assert_float_eq!(channel[..], expected[..], abs_all <= 0.); + } } From 23545cff4cb08bd5e76f8c4f1497c920dd72f698 Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 4 Sep 2024 18:56:18 +0200 Subject: [PATCH 3/6] tests: add test for offset larger than buffer duration --- src/node/audio_buffer_source.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index 191875a6..f4090140 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -1321,6 +1321,24 @@ mod tests { assert_float_eq!(channel[..], expected[..], abs_all <= 0.); } + #[test] + fn test_offset_larger_than_buffer_duration() { + let sample_rate = 48_000.; + let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate); + let mut buffer = context.create_buffer(1, 13, sample_rate); + buffer.copy_to_channel(&[1.; 13], 0); + + let mut src = context.create_buffer_source(); + src.set_buffer(buffer); + src.start_at_with_offset(0., 64. / sample_rate as f64); // offset larger than buffer size + + let result = context.start_rendering_sync(); + let channel = result.get_channel_data(0); + + let expected = [0.; RENDER_QUANTUM_SIZE]; + assert_float_eq!(channel[..], expected[..], abs_all <= 0.); + } + #[test] fn test_fast_track_loop_mono() { let sample_rate = 48_000.; From 4ed8ad35553a76769b7f3d9748d09147d244e0e9 Mon Sep 17 00:00:00 2001 From: b-ma Date: Thu, 5 Sep 2024 08:08:49 +0200 Subject: [PATCH 4/6] tests: add test for ended event before drop --- src/node/audio_buffer_source.rs | 57 ++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index f4090140..33572cfa 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -216,7 +216,6 @@ impl AudioBufferSourceNode { playback_rate: pr_proc, loop_state, render_state: AudioBufferRendererState::default(), - ended: false, onended_triggered: false, }; @@ -361,6 +360,7 @@ struct AudioBufferRendererState { entered_loop: bool, buffer_time_elapsed: f64, is_aligned: bool, + ended: bool, } impl Default for AudioBufferRendererState { @@ -371,6 +371,7 @@ impl Default for AudioBufferRendererState { entered_loop: false, buffer_time_elapsed: 0., is_aligned: false, + ended: false, } } } @@ -385,7 +386,6 @@ struct AudioBufferSourceRenderer { playback_rate: AudioParamId, loop_state: LoopState, render_state: AudioBufferRendererState, - ended: bool, onended_triggered: bool, } @@ -416,7 +416,7 @@ impl AudioProcessor for AudioBufferSourceRenderer { // Single output node let output = &mut outputs[0]; - if self.ended { + if self.render_state.ended { // make sure ended event s sent only once if !self.onended_triggered { scope.send_ended_event(); @@ -742,7 +742,7 @@ impl AudioProcessor for AudioBufferSourceRenderer { && (computed_playback_rate > 0. && buffer_time >= buffer_duration || computed_playback_rate < 0. && buffer_time < 0.) { - self.ended = true; + self.render_state.ended = true; } true @@ -773,9 +773,10 @@ impl AudioProcessor for AudioBufferSourceRenderer { } fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) { - if !self.ended && scope.current_time >= self.start_time { + if !self.onended_triggered && scope.current_time >= self.start_time { scope.send_ended_event(); - self.ended = true; + self.render_state.ended = true; + self.onended_triggered = true; } } } @@ -784,6 +785,8 @@ impl AudioProcessor for AudioBufferSourceRenderer { mod tests { use float_eq::assert_float_eq; use std::f32::consts::PI; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::{Arc, Mutex}; use crate::context::{BaseAudioContext, OfflineAudioContext}; use crate::RENDER_QUANTUM_SIZE; @@ -1487,8 +1490,6 @@ mod tests { #[test] fn test_loop_no_restart_onended_fast_track() { - use std::sync::Mutex; - let sample_rate = 48_000.; // ended event is send on second render quantum, let's take a few more to be sure let result_size = RENDER_QUANTUM_SIZE * 4; @@ -1505,7 +1506,7 @@ mod tests { // play in fast track guard.start_at(0.); - let clone = src.clone(); + let clone = Arc::clone(&src); guard.set_onended(move |_| { let mut guard = clone.lock().unwrap(); @@ -1525,8 +1526,6 @@ mod tests { #[test] fn test_loop_no_restart_onended_slow_track() { - use std::sync::Mutex; - let sample_rate = 48_000.; // ended event is send on second render quantum, let's take a few more to be sure let result_size = RENDER_QUANTUM_SIZE * 4; @@ -1543,7 +1542,7 @@ mod tests { // play in slow track guard.start_at(1. / sample_rate as f64); - let clone = src.clone(); + let clone = Arc::clone(&src); guard.set_onended(move |_| { let mut guard = clone.lock().unwrap(); @@ -1560,4 +1559,38 @@ mod tests { assert_float_eq!(channel[..], expected[..], abs_all <= 0.); } + + #[test] + fn test_onended_before_drop() { + let sample_rate = 48_000.; + let result_size = RENDER_QUANTUM_SIZE; + let mut context = OfflineAudioContext::new(1, result_size, sample_rate); + + let mut buffer = context.create_buffer(1, 1, sample_rate); + let data = vec![1.; 1]; + buffer.copy_to_channel(&data, 0); + + let mut src = context.create_buffer_source(); + src.connect(&context.destination()); + src.set_buffer(buffer); + src.start(); + + let onended_called = Arc::new(AtomicBool::new(false)); + let onended_called_clone = Arc::clone(&onended_called); + + src.set_onended(move |_| { + onended_called_clone.store(true, Ordering::SeqCst); + }); + + let result = context.start_rendering_sync(); + let channel = result.get_channel_data(0); + + let mut expected = vec![0.; result_size]; + expected[0] = 1.; + + // buffer has been rendered + assert_float_eq!(channel[..], expected[..], abs_all <= 0.); + // ended event has been trigerred + assert_eq!(onended_called.load(Ordering::SeqCst), true); + } } From 685cfdfb511012b043d7343d0a75286719d85274 Mon Sep 17 00:00:00 2001 From: b-ma Date: Thu, 5 Sep 2024 08:47:36 +0200 Subject: [PATCH 5/6] fix: send ended event right now and not in next render quantum --- src/node/audio_buffer_source.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index 33572cfa..190a19ef 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -216,7 +216,6 @@ impl AudioBufferSourceNode { playback_rate: pr_proc, loop_state, render_state: AudioBufferRendererState::default(), - onended_triggered: false, }; let node = Self { @@ -386,7 +385,6 @@ struct AudioBufferSourceRenderer { playback_rate: AudioParamId, loop_state: LoopState, render_state: AudioBufferRendererState, - onended_triggered: bool, } impl AudioBufferSourceRenderer { @@ -417,12 +415,6 @@ impl AudioProcessor for AudioBufferSourceRenderer { let output = &mut outputs[0]; if self.render_state.ended { - // make sure ended event s sent only once - if !self.onended_triggered { - scope.send_ended_event(); - self.onended_triggered = true; - } - output.make_silent(); return false; } @@ -638,11 +630,13 @@ impl AudioProcessor for AudioBufferSourceRenderer { // handle that start time may be between last sample and this one self.offset += current_time - self.start_time; - if is_looping && computed_playback_rate >= 0. && self.offset >= actual_loop_end { + if is_looping && computed_playback_rate >= 0. && self.offset >= actual_loop_end + { self.offset = actual_loop_end; } - if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start { + if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start + { self.offset = actual_loop_start; } @@ -713,7 +707,8 @@ impl AudioProcessor for AudioBufferSourceRenderer { }) => { // `prev_frame_index` cannot be out of bounds let prev_sample = buffer_channel[*prev_frame_index]; - let next_sample = match buffer_channel.get(prev_frame_index + 1) { + let next_sample = match buffer_channel.get(prev_frame_index + 1) + { Some(val) => *val, None => 0., }; @@ -724,7 +719,6 @@ impl AudioProcessor for AudioBufferSourceRenderer { }; }); }); - } // Update render state @@ -743,6 +737,7 @@ impl AudioProcessor for AudioBufferSourceRenderer { || computed_playback_rate < 0. && buffer_time < 0.) { self.render_state.ended = true; + scope.send_ended_event(); } true @@ -773,10 +768,9 @@ impl AudioProcessor for AudioBufferSourceRenderer { } fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) { - if !self.onended_triggered && scope.current_time >= self.start_time { + if !self.render_state.ended && scope.current_time >= self.start_time { scope.send_ended_event(); self.render_state.ended = true; - self.onended_triggered = true; } } } @@ -1565,8 +1559,8 @@ mod tests { let sample_rate = 48_000.; let result_size = RENDER_QUANTUM_SIZE; let mut context = OfflineAudioContext::new(1, result_size, sample_rate); - - let mut buffer = context.create_buffer(1, 1, sample_rate); + // buffer is larger than context output so it never goes into the ended check condition + let mut buffer = context.create_buffer(1, result_size * 2, sample_rate); let data = vec![1.; 1]; buffer.copy_to_channel(&data, 0); @@ -1588,9 +1582,7 @@ mod tests { let mut expected = vec![0.; result_size]; expected[0] = 1.; - // buffer has been rendered assert_float_eq!(channel[..], expected[..], abs_all <= 0.); - // ended event has been trigerred - assert_eq!(onended_called.load(Ordering::SeqCst), true); + assert!(onended_called.load(Ordering::SeqCst)); } } From be88f465e5128a7346340da5a8878f3d1d14f708 Mon Sep 17 00:00:00 2001 From: Otto Date: Thu, 5 Sep 2024 08:13:16 +0200 Subject: [PATCH 6/6] Improve AudioBufferSource test readability by defering Mutex wrap --- src/node/audio_buffer_source.rs | 36 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/node/audio_buffer_source.rs b/src/node/audio_buffer_source.rs index 190a19ef..c5c3a2a4 100644 --- a/src/node/audio_buffer_source.rs +++ b/src/node/audio_buffer_source.rs @@ -1493,22 +1493,18 @@ mod tests { let data = vec![1.; 1]; buffer.copy_to_channel(&data, 0); - let src = Arc::new(Mutex::new(context.create_buffer_source())); - let mut guard = src.lock().unwrap(); - guard.connect(&context.destination()); - guard.set_buffer(buffer); + let mut src = context.create_buffer_source(); + src.connect(&context.destination()); + src.set_buffer(buffer); // play in fast track - guard.start_at(0.); + src.start_at(0.); + let src = Arc::new(Mutex::new(src)); let clone = Arc::clone(&src); - - guard.set_onended(move |_| { - let mut guard = clone.lock().unwrap(); - guard.set_loop(true); + src.lock().unwrap().set_onended(move |_| { + clone.lock().unwrap().set_loop(true); }); - drop(guard); - let result = context.start_rendering_sync(); let channel = result.get_channel_data(0); @@ -1529,22 +1525,18 @@ mod tests { let data = vec![1.; 1]; buffer.copy_to_channel(&data, 0); - let src = Arc::new(Mutex::new(context.create_buffer_source())); - let mut guard = src.lock().unwrap(); - guard.connect(&context.destination()); - guard.set_buffer(buffer); + let mut src = context.create_buffer_source(); + src.connect(&context.destination()); + src.set_buffer(buffer); // play in slow track - guard.start_at(1. / sample_rate as f64); + src.start_at(1. / sample_rate as f64); + let src = Arc::new(Mutex::new(src)); let clone = Arc::clone(&src); - - guard.set_onended(move |_| { - let mut guard = clone.lock().unwrap(); - guard.set_loop(true); + src.lock().unwrap().set_onended(move |_| { + clone.lock().unwrap().set_loop(true); }); - drop(guard); - let result = context.start_rendering_sync(); let channel = result.get_channel_data(0);