Skip to content

Commit 86b9a28

Browse files
author
bors-servo
authored
Auto merge of #293 - collares:looping-playbackrate, r=ferjm
Implement missing functionality of AudioBufferSourceNode This is most of the fix for [Servo's #22363](servo/servo#22363). It is broken into four pieces. Let me know if I can help in the reviewing process.
2 parents ae49404 + 198f060 commit 86b9a28

File tree

5 files changed

+237
-53
lines changed

5 files changed

+237
-53
lines changed

audio/block.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,9 @@ impl Div<f64> for Tick {
637637

638638
impl Tick {
639639
pub const FRAMES_PER_BLOCK: Tick = FRAMES_PER_BLOCK;
640+
const EPSILON: f64 = 1e-7;
640641
pub fn from_time(time: f64, rate: f32) -> Tick {
641-
Tick((0.5 + time * rate as f64).floor() as u64)
642+
Tick((time * rate as f64 - Tick::EPSILON).ceil() as u64)
642643
}
643644

644645
pub fn advance(&mut self) {

audio/buffer_source_node.rs

Lines changed: 223 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ use param::{Param, ParamType};
88
pub enum AudioBufferSourceNodeMessage {
99
/// Set the data block holding the audio sample data to be played.
1010
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>),
1119
}
1220

1321
/// This specifies options for constructing an AudioBufferSourceNode.
@@ -42,16 +50,23 @@ impl Default for AudioBufferSourceNodeOptions {
4250

4351
/// AudioBufferSourceNode engine.
4452
/// https://webaudio.github.io/web-audio-api/#AudioBufferSourceNode
45-
/// XXX Implement looping
46-
/// XXX Implement playbackRate and related bits
4753
#[derive(AudioScheduledSourceNode, AudioNodeCommon)]
4854
#[allow(dead_code)]
4955
pub(crate) struct AudioBufferSourceNode {
5056
channel_info: ChannelInfo,
5157
/// A data block holding the audio sample data to be played.
5258
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,
5366
/// AudioParam to modulate the speed at which is rendered the audio stream.
5467
detune: Param,
68+
/// Whether we need to compute offsets from scratch.
69+
initialized_pos: bool,
5570
/// Indicates if the region of audio data designated by loopStart and loopEnd
5671
/// should be played continuously in a loop.
5772
loop_enabled: bool,
@@ -61,12 +76,19 @@ pub(crate) struct AudioBufferSourceNode {
6176
/// An playhead position where looping should begin if the loop_enabled
6277
/// attribute is true.
6378
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.
6782
playback_rate: Param,
6883
/// Time at which the source should start playing.
6984
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,
7092
/// Time at which the source should stop playing.
7193
stop_at: Option<Tick>,
7294
/// The ended event callback.
@@ -78,13 +100,18 @@ impl AudioBufferSourceNode {
78100
Self {
79101
channel_info,
80102
buffer: options.buffer,
103+
buffer_pos: 0.,
81104
detune: Param::new(options.detune),
105+
initialized_pos: false,
82106
loop_enabled: options.loop_enabled,
83107
loop_end: options.loop_end,
84108
loop_start: options.loop_start,
85-
playback_offset: 0,
86109
playback_rate: Param::new(options.playback_rate),
110+
buffer_duration: std::f64::INFINITY,
87111
start_at: None,
112+
start_offset: None,
113+
start_duration: None,
114+
start_when: 0.,
88115
stop_at: None,
89116
onended_callback: None,
90117
}
@@ -95,6 +122,22 @@ impl AudioBufferSourceNode {
95122
AudioBufferSourceNodeMessage::SetBuffer(buffer) => {
96123
self.buffer = buffer;
97124
}
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+
}
98141
}
99142
}
100143
}
@@ -116,15 +159,7 @@ impl AudioNodeEngine for AudioBufferSourceNode {
116159
return inputs;
117160
}
118161

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) {
128163
ShouldPlay::No => {
129164
inputs.blocks.push(Default::default());
130165
return inputs;
@@ -134,36 +169,165 @@ impl AudioNodeEngine for AudioBufferSourceNode {
134169

135170
let buffer = self.buffer.as_ref().unwrap();
136171

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;
139258
}
140-
let samples_to_copy = stop_at - start_at;
141259

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+
{
145269
let mut block = Block::empty();
270+
let pos = self.buffer_pos as usize;
271+
146272
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)]);
148274
}
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;
150279
} else {
151-
// silent fill and copy
280+
// Slow path, with interpolation.
152281
let mut block = Block::default();
153282
block.repeat(buffer.chans());
154283
block.explicit_repeat();
284+
285+
debug_assert!(buffer.chans() > 0);
286+
155287
for chan in 0..buffer.chans() {
156288
let data = block.data_chan_mut(chan);
157289
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+
}
162320
}
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();
164329
}
165330

166-
self.playback_offset = next_offset;
167331
inputs
168332
}
169333

@@ -185,23 +349,34 @@ impl AudioNodeEngine for AudioBufferSourceNode {
185349
pub struct AudioBuffer {
186350
/// Invariant: all buffers must be of the same length
187351
pub buffers: Vec<Vec<f32>>,
352+
pub sample_rate: f32,
188353
}
189354

190355
impl AudioBuffer {
191-
pub fn new(chan: u8, len: usize) -> Self {
356+
pub fn new(chan: u8, len: usize, sample_rate: f32) -> Self {
192357
assert!(chan > 0);
193358
let mut buffers = Vec::with_capacity(chan as usize);
194359
let single = vec![0.; len];
195360
buffers.resize(chan as usize, single);
196-
AudioBuffer { buffers }
361+
AudioBuffer {
362+
buffers,
363+
sample_rate,
364+
}
197365
}
198366

199-
pub fn from_buffers(buffers: Vec<Vec<f32>>) -> Self {
367+
pub fn from_buffers(buffers: Vec<Vec<f32>>, sample_rate: f32) -> Self {
200368
for buf in &buffers {
201369
assert_eq!(buf.len(), buffers[0].len())
202370
}
203371

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)
205380
}
206381

207382
pub fn len(&self) -> usize {
@@ -212,19 +387,22 @@ impl AudioBuffer {
212387
self.buffers.len() as u8
213388
}
214389

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);
219400

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
223403
}
224-
}
225404

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]
229407
}
230408
}

0 commit comments

Comments
 (0)