servo_media_audio/
buffer_source_node.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use crate::block::{Block, Chunk, FRAMES_PER_BLOCK, Tick};
6use crate::node::{
7    AudioNodeEngine, AudioNodeType, AudioScheduledSourceNodeMessage, BlockInfo, ChannelInfo,
8    OnEndedCallback, ShouldPlay,
9};
10use crate::param::{Param, ParamType};
11
12/// Control messages directed to AudioBufferSourceNodes.
13#[derive(Debug, Clone)]
14pub enum AudioBufferSourceNodeMessage {
15    /// Set the data block holding the audio sample data to be played.
16    SetBuffer(Option<AudioBuffer>),
17    /// Set loop parameter.
18    SetLoopEnabled(bool),
19    /// Set loop parameter.
20    SetLoopEnd(f64),
21    /// Set loop parameter.
22    SetLoopStart(f64),
23    /// Set start parameters (when, offset, duration).
24    SetStartParams(f64, Option<f64>, Option<f64>),
25}
26
27/// This specifies options for constructing an AudioBufferSourceNode.
28#[derive(Debug, Clone)]
29pub struct AudioBufferSourceNodeOptions {
30    /// The audio asset to be played.
31    pub buffer: Option<AudioBuffer>,
32    /// The initial value for the detune AudioParam.
33    pub detune: f32,
34    /// The initial value for the loop_enabled attribute.
35    pub loop_enabled: bool,
36    /// The initial value for the loop_end attribute.
37    pub loop_end: Option<f64>,
38    /// The initial value for the loop_start attribute.
39    pub loop_start: Option<f64>,
40    /// The initial value for the playback_rate AudioParam.
41    pub playback_rate: f32,
42}
43
44impl Default for AudioBufferSourceNodeOptions {
45    fn default() -> Self {
46        AudioBufferSourceNodeOptions {
47            buffer: None,
48            detune: 0.,
49            loop_enabled: false,
50            loop_end: None,
51            loop_start: None,
52            playback_rate: 1.,
53        }
54    }
55}
56
57/// AudioBufferSourceNode engine.
58/// <https://webaudio.github.io/web-audio-api/#AudioBufferSourceNode>
59#[derive(AudioScheduledSourceNode, AudioNodeCommon)]
60#[allow(dead_code)]
61pub(crate) struct AudioBufferSourceNode {
62    channel_info: ChannelInfo,
63    /// A data block holding the audio sample data to be played.
64    buffer: Option<AudioBuffer>,
65    /// How many more buffer-frames to output. See buffer_pos for clarification.
66    buffer_duration: f64,
67    /// "Index" of the next buffer frame to play. "Index" is in quotes because
68    /// this variable maps to a playhead position (the offset in seconds can be
69    /// obtained by dividing by self.buffer.sample_rate), and therefore has
70    /// subsample accuracy; a fractional "index" means interpolation is needed.
71    buffer_pos: f64,
72    /// AudioParam to modulate the speed at which is rendered the audio stream.
73    detune: Param,
74    /// Whether we need to compute offsets from scratch.
75    initialized_pos: bool,
76    /// Indicates if the region of audio data designated by loopStart and loopEnd
77    /// should be played continuously in a loop.
78    loop_enabled: bool,
79    /// An playhead position where looping should end if the loop_enabled
80    /// attribute is true.
81    loop_end: Option<f64>,
82    /// An playhead position where looping should begin if the loop_enabled
83    /// attribute is true.
84    loop_start: Option<f64>,
85    /// The speed at which to render the audio stream. Can be negative if the
86    /// audio is to be played backwards. With a negative playback_rate, looping
87    /// jumps from loop_start to loop_end instead of the other way around.
88    playback_rate: Param,
89    /// Time at which the source should start playing.
90    start_at: Option<Tick>,
91    /// Offset parameter passed to Start().
92    start_offset: Option<f64>,
93    /// Duration parameter passed to Start().
94    start_duration: Option<f64>,
95    /// The same as start_at, but with subsample accuracy.
96    /// FIXME: AudioScheduledSourceNode should use this as well.
97    start_when: f64,
98    /// Time at which the source should stop playing.
99    stop_at: Option<Tick>,
100    /// The ended event callback.
101    pub onended_callback: Option<OnEndedCallback>,
102}
103
104impl AudioBufferSourceNode {
105    pub fn new(options: AudioBufferSourceNodeOptions, channel_info: ChannelInfo) -> Self {
106        Self {
107            channel_info,
108            buffer: options.buffer,
109            buffer_pos: 0.,
110            detune: Param::new_krate(options.detune),
111            initialized_pos: false,
112            loop_enabled: options.loop_enabled,
113            loop_end: options.loop_end,
114            loop_start: options.loop_start,
115            playback_rate: Param::new_krate(options.playback_rate),
116            buffer_duration: f64::INFINITY,
117            start_at: None,
118            start_offset: None,
119            start_duration: None,
120            start_when: 0.,
121            stop_at: None,
122            onended_callback: None,
123        }
124    }
125
126    pub fn handle_message(&mut self, message: AudioBufferSourceNodeMessage, _: f32) {
127        match message {
128            AudioBufferSourceNodeMessage::SetBuffer(buffer) => {
129                self.buffer = buffer;
130            },
131            // XXX(collares): To fully support dynamically updating loop bounds,
132            // Must truncate self.buffer_pos if it is now outside the loop.
133            AudioBufferSourceNodeMessage::SetLoopEnabled(loop_enabled) => {
134                self.loop_enabled = loop_enabled
135            },
136            AudioBufferSourceNodeMessage::SetLoopEnd(loop_end) => self.loop_end = Some(loop_end),
137            AudioBufferSourceNodeMessage::SetLoopStart(loop_start) => {
138                self.loop_start = Some(loop_start)
139            },
140            AudioBufferSourceNodeMessage::SetStartParams(when, offset, duration) => {
141                self.start_when = when;
142                self.start_offset = offset;
143                self.start_duration = duration;
144            },
145        }
146    }
147}
148
149impl AudioNodeEngine for AudioBufferSourceNode {
150    fn node_type(&self) -> AudioNodeType {
151        AudioNodeType::AudioBufferSourceNode
152    }
153
154    fn input_count(&self) -> u32 {
155        0
156    }
157
158    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
159        debug_assert!(inputs.is_empty());
160
161        if self.buffer.is_none() {
162            inputs.blocks.push(Default::default());
163            return inputs;
164        }
165
166        let (start_at, stop_at) = match self.should_play_at(info.frame) {
167            ShouldPlay::No => {
168                inputs.blocks.push(Default::default());
169                return inputs;
170            },
171            ShouldPlay::Between(start, end) => (start.0 as usize, end.0 as usize),
172        };
173
174        let buffer = self.buffer.as_ref().unwrap();
175
176        let (mut actual_loop_start, mut actual_loop_end) = (0., buffer.len() as f64);
177        if self.loop_enabled {
178            let loop_start = self.loop_start.unwrap_or(0.);
179            let loop_end = self.loop_end.unwrap_or(0.);
180
181            if loop_start >= 0. && loop_end > loop_start {
182                actual_loop_start = loop_start * (buffer.sample_rate as f64);
183                actual_loop_end = loop_end * (buffer.sample_rate as f64);
184            }
185        }
186
187        // https://webaudio.github.io/web-audio-api/#computedplaybackrate
188        self.playback_rate.update(info, Tick(0));
189        self.detune.update(info, Tick(0));
190        // computed_playback_rate can be negative or zero.
191        let computed_playback_rate =
192            self.playback_rate.value() as f64 * (2.0_f64).powf(self.detune.value() as f64 / 1200.);
193        let forward = computed_playback_rate >= 0.;
194
195        if !self.initialized_pos {
196            self.initialized_pos = true;
197
198            // Apply the offset and duration parameters passed to start. We handle
199            // this here because the buffer may be set after Start() gets called, so
200            // this might be the first time we know the buffer's sample rate.
201            if let Some(start_offset) = self.start_offset {
202                self.buffer_pos = start_offset * (buffer.sample_rate as f64);
203                if self.buffer_pos < 0. {
204                    self.buffer_pos = 0.
205                } else if self.buffer_pos > buffer.len() as f64 {
206                    self.buffer_pos = buffer.len() as f64;
207                }
208            }
209
210            if self.loop_enabled {
211                if forward && self.buffer_pos >= actual_loop_end {
212                    self.buffer_pos = actual_loop_start;
213                }
214                // https://github.com/WebAudio/web-audio-api/issues/2031
215                if !forward && self.buffer_pos < actual_loop_start {
216                    self.buffer_pos = actual_loop_end;
217                }
218            }
219
220            if let Some(start_duration) = self.start_duration {
221                self.buffer_duration = start_duration * (buffer.sample_rate as f64);
222            }
223
224            // start_when can be subsample accurate. Correct buffer_pos.
225            //
226            // XXX(collares): What happens to "start_when" if the buffer gets
227            // set after Start()?
228            // XXX(collares): Need a better way to distingush between Start()
229            // being called with "when" in the past (in which case "when" must
230            // be ignored) and Start() being called with "when" in the future.
231            // This can now make a difference if "when" shouldn't be ignored
232            // but falls after the last frame of the previous quantum.
233            if self.start_when > info.time - 1. / info.sample_rate as f64 {
234                let first_time = info.time + start_at as f64 / info.sample_rate as f64;
235                if self.start_when <= first_time {
236                    let subsample_offset = (first_time - self.start_when) *
237                        (buffer.sample_rate as f64) *
238                        computed_playback_rate;
239                    self.buffer_pos += subsample_offset;
240                    self.buffer_duration -= subsample_offset.abs();
241                }
242            }
243        }
244
245        let mut buffer_offset_per_tick =
246            computed_playback_rate * (buffer.sample_rate as f64 / info.sample_rate as f64);
247
248        // WebAudio ยง1.9.5: "Setting the loop attribute to true causes playback of
249        // the region of the buffer defined by the endpoints loopStart and loopEnd
250        // to continue indefinitely, once any part of the looped region has been
251        // played. While loop remains true, looped playback will continue until one
252        // of the following occurs:
253        //  * stop() is called,
254        //  * the scheduled stop time has been reached,
255        //  * the duration has been exceeded, if start() was called with a duration value."
256        // Even with extreme playback rates we must stay inside the loop body, so wrap
257        // the per-tick delta instead of bailing.
258        if self.loop_enabled && actual_loop_end > actual_loop_start {
259            let loop_length = actual_loop_end - actual_loop_start;
260            if loop_length > 0. {
261                let step = buffer_offset_per_tick.abs();
262                if step >= loop_length {
263                    let mut wrapped = step.rem_euclid(loop_length);
264                    if wrapped == 0. {
265                        wrapped = loop_length;
266                    }
267                    buffer_offset_per_tick = wrapped.copysign(buffer_offset_per_tick);
268                }
269            }
270        }
271
272        // We will output at most this many frames (fewer if we run out of data).
273        let frames_to_output = stop_at - start_at;
274
275        // Fast path for the case where we can just copy FRAMES_PER_BLOCK
276        // frames straight from the buffer.
277        if frames_to_output == FRAMES_PER_BLOCK.0 as usize &&
278            forward &&
279            buffer_offset_per_tick == 1. &&
280            self.buffer_pos.trunc() == self.buffer_pos &&
281            self.buffer_pos + (FRAMES_PER_BLOCK.0 as f64) <= actual_loop_end &&
282            FRAMES_PER_BLOCK.0 as f64 <= self.buffer_duration
283        {
284            let mut block = Block::empty();
285            let pos = self.buffer_pos as usize;
286
287            for chan in 0..buffer.chans() {
288                block.push_chan(&buffer.buffers[chan as usize][pos..(pos + frames_to_output)]);
289            }
290
291            inputs.blocks.push(block);
292            self.buffer_pos += FRAMES_PER_BLOCK.0 as f64;
293            self.buffer_duration -= FRAMES_PER_BLOCK.0 as f64;
294        } else {
295            // Slow path, with interpolation.
296            let mut block = Block::default();
297            block.repeat(buffer.chans());
298            block.explicit_repeat();
299
300            debug_assert!(buffer.chans() > 0);
301
302            for chan in 0..buffer.chans() {
303                let data = block.data_chan_mut(chan);
304                let (_, data) = data.split_at_mut(start_at);
305                let (data, _) = data.split_at_mut(frames_to_output);
306
307                let mut pos = self.buffer_pos;
308                let mut duration = self.buffer_duration;
309
310                for sample in data {
311                    if duration <= 0. {
312                        break;
313                    }
314
315                    if self.loop_enabled {
316                        if forward && pos >= actual_loop_end {
317                            pos -= actual_loop_end - actual_loop_start;
318                        } else if !forward && pos < actual_loop_start {
319                            pos += actual_loop_end - actual_loop_start;
320                        }
321                    } else if pos < 0. || pos >= buffer.len() as f64 {
322                        break;
323                    }
324
325                    *sample = buffer.interpolate(chan, pos);
326                    pos += buffer_offset_per_tick;
327                    duration -= buffer_offset_per_tick.abs();
328                }
329
330                // This is the last channel, update parameters.
331                if chan == buffer.chans() - 1 {
332                    self.buffer_pos = pos;
333                    self.buffer_duration = duration;
334                }
335            }
336
337            inputs.blocks.push(block);
338        }
339
340        if !self.loop_enabled && (self.buffer_pos < 0. || self.buffer_pos >= buffer.len() as f64) ||
341            self.buffer_duration <= 0.
342        {
343            self.maybe_trigger_onended_callback();
344        }
345
346        inputs
347    }
348
349    fn get_param(&mut self, id: ParamType) -> &mut Param {
350        match id {
351            ParamType::PlaybackRate => &mut self.playback_rate,
352            ParamType::Detune => &mut self.detune,
353            _ => panic!("Unknown param {:?} for AudioBufferSourceNode", id),
354        }
355    }
356
357    make_message_handler!(
358        AudioBufferSourceNode: handle_message,
359        AudioScheduledSourceNode: handle_source_node_message
360    );
361}
362
363#[derive(Debug, Clone)]
364pub struct AudioBuffer {
365    /// Invariant: all buffers must be of the same length
366    pub buffers: Vec<Vec<f32>>,
367    pub sample_rate: f32,
368}
369
370impl AudioBuffer {
371    pub fn new(chan: u8, len: usize, sample_rate: f32) -> Self {
372        assert!(chan > 0);
373        let mut buffers = Vec::with_capacity(chan as usize);
374        let single = vec![0.; len];
375        buffers.resize(chan as usize, single);
376        AudioBuffer {
377            buffers,
378            sample_rate,
379        }
380    }
381
382    pub fn from_buffers(buffers: Vec<Vec<f32>>, sample_rate: f32) -> Self {
383        for buf in &buffers {
384            assert_eq!(buf.len(), buffers[0].len())
385        }
386
387        Self {
388            buffers,
389            sample_rate,
390        }
391    }
392
393    pub fn from_buffer(buffer: Vec<f32>, sample_rate: f32) -> Self {
394        AudioBuffer::from_buffers(vec![buffer], sample_rate)
395    }
396
397    pub fn len(&self) -> usize {
398        self.buffers[0].len()
399    }
400
401    pub fn is_empty(&self) -> bool {
402        self.len() == 0
403    }
404
405    pub fn chans(&self) -> u8 {
406        self.buffers.len() as u8
407    }
408
409    // XXX(collares): There are better fast interpolation algorithms.
410    // Firefox uses (via Speex's resampler) the algorithm described in
411    // https://ccrma.stanford.edu/~jos/resample/resample.pdf
412    // There are Rust bindings: https://github.com/rust-av/speexdsp-rs
413    pub fn interpolate(&self, chan: u8, pos: f64) -> f32 {
414        debug_assert!(pos >= 0. && pos < self.len() as f64);
415
416        let prev = pos.floor() as usize;
417        let offset = pos - pos.floor();
418        match self.buffers[chan as usize].get(prev + 1) {
419            Some(next_sample) => {
420                ((1. - offset) * (self.buffers[chan as usize][prev] as f64) +
421                    offset * (*next_sample as f64)) as f32
422            },
423            _ => {
424                // linear extrapolation of two prev samples if there are two
425                if prev > 0 {
426                    ((1. + offset) * (self.buffers[chan as usize][prev] as f64) -
427                        offset * (self.buffers[chan as usize][prev - 1] as f64))
428                        as f32
429                } else {
430                    self.buffers[chan as usize][prev]
431                }
432            },
433        }
434    }
435
436    pub fn data_chan_mut(&mut self, chan: u8) -> &mut [f32] {
437        &mut self.buffers[chan as usize]
438    }
439}