servo_media_audio/
buffer_source_node.rs

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