@@ -633,6 +633,12 @@ 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+ // Sticky behavior to handle floating point errors due to start time computation
637+ // cf. test_subsample_buffer_stitching
638+ if !self . render_state . started && almost:: equal ( current_time, self . start_time ) {
639+ self . start_time = current_time;
640+ }
641+
636642 // Handle following cases:
637643 // - we are before start time
638644 // - we are after stop time
@@ -736,8 +742,9 @@ impl AudioProcessor for AudioBufferSourceRenderer {
736742 let next_sample = match buffer_channel. get ( prev_frame_index + 1 )
737743 {
738744 Some ( val) => * val as f64 ,
745+ // End of buffer
739746 None => {
740- let sample = if is_looping {
747+ if is_looping {
741748 if playback_rate >= 0. {
742749 let start_playhead =
743750 actual_loop_start * sample_rate;
@@ -749,18 +756,31 @@ impl AudioProcessor for AudioBufferSourceRenderer {
749756 start_playhead as usize + 1
750757 } ;
751758
752- buffer_channel[ start_index]
759+ buffer_channel[ start_index] as f64
753760 } else {
754761 let end_playhead =
755762 actual_loop_end * sample_rate;
756763 let end_index = end_playhead as usize ;
757- buffer_channel[ end_index]
764+ buffer_channel[ end_index] as f64
758765 }
759766 } else {
760- 0.
761- } ;
762-
763- 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+ }
764784 }
765785 } ;
766786
@@ -835,6 +855,7 @@ mod tests {
835855 use std:: sync:: { Arc , Mutex } ;
836856
837857 use crate :: context:: { BaseAudioContext , OfflineAudioContext } ;
858+ use crate :: AudioBufferOptions ;
838859 use crate :: RENDER_QUANTUM_SIZE ;
839860
840861 use super :: * ;
@@ -1120,44 +1141,47 @@ mod tests {
11201141
11211142 #[ test]
11221143 fn test_audio_buffer_resampling ( ) {
1123- [ 22_500 , 38_000 , 48_000 , 96_000 ] . iter ( ) . for_each ( |sr| {
1124- let base_sr = 44_100 ;
1125- let mut context = OfflineAudioContext :: new ( 1 , base_sr, base_sr as f32 ) ;
1126-
1127- // 1Hz sine at different sample rates
1128- let buf_sr = * sr;
1129- // safe cast for sample rate, see discussion at #113
1130- let sample_rate = buf_sr as f32 ;
1131- let mut buffer = context. create_buffer ( 1 , buf_sr, sample_rate) ;
1132- let mut sine = vec ! [ ] ;
1133-
1134- for i in 0 ..buf_sr {
1135- let phase = i as f32 / buf_sr as f32 * 2. * PI ;
1136- let sample = phase. sin ( ) ;
1137- sine. push ( sample) ;
1138- }
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+ }
11391163
1140- buffer. copy_to_channel ( & sine[ ..] , 0 ) ;
1164+ buffer. copy_to_channel ( & sine[ ..] , 0 ) ;
11411165
1142- let mut src = context. create_buffer_source ( ) ;
1143- src. connect ( & context. destination ( ) ) ;
1144- src. set_buffer ( buffer) ;
1145- 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 ) ;
11461170
1147- let result = context. start_rendering_sync ( ) ;
1148- let channel = result. get_channel_data ( 0 ) ;
1171+ let result = context. start_rendering_sync ( ) ;
1172+ let channel = result. get_channel_data ( 0 ) ;
11491173
1150- // 1Hz sine at audio context sample rate
1151- let mut expected = vec ! [ ] ;
1174+ // 1Hz sine at audio context sample rate
1175+ let mut expected = vec ! [ ] ;
11521176
1153- for i in 0 ..base_sr {
1154- let phase = i as f32 / base_sr as f32 * 2. * PI ;
1155- let sample = phase. sin ( ) ;
1156- expected. push ( sample) ;
1157- }
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+ }
11581182
1159- assert_float_eq ! ( channel[ ..] , expected[ ..] , abs_all <= 1e-6 ) ;
1160- } ) ;
1183+ assert_float_eq ! ( channel[ ..] , expected[ ..] , abs_all <= 1e-6 ) ;
1184+ } ) ;
11611185 }
11621186
11631187 #[ test]
@@ -1263,7 +1287,7 @@ mod tests {
12631287 }
12641288
12651289 #[ test]
1266- fn test_end_of_file_slow_track ( ) {
1290+ fn test_end_of_file_slow_track_1 ( ) {
12671291 let sample_rate = 48_000. ;
12681292 let mut context = OfflineAudioContext :: new ( 1 , RENDER_QUANTUM_SIZE * 2 , sample_rate) ;
12691293
@@ -1817,6 +1841,64 @@ mod tests {
18171841 assert_float_eq ! ( channel[ ..] , expected[ ..] , abs_all <= 0. ) ;
18181842 }
18191843
1844+ #[ test]
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
1848+ fn test_subsample_buffer_stitching ( ) {
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+ } ) ;
1866+
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+ }
1888+
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+ } ) ;
1894+
1895+ let result = context. start_rendering_sync ( ) ;
1896+ let actual = result. get_channel_data ( 0 ) ;
1897+
1898+ assert_float_eq ! ( actual[ ..] , expected[ ..] , abs_all <= error_threshold) ;
1899+ } ) ;
1900+ }
1901+
18201902 #[ test]
18211903 fn test_onended_before_drop ( ) {
18221904 let sample_rate = 48_000. ;
0 commit comments