@@ -8,6 +8,14 @@ use param::{Param, ParamType};
8
8
pub enum AudioBufferSourceNodeMessage {
9
9
/// Set the data block holding the audio sample data to be played.
10
10
SetBuffer ( Option < AudioBuffer > ) ,
11
+ /// Set loop parameter.
12
+ SetLoopEnabled ( bool ) ,
13
+ /// Set loop parameter.
14
+ SetLoopEnd ( f64 ) ,
15
+ /// Set loop parameter.
16
+ SetLoopStart ( f64 ) ,
17
+ /// Set start parameters (when, offset, duration).
18
+ SetStartParams ( f64 , Option < f64 > , Option < f64 > ) ,
11
19
}
12
20
13
21
/// This specifies options for constructing an AudioBufferSourceNode.
@@ -42,16 +50,23 @@ impl Default for AudioBufferSourceNodeOptions {
42
50
43
51
/// AudioBufferSourceNode engine.
44
52
/// https://webaudio.github.io/web-audio-api/#AudioBufferSourceNode
45
- /// XXX Implement looping
46
- /// XXX Implement playbackRate and related bits
47
53
#[ derive( AudioScheduledSourceNode , AudioNodeCommon ) ]
48
54
#[ allow( dead_code) ]
49
55
pub ( crate ) struct AudioBufferSourceNode {
50
56
channel_info : ChannelInfo ,
51
57
/// A data block holding the audio sample data to be played.
52
58
buffer : Option < AudioBuffer > ,
59
+ /// How many more buffer-frames to output. See buffer_pos for clarification.
60
+ buffer_duration : f64 ,
61
+ /// "Index" of the next buffer frame to play. "Index" is in quotes because
62
+ /// this variable maps to a playhead position (the offset in seconds can be
63
+ /// obtained by dividing by self.buffer.sample_rate), and therefore has
64
+ /// subsample accuracy; a fractional "index" means interpolation is needed.
65
+ buffer_pos : f64 ,
53
66
/// AudioParam to modulate the speed at which is rendered the audio stream.
54
67
detune : Param ,
68
+ /// Whether we need to compute offsets from scratch.
69
+ initialized_pos : bool ,
55
70
/// Indicates if the region of audio data designated by loopStart and loopEnd
56
71
/// should be played continuously in a loop.
57
72
loop_enabled : bool ,
@@ -61,12 +76,19 @@ pub(crate) struct AudioBufferSourceNode {
61
76
/// An playhead position where looping should begin if the loop_enabled
62
77
/// attribute is true.
63
78
loop_start : Option < f64 > ,
64
- /// Playback offset.
65
- playback_offset : usize ,
66
- /// The speed at which to render the audio stream .
79
+ /// The speed at which to render the audio stream. Can be negative if the
80
+ /// audio is to be played backwards. With a negative playback_rate, looping
81
+ /// jumps from loop_start to loop_end instead of the other way around .
67
82
playback_rate : Param ,
68
83
/// Time at which the source should start playing.
69
84
start_at : Option < Tick > ,
85
+ /// Offset parameter passed to Start().
86
+ start_offset : Option < f64 > ,
87
+ /// Duration parameter passed to Start().
88
+ start_duration : Option < f64 > ,
89
+ /// The same as start_at, but with subsample accuracy.
90
+ /// FIXME: AudioScheduledSourceNode should use this as well.
91
+ start_when : f64 ,
70
92
/// Time at which the source should stop playing.
71
93
stop_at : Option < Tick > ,
72
94
/// The ended event callback.
@@ -78,13 +100,18 @@ impl AudioBufferSourceNode {
78
100
Self {
79
101
channel_info,
80
102
buffer : options. buffer ,
103
+ buffer_pos : 0. ,
81
104
detune : Param :: new ( options. detune ) ,
105
+ initialized_pos : false ,
82
106
loop_enabled : options. loop_enabled ,
83
107
loop_end : options. loop_end ,
84
108
loop_start : options. loop_start ,
85
- playback_offset : 0 ,
86
109
playback_rate : Param :: new ( options. playback_rate ) ,
110
+ buffer_duration : std:: f64:: INFINITY ,
87
111
start_at : None ,
112
+ start_offset : None ,
113
+ start_duration : None ,
114
+ start_when : 0. ,
88
115
stop_at : None ,
89
116
onended_callback : None ,
90
117
}
@@ -95,6 +122,22 @@ impl AudioBufferSourceNode {
95
122
AudioBufferSourceNodeMessage :: SetBuffer ( buffer) => {
96
123
self . buffer = buffer;
97
124
}
125
+ // XXX(collares): To fully support dynamically updating loop bounds,
126
+ // Must truncate self.buffer_pos if it is now outside the loop.
127
+ AudioBufferSourceNodeMessage :: SetLoopEnabled ( loop_enabled) => {
128
+ self . loop_enabled = loop_enabled
129
+ }
130
+ AudioBufferSourceNodeMessage :: SetLoopEnd ( loop_end) => {
131
+ self . loop_end = Some ( loop_end)
132
+ }
133
+ AudioBufferSourceNodeMessage :: SetLoopStart ( loop_start) => {
134
+ self . loop_start = Some ( loop_start)
135
+ }
136
+ AudioBufferSourceNodeMessage :: SetStartParams ( when, offset, duration) => {
137
+ self . start_when = when;
138
+ self . start_offset = offset;
139
+ self . start_duration = duration;
140
+ }
98
141
}
99
142
}
100
143
}
@@ -116,15 +159,7 @@ impl AudioNodeEngine for AudioBufferSourceNode {
116
159
return inputs;
117
160
}
118
161
119
- let len = { self . buffer . as_ref ( ) . unwrap ( ) . len ( ) as usize } ;
120
-
121
- if self . playback_offset >= len {
122
- self . maybe_trigger_onended_callback ( ) ;
123
- inputs. blocks . push ( Default :: default ( ) ) ;
124
- return inputs;
125
- }
126
-
127
- let ( start_at, mut stop_at) = match self . should_play_at ( info. frame ) {
162
+ let ( start_at, stop_at) = match self . should_play_at ( info. frame ) {
128
163
ShouldPlay :: No => {
129
164
inputs. blocks . push ( Default :: default ( ) ) ;
130
165
return inputs;
@@ -134,36 +169,165 @@ impl AudioNodeEngine for AudioBufferSourceNode {
134
169
135
170
let buffer = self . buffer . as_ref ( ) . unwrap ( ) ;
136
171
137
- if self . playback_offset + stop_at - start_at > len {
138
- stop_at = start_at + len - self . playback_offset ;
172
+ let ( mut actual_loop_start, mut actual_loop_end) = ( 0. , buffer. len ( ) as f64 ) ;
173
+ if self . loop_enabled {
174
+ let loop_start = self . loop_start . unwrap_or ( 0. ) ;
175
+ let loop_end = self . loop_end . unwrap_or ( 0. ) ;
176
+
177
+ if loop_start >= 0. && loop_end > loop_start {
178
+ actual_loop_start = loop_start * ( buffer. sample_rate as f64 ) ;
179
+ actual_loop_end = loop_end * ( buffer. sample_rate as f64 ) ;
180
+ }
181
+ }
182
+
183
+ // https://webaudio.github.io/web-audio-api/#computedplaybackrate
184
+ self . playback_rate . update ( info, Tick ( 0 ) ) ;
185
+ self . detune . update ( info, Tick ( 0 ) ) ;
186
+ // computed_playback_rate can be negative or zero.
187
+ let computed_playback_rate =
188
+ self . playback_rate . value ( ) as f64 * ( 2.0_f64 ) . powf ( self . detune . value ( ) as f64 / 1200. ) ;
189
+ let forward = computed_playback_rate >= 0. ;
190
+
191
+ if !self . initialized_pos {
192
+ self . initialized_pos = true ;
193
+
194
+ // Apply the offset and duration parameters passed to start. We handle
195
+ // this here because the buffer may be set after Start() gets called, so
196
+ // this might be the first time we know the buffer's sample rate.
197
+ if let Some ( start_offset) = self . start_offset {
198
+ self . buffer_pos = start_offset * ( buffer. sample_rate as f64 ) ;
199
+ if self . buffer_pos < 0. {
200
+ self . buffer_pos = 0.
201
+ } else if self . buffer_pos > buffer. len ( ) as f64 {
202
+ self . buffer_pos = buffer. len ( ) as f64 ;
203
+ }
204
+ }
205
+
206
+ if self . loop_enabled {
207
+ if forward && self . buffer_pos >= actual_loop_end {
208
+ self . buffer_pos = actual_loop_start;
209
+ }
210
+ // https://github.com/WebAudio/web-audio-api/issues/2031
211
+ if !forward && self . buffer_pos < actual_loop_start {
212
+ self . buffer_pos = actual_loop_end;
213
+ }
214
+ }
215
+
216
+ if let Some ( start_duration) = self . start_duration {
217
+ self . buffer_duration = start_duration * ( buffer. sample_rate as f64 ) ;
218
+ }
219
+
220
+ // start_when can be subsample accurate. Correct buffer_pos.
221
+ //
222
+ // XXX(collares): What happens to "start_when" if the buffer gets
223
+ // set after Start()?
224
+ // XXX(collares): Need a better way to distingush between Start()
225
+ // being called with "when" in the past (in which case "when" must
226
+ // be ignored) and Start() being called with "when" in the future.
227
+ // This can now make a difference if "when" shouldn't be ignored
228
+ // but falls after the last frame of the previous quantum.
229
+ if self . start_when > info. time - 1. / info. sample_rate as f64 {
230
+ let first_time = info. time + start_at as f64 / info. sample_rate as f64 ;
231
+ if self . start_when <= first_time {
232
+ let subsample_offset = ( first_time - self . start_when )
233
+ * ( buffer. sample_rate as f64 )
234
+ * computed_playback_rate;
235
+ self . buffer_pos += subsample_offset;
236
+ self . buffer_duration -= subsample_offset. abs ( ) ;
237
+ }
238
+ }
239
+ }
240
+
241
+ let buffer_offset_per_tick =
242
+ computed_playback_rate * ( buffer. sample_rate as f64 / info. sample_rate as f64 ) ;
243
+
244
+ // We will output at most this many frames (fewer if we run out of data).
245
+ let frames_to_output = stop_at - start_at;
246
+
247
+ if self . loop_enabled && buffer_offset_per_tick. abs ( ) < actual_loop_end - actual_loop_start {
248
+ // Refuse to output data in this extreme edge case.
249
+ //
250
+ // XXX(collares): There are two ways we could handle it:
251
+ // 1) Take buffer_offset_per_tick modulo the loop length, and handle
252
+ // the pre-loop-entering output separately.
253
+ // 2) Add a division by the loop length to the hot path below.
254
+ // None of them seem worth the trouble. The spec should forbid this.
255
+ self . maybe_trigger_onended_callback ( ) ;
256
+ inputs. blocks . push ( Default :: default ( ) ) ;
257
+ return inputs;
139
258
}
140
- let samples_to_copy = stop_at - start_at;
141
259
142
- let next_offset = self . playback_offset + samples_to_copy;
143
- if samples_to_copy == FRAMES_PER_BLOCK . 0 as usize {
144
- // copy entire chan
260
+ // Fast path for the case where we can just copy FRAMES_PER_BLOCK
261
+ // frames straight from the buffer.
262
+ if frames_to_output == FRAMES_PER_BLOCK . 0 as usize
263
+ && forward
264
+ && buffer_offset_per_tick == 1.
265
+ && self . buffer_pos . trunc ( ) == self . buffer_pos
266
+ && self . buffer_pos + ( FRAMES_PER_BLOCK . 0 as f64 ) <= actual_loop_end
267
+ && FRAMES_PER_BLOCK . 0 as f64 <= self . buffer_duration
268
+ {
145
269
let mut block = Block :: empty ( ) ;
270
+ let pos = self . buffer_pos as usize ;
271
+
146
272
for chan in 0 ..buffer. chans ( ) {
147
- block. push_chan ( & buffer. buffers [ chan as usize ] [ self . playback_offset ..next_offset ] ) ;
273
+ block. push_chan ( & buffer. buffers [ chan as usize ] [ pos.. ( pos + frames_to_output ) ] ) ;
148
274
}
149
- inputs. blocks . push ( block)
275
+
276
+ inputs. blocks . push ( block) ;
277
+ self . buffer_pos += FRAMES_PER_BLOCK . 0 as f64 ;
278
+ self . buffer_duration -= FRAMES_PER_BLOCK . 0 as f64 ;
150
279
} else {
151
- // silent fill and copy
280
+ // Slow path, with interpolation.
152
281
let mut block = Block :: default ( ) ;
153
282
block. repeat ( buffer. chans ( ) ) ;
154
283
block. explicit_repeat ( ) ;
284
+
285
+ debug_assert ! ( buffer. chans( ) > 0 ) ;
286
+
155
287
for chan in 0 ..buffer. chans ( ) {
156
288
let data = block. data_chan_mut ( chan) ;
157
289
let ( _, data) = data. split_at_mut ( start_at) ;
158
- let ( data, _) = data. split_at_mut ( samples_to_copy) ;
159
- data. copy_from_slice (
160
- & buffer. buffers [ chan as usize ] [ self . playback_offset ..next_offset] ,
161
- ) ;
290
+ let ( data, _) = data. split_at_mut ( frames_to_output) ;
291
+
292
+ let mut pos = self . buffer_pos ;
293
+ let mut duration = self . buffer_duration ;
294
+
295
+ for sample in data {
296
+ if duration <= 0. {
297
+ break ;
298
+ }
299
+
300
+ if self . loop_enabled {
301
+ if forward && pos >= actual_loop_end {
302
+ pos -= actual_loop_end - actual_loop_start;
303
+ } else if !forward && pos < actual_loop_start {
304
+ pos += actual_loop_end - actual_loop_start;
305
+ }
306
+ } else if pos < 0. || pos >= buffer. len ( ) as f64 {
307
+ break ;
308
+ }
309
+
310
+ * sample = buffer. interpolate ( chan, pos) ;
311
+ pos += buffer_offset_per_tick;
312
+ duration -= buffer_offset_per_tick. abs ( ) ;
313
+ }
314
+
315
+ // This is the last channel, update parameters.
316
+ if chan == buffer. chans ( ) - 1 {
317
+ self . buffer_pos = pos;
318
+ self . buffer_duration = duration;
319
+ }
162
320
}
163
- inputs. blocks . push ( block)
321
+
322
+ inputs. blocks . push ( block) ;
323
+ }
324
+
325
+ if !self . loop_enabled && ( self . buffer_pos < 0. || self . buffer_pos >= buffer. len ( ) as f64 )
326
+ || self . buffer_duration <= 0.
327
+ {
328
+ self . maybe_trigger_onended_callback ( ) ;
164
329
}
165
330
166
- self . playback_offset = next_offset;
167
331
inputs
168
332
}
169
333
@@ -185,23 +349,34 @@ impl AudioNodeEngine for AudioBufferSourceNode {
185
349
pub struct AudioBuffer {
186
350
/// Invariant: all buffers must be of the same length
187
351
pub buffers : Vec < Vec < f32 > > ,
352
+ pub sample_rate : f32 ,
188
353
}
189
354
190
355
impl AudioBuffer {
191
- pub fn new ( chan : u8 , len : usize ) -> Self {
356
+ pub fn new ( chan : u8 , len : usize , sample_rate : f32 ) -> Self {
192
357
assert ! ( chan > 0 ) ;
193
358
let mut buffers = Vec :: with_capacity ( chan as usize ) ;
194
359
let single = vec ! [ 0. ; len] ;
195
360
buffers. resize ( chan as usize , single) ;
196
- AudioBuffer { buffers }
361
+ AudioBuffer {
362
+ buffers,
363
+ sample_rate,
364
+ }
197
365
}
198
366
199
- pub fn from_buffers ( buffers : Vec < Vec < f32 > > ) -> Self {
367
+ pub fn from_buffers ( buffers : Vec < Vec < f32 > > , sample_rate : f32 ) -> Self {
200
368
for buf in & buffers {
201
369
assert_eq ! ( buf. len( ) , buffers[ 0 ] . len( ) )
202
370
}
203
371
204
- Self { buffers }
372
+ Self {
373
+ buffers,
374
+ sample_rate,
375
+ }
376
+ }
377
+
378
+ pub fn from_buffer ( buffer : Vec < f32 > , sample_rate : f32 ) -> Self {
379
+ AudioBuffer :: from_buffers ( vec ! [ buffer] , sample_rate)
205
380
}
206
381
207
382
pub fn len ( & self ) -> usize {
@@ -212,19 +387,22 @@ impl AudioBuffer {
212
387
self . buffers . len ( ) as u8
213
388
}
214
389
215
- pub fn data_chan_mut ( & mut self , chan : u8 ) -> & mut [ f32 ] {
216
- & mut self . buffers [ chan as usize ]
217
- }
218
- }
390
+ // XXX(collares): There are better fast interpolation algorithms.
391
+ // Firefox uses (via Speex's resampler) the algorithm described in
392
+ // https://ccrma.stanford.edu/~jos/resample/resample.pdf
393
+ // There are Rust bindings: https://github.com/rust-av/speexdsp-rs
394
+ pub fn interpolate ( & self , chan : u8 , pos : f64 ) -> f32 {
395
+ debug_assert ! ( pos >= 0. && pos < self . len( ) as f64 ) ;
396
+
397
+ let prev = pos. floor ( ) as usize ;
398
+ let offset = pos - pos. floor ( ) ;
399
+ let next_sample = * self . buffers [ chan as usize ] . get ( prev + 1 ) . unwrap_or ( & 0.0 ) ;
219
400
220
- impl From < Vec < f32 > > for AudioBuffer {
221
- fn from ( vec : Vec < f32 > ) -> Self {
222
- Self { buffers : vec ! [ vec] }
401
+ ( ( 1. - offset) * ( self . buffers [ chan as usize ] [ prev] as f64 ) + offset * ( next_sample as f64 ) )
402
+ as f32
223
403
}
224
- }
225
404
226
- impl From < Vec < Vec < f32 > > > for AudioBuffer {
227
- fn from ( vec : Vec < Vec < f32 > > ) -> Self {
228
- AudioBuffer :: from_buffers ( vec)
405
+ pub fn data_chan_mut ( & mut self , chan : u8 ) -> & mut [ f32 ] {
406
+ & mut self . buffers [ chan as usize ]
229
407
}
230
408
}
0 commit comments