Skip to content

Commit aa42777

Browse files
committed
make start time sticky to sample if in floating point error range + extrapolate sample after end of buffer in certain conditions
1 parent a6913d1 commit aa42777

File tree

1 file changed

+109
-80
lines changed

1 file changed

+109
-80
lines changed

src/node/audio_buffer_source.rs

Lines changed: 109 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -633,12 +633,10 @@ impl AudioProcessor for AudioBufferSourceRenderer {
633633
for (i, playback_info) in playback_infos.iter_mut().enumerate() {
634634
let current_time = block_time + i as f64 * dt;
635635

636-
// handle floating point errors due to start time computation
636+
// Sticky behavior to handle floating point errors due to start time computation
637637
// cf. test_subsample_buffer_stitching
638-
if !self.render_state.started {
639-
if almost::equal(current_time, self.start_time) {
640-
self.start_time = current_time;
641-
}
638+
if !self.render_state.started && almost::equal(current_time, self.start_time) {
639+
self.start_time = current_time;
642640
}
643641

644642
// Handle following cases:
@@ -655,7 +653,6 @@ impl AudioProcessor for AudioBufferSourceRenderer {
655653

656654
// we have now reached start time
657655
if !self.render_state.started {
658-
// println!("start, {}, {}, {}", current_time, self.start_time, (current_time - self.start_time).abs());
659656
let delta = current_time - self.start_time;
660657
// handle that start time may be between last sample and this one
661658
self.offset += delta;
@@ -745,8 +742,9 @@ impl AudioProcessor for AudioBufferSourceRenderer {
745742
let next_sample = match buffer_channel.get(prev_frame_index + 1)
746743
{
747744
Some(val) => *val as f64,
745+
// End of buffer
748746
None => {
749-
let sample = if is_looping {
747+
if is_looping {
750748
if playback_rate >= 0. {
751749
let start_playhead =
752750
actual_loop_start * sample_rate;
@@ -758,18 +756,31 @@ impl AudioProcessor for AudioBufferSourceRenderer {
758756
start_playhead as usize + 1
759757
};
760758

761-
buffer_channel[start_index]
759+
buffer_channel[start_index] as f64
762760
} else {
763761
let end_playhead =
764762
actual_loop_end * sample_rate;
765763
let end_index = end_playhead as usize;
766-
buffer_channel[end_index]
764+
buffer_channel[end_index] as f64
767765
}
768766
} else {
769-
0.
770-
};
771-
772-
sample as f64
767+
// Handle 2 edge cases:
768+
// 1. We are in a case where buffer time is below buffer
769+
// duration due to floating point errors, but where
770+
// prev_frame_index is last index and k is near 1. We can't
771+
// filter this case before, because it might break
772+
// loops logic.
773+
// 2. Buffer contains only one sample
774+
if almost::equal(*k, 1.) || *prev_frame_index == 0 {
775+
0.
776+
} else {
777+
// Extrapolate next sample using the last two known samples
778+
// cf. https://github.com/WebAudio/web-audio-api/issues/2032
779+
let prev_prev_sample =
780+
buffer_channel[*prev_frame_index - 1];
781+
2. * prev_sample - prev_prev_sample as f64
782+
}
783+
}
773784
}
774785
};
775786

@@ -1130,44 +1141,47 @@ mod tests {
11301141

11311142
#[test]
11321143
fn test_audio_buffer_resampling() {
1133-
[22_500, 38_000, 48_000, 96_000].iter().for_each(|sr| {
1134-
let base_sr = 44_100;
1135-
let mut context = OfflineAudioContext::new(1, base_sr, base_sr as f32);
1136-
1137-
// 1Hz sine at different sample rates
1138-
let buf_sr = *sr;
1139-
// safe cast for sample rate, see discussion at #113
1140-
let sample_rate = buf_sr as f32;
1141-
let mut buffer = context.create_buffer(1, buf_sr, sample_rate);
1142-
let mut sine = vec![];
1143-
1144-
for i in 0..buf_sr {
1145-
let phase = i as f32 / buf_sr as f32 * 2. * PI;
1146-
let sample = phase.sin();
1147-
sine.push(sample);
1148-
}
1144+
[22_500, 38_000, 43_800, 48_000, 96_000]
1145+
.iter()
1146+
.for_each(|sr| {
1147+
let freq = 1.;
1148+
let base_sr = 44_100;
1149+
let mut context = OfflineAudioContext::new(1, base_sr, base_sr as f32);
1150+
1151+
// 1Hz sine at different sample rates
1152+
let buf_sr = *sr;
1153+
// safe cast for sample rate, see discussion at #113
1154+
let sample_rate = buf_sr as f32;
1155+
let mut buffer = context.create_buffer(1, buf_sr, sample_rate);
1156+
let mut sine = vec![];
1157+
1158+
for i in 0..buf_sr {
1159+
let phase = freq * i as f32 / buf_sr as f32 * 2. * PI;
1160+
let sample = phase.sin();
1161+
sine.push(sample);
1162+
}
11491163

1150-
buffer.copy_to_channel(&sine[..], 0);
1164+
buffer.copy_to_channel(&sine[..], 0);
11511165

1152-
let mut src = context.create_buffer_source();
1153-
src.connect(&context.destination());
1154-
src.set_buffer(buffer);
1155-
src.start_at(0. / sample_rate as f64);
1166+
let mut src = context.create_buffer_source();
1167+
src.connect(&context.destination());
1168+
src.set_buffer(buffer);
1169+
src.start_at(0. / sample_rate as f64);
11561170

1157-
let result = context.start_rendering_sync();
1158-
let channel = result.get_channel_data(0);
1171+
let result = context.start_rendering_sync();
1172+
let channel = result.get_channel_data(0);
11591173

1160-
// 1Hz sine at audio context sample rate
1161-
let mut expected = vec![];
1174+
// 1Hz sine at audio context sample rate
1175+
let mut expected = vec![];
11621176

1163-
for i in 0..base_sr {
1164-
let phase = i as f32 / base_sr as f32 * 2. * PI;
1165-
let sample = phase.sin();
1166-
expected.push(sample);
1167-
}
1177+
for i in 0..base_sr {
1178+
let phase = freq * i as f32 / base_sr as f32 * 2. * PI;
1179+
let sample = phase.sin();
1180+
expected.push(sample);
1181+
}
11681182

1169-
assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1170-
});
1183+
assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1184+
});
11711185
}
11721186

11731187
#[test]
@@ -1273,7 +1287,7 @@ mod tests {
12731287
}
12741288

12751289
#[test]
1276-
fn test_end_of_file_slow_track() {
1290+
fn test_end_of_file_slow_track_1() {
12771291
let sample_rate = 48_000.;
12781292
let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate);
12791293

@@ -1828,46 +1842,61 @@ mod tests {
18281842
}
18291843

18301844
#[test]
1831-
// ported from wpt: the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html
1845+
// Ported from wpt: the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html
1846+
// Note that in wpt, results are tested against an oscillator node, which fails
1847+
// in the (44_100., 43_800., 3.8986e-3) condition for some (yet) unknown reason
18321848
fn test_subsample_buffer_stitching() {
1833-
let sample_rate = 44_100.;
1834-
let buffer_rate = 44_100.;
1835-
let buffer_length = 30;
1836-
let frequency = 440.;
1849+
[(44_100., 44_100., 9.0957e-5), (44_100., 43_800., 3.8986e-3)]
1850+
.iter()
1851+
.for_each(|(sample_rate, buffer_rate, error_threshold)| {
1852+
let sample_rate = *sample_rate;
1853+
let buffer_rate = *buffer_rate;
1854+
let buffer_length = 30;
1855+
let frequency = 440.;
1856+
1857+
// let length = sample_rate as usize;
1858+
let length = buffer_length * 15;
1859+
let mut context = OfflineAudioContext::new(2, length, sample_rate);
1860+
1861+
let mut wave_signal = vec![0.; context.length()];
1862+
let omega = 2. * PI / buffer_rate * frequency;
1863+
wave_signal.iter_mut().enumerate().for_each(|(i, s)| {
1864+
*s = (omega * i as f32).sin();
1865+
});
18371866

1838-
let length = buffer_length * 15;
1839-
let mut context = OfflineAudioContext::new(1, length, sample_rate);
1867+
// Slice the sine wave into many little buffers to be assigned to ABSNs
1868+
// that are started at the appropriate times to produce a final sine
1869+
// wave.
1870+
for k in (0..context.length()).step_by(buffer_length) {
1871+
let mut buffer = AudioBuffer::new(AudioBufferOptions {
1872+
number_of_channels: 1,
1873+
length: buffer_length,
1874+
sample_rate: buffer_rate,
1875+
});
1876+
buffer.copy_to_channel(&wave_signal[k..k + buffer_length], 0);
1877+
1878+
let mut src = AudioBufferSourceNode::new(
1879+
&context,
1880+
AudioBufferSourceOptions {
1881+
buffer: Some(buffer),
1882+
..Default::default()
1883+
},
1884+
);
1885+
src.connect(&context.destination());
1886+
src.start_at(k as f64 / buffer_rate as f64);
1887+
}
18401888

1841-
let mut wave_signal = vec![0.; context.length()];
1842-
let omega = 2. * PI / buffer_rate * frequency;
1889+
let mut expected = vec![0.; context.length()];
1890+
let omega = 2. * PI / sample_rate * frequency;
1891+
expected.iter_mut().enumerate().for_each(|(i, s)| {
1892+
*s = (omega * i as f32).sin();
1893+
});
18431894

1844-
wave_signal.iter_mut().enumerate().for_each(|(i, s)| {
1845-
*s = (omega * i as f32).sin();
1846-
});
1895+
let result = context.start_rendering_sync();
1896+
let actual = result.get_channel_data(0);
18471897

1848-
// Slice the sine wave into many little buffers to be assigned to ABSNs
1849-
// that are started at the appropriate times to produce a final sine
1850-
// wave.
1851-
for k in (0..context.length()).step_by(buffer_length) {
1852-
let mut buffer = AudioBuffer::new(AudioBufferOptions {
1853-
number_of_channels: 1,
1854-
length: buffer_length,
1855-
sample_rate,
1898+
assert_float_eq!(actual[..], expected[..], abs_all <= error_threshold);
18561899
});
1857-
buffer.copy_to_channel(&wave_signal[k..k + buffer_length], 0);
1858-
1859-
let mut src = AudioBufferSourceNode::new(&context, AudioBufferSourceOptions {
1860-
buffer: Some(buffer),
1861-
..Default::default()
1862-
});
1863-
src.connect(&context.destination());
1864-
src.start_at(k as f64 / buffer_rate as f64);
1865-
}
1866-
1867-
let result = context.start_rendering_sync();
1868-
let channel = result.get_channel_data(0);
1869-
1870-
assert_float_eq!(channel[..], wave_signal[..], abs_all <= 1e-9);
18711900
}
18721901

18731902
#[test]

0 commit comments

Comments
 (0)