Skip to main content

web_audio_api/node/
audio_buffer_source.rs

1use std::any::Any;
2use std::sync::atomic::Ordering;
3use std::sync::Arc;
4
5use crate::buffer::AudioBuffer;
6use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
7use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
8use crate::render::{
9    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
10};
11use crate::{assert_valid_time_value, AtomicF64, RENDER_QUANTUM_SIZE};
12
13use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
14
15/// Options for constructing an [`AudioBufferSourceNode`]
16// dictionary AudioBufferSourceOptions {
17//   AudioBuffer? buffer;
18//   float detune = 0;
19//   boolean loop = false;
20//   double loopEnd = 0;
21//   double loopStart = 0;
22//   float playbackRate = 1;
23// };
24//
25// @note - Does extend AudioNodeOptions but they are useless for source nodes as
26// they instruct how to upmix the inputs.
27// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472, and
28// an issue in the spec, see discussion in https://github.com/WebAudio/web-audio-api/issues/2496
29#[derive(Clone, Debug)]
30pub struct AudioBufferSourceOptions {
31    pub buffer: Option<AudioBuffer>,
32    pub detune: f32,
33    pub loop_: bool,
34    pub loop_start: f64,
35    pub loop_end: f64,
36    pub playback_rate: f32,
37}
38
39impl Default for AudioBufferSourceOptions {
40    fn default() -> Self {
41        Self {
42            buffer: None,
43            detune: 0.,
44            loop_: false,
45            loop_start: 0.,
46            loop_end: 0.,
47            playback_rate: 1.,
48        }
49    }
50}
51
52#[derive(Debug, Copy, Clone)]
53struct PlaybackInfo {
54    prev_frame_index: usize,
55    k: f64,
56}
57
58#[derive(Debug, Clone, Copy)]
59struct LoopState {
60    pub is_looping: bool,
61    pub start: f64,
62    pub end: f64,
63}
64
65/// Instructions to start or stop processing
66#[derive(Debug, Clone)]
67enum ControlMessage {
68    StartWithOffsetAndDuration(f64, f64, f64),
69    Stop(f64),
70    Loop(bool),
71    LoopStart(f64),
72    LoopEnd(f64),
73}
74
75/// `AudioBufferSourceNode` represents an audio source that consists of an
76/// in-memory audio source (i.e. an audio file completely loaded in memory),
77/// stored in an [`AudioBuffer`].
78///
79/// - MDN documentation: <https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode>
80/// - specification: <https://webaudio.github.io/web-audio-api/#AudioBufferSourceNode>
81/// - see also: [`BaseAudioContext::create_buffer_source`]
82///
83/// # Usage
84///
85/// ```no_run
86/// use std::fs::File;
87/// use web_audio_api::context::{BaseAudioContext, AudioContext};
88/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};
89///
90/// // create an `AudioContext`
91/// let context = AudioContext::default();
92/// // load and decode a soundfile
93/// let file = File::open("samples/sample.wav").unwrap();
94/// let audio_buffer = context.decode_audio_data_sync(file).unwrap();
95/// // play the sound file
96/// let mut src = context.create_buffer_source();
97/// src.set_buffer(audio_buffer);
98/// src.connect(&context.destination());
99/// src.start();
100/// ```
101///
102/// # Examples
103///
104/// - `cargo run --release --example trigger_soundfile`
105/// - `cargo run --release --example granular`
106///
107#[derive(Debug)]
108pub struct AudioBufferSourceNode {
109    registration: AudioContextRegistration,
110    channel_config: ChannelConfig,
111    detune: AudioParam,        // has constraints, no a-rate
112    playback_rate: AudioParam, // has constraints, no a-rate
113    buffer_time: Arc<AtomicF64>,
114    buffer: Option<AudioBuffer>,
115    loop_state: LoopState,
116    has_start: bool,
117}
118
119impl AudioNode for AudioBufferSourceNode {
120    fn registration(&self) -> &AudioContextRegistration {
121        &self.registration
122    }
123
124    fn channel_config(&self) -> &ChannelConfig {
125        &self.channel_config
126    }
127
128    fn number_of_inputs(&self) -> usize {
129        0
130    }
131
132    fn number_of_outputs(&self) -> usize {
133        1
134    }
135}
136
137impl AudioScheduledSourceNode for AudioBufferSourceNode {
138    fn start(&mut self) {
139        let start = self.registration.context().current_time();
140        self.start_at_with_offset_and_duration(start, 0., f64::MAX);
141    }
142
143    fn start_at(&mut self, when: f64) {
144        self.start_at_with_offset_and_duration(when, 0., f64::MAX);
145    }
146
147    fn stop(&mut self) {
148        let stop = self.registration.context().current_time();
149        self.stop_at(stop);
150    }
151
152    fn stop_at(&mut self, when: f64) {
153        assert_valid_time_value(when);
154        assert!(
155            self.has_start,
156            "InvalidStateError - cannot stop before start"
157        );
158
159        self.registration.post_message(ControlMessage::Stop(when));
160    }
161}
162
163impl AudioBufferSourceNode {
164    /// Create a new [`AudioBufferSourceNode`] instance
165    pub fn new<C: BaseAudioContext>(context: &C, options: AudioBufferSourceOptions) -> Self {
166        let AudioBufferSourceOptions {
167            buffer,
168            detune,
169            loop_,
170            loop_start,
171            loop_end,
172            playback_rate,
173        } = options;
174
175        let mut node = context.base().register(move |registration| {
176            // these parameters can't be changed to a-rate
177            // @see - <https://webaudio.github.io/web-audio-api/#audioparam-automation-rate-constraints>
178            let detune_param_options = AudioParamDescriptor {
179                name: String::new(),
180                min_value: f32::MIN,
181                max_value: f32::MAX,
182                default_value: 0.,
183                automation_rate: AutomationRate::K,
184            };
185            let (mut d_param, d_proc) =
186                context.create_audio_param(detune_param_options, &registration);
187            d_param.set_automation_rate_constrained(true);
188            d_param.set_value(detune);
189
190            let playback_rate_param_options = AudioParamDescriptor {
191                name: String::new(),
192                min_value: f32::MIN,
193                max_value: f32::MAX,
194                default_value: 1.,
195                automation_rate: AutomationRate::K,
196            };
197            let (mut pr_param, pr_proc) =
198                context.create_audio_param(playback_rate_param_options, &registration);
199            pr_param.set_automation_rate_constrained(true);
200            pr_param.set_value(playback_rate);
201
202            let loop_state = LoopState {
203                is_looping: loop_,
204                start: loop_start,
205                end: loop_end,
206            };
207
208            let renderer = AudioBufferSourceRenderer {
209                start_time: f64::MAX,
210                stop_time: f64::MAX,
211                duration: f64::MAX,
212                offset: 0.,
213                buffer: None,
214                detune: d_proc,
215                playback_rate: pr_proc,
216                loop_state,
217                render_state: AudioBufferRendererState::default(),
218            };
219
220            let node = Self {
221                registration,
222                channel_config: ChannelConfig::default(),
223                detune: d_param,
224                playback_rate: pr_param,
225                buffer_time: Arc::clone(&renderer.render_state.buffer_time),
226                buffer: None,
227                loop_state,
228                has_start: false,
229            };
230
231            (node, Box::new(renderer))
232        });
233
234        // renderer has been sent to render thread, we can send it messages
235        if let Some(buf) = buffer {
236            node.set_buffer(buf);
237        }
238
239        node
240    }
241
242    /// Start the playback at the given time and with a given offset
243    ///
244    /// # Panics
245    ///
246    /// Panics if the source was already started
247    pub fn start_at_with_offset(&mut self, start: f64, offset: f64) {
248        self.start_at_with_offset_and_duration(start, offset, f64::MAX);
249    }
250
251    /// Start the playback at the given time, with a given offset, for a given duration
252    ///
253    /// # Panics
254    ///
255    /// Panics if the source was already started
256    pub fn start_at_with_offset_and_duration(&mut self, start: f64, offset: f64, duration: f64) {
257        assert_valid_time_value(start);
258        assert_valid_time_value(offset);
259        assert_valid_time_value(duration);
260        assert!(
261            !self.has_start,
262            "InvalidStateError - Cannot call `start` twice"
263        );
264
265        self.has_start = true;
266        let control = ControlMessage::StartWithOffsetAndDuration(start, offset, duration);
267        self.registration.post_message(control);
268    }
269
270    /// Current buffer value (nullable)
271    pub fn buffer(&self) -> Option<&AudioBuffer> {
272        self.buffer.as_ref()
273    }
274
275    /// Provide an [`AudioBuffer`] as the source of data to be played bask
276    ///
277    /// # Panics
278    ///
279    /// Panics if a buffer has already been given to the source (though `new` or through
280    /// `set_buffer`)
281    pub fn set_buffer(&mut self, audio_buffer: AudioBuffer) {
282        let clone = audio_buffer.clone();
283
284        assert!(
285            self.buffer.is_none(),
286            "InvalidStateError - cannot assign buffer twice",
287        );
288        self.buffer = Some(audio_buffer);
289
290        self.registration.post_message(clone);
291    }
292
293    /// K-rate [`AudioParam`] that defines the speed at which the [`AudioBuffer`]
294    /// will be played, e.g.:
295    /// - `0.5` will play the file at half speed
296    /// - `-1` will play the file in reverse
297    ///
298    /// Note that playback rate will also alter the pitch of the [`AudioBuffer`]
299    pub fn playback_rate(&self) -> &AudioParam {
300        &self.playback_rate
301    }
302
303    /// Current playhead position in seconds within the [`AudioBuffer`].
304    ///
305    /// This value is updated at the end of each render quantum.
306    ///
307    /// Unofficial v2 API extension, not part of the spec yet.
308    /// See also: <https://github.com/WebAudio/web-audio-api/issues/2397#issuecomment-709478405>
309    pub fn position(&self) -> f64 {
310        self.buffer_time.load(Ordering::Relaxed)
311    }
312
313    /// K-rate [`AudioParam`] that defines a pitch transposition of the file,
314    /// expressed in cents
315    ///
316    /// see <https://en.wikipedia.org/wiki/Cent_(music)>
317    pub fn detune(&self) -> &AudioParam {
318        &self.detune
319    }
320
321    /// Defines if the playback the [`AudioBuffer`] should be looped
322    #[allow(clippy::missing_panics_doc)]
323    pub fn loop_(&self) -> bool {
324        self.loop_state.is_looping
325    }
326
327    pub fn set_loop(&mut self, value: bool) {
328        self.loop_state.is_looping = value;
329        self.registration.post_message(ControlMessage::Loop(value));
330    }
331
332    /// Defines the loop start point, in the time reference of the [`AudioBuffer`]
333    pub fn loop_start(&self) -> f64 {
334        self.loop_state.start
335    }
336
337    pub fn set_loop_start(&mut self, value: f64) {
338        self.loop_state.start = value;
339        self.registration
340            .post_message(ControlMessage::LoopStart(value));
341    }
342
343    /// Defines the loop end point, in the time reference of the [`AudioBuffer`]
344    pub fn loop_end(&self) -> f64 {
345        self.loop_state.end
346    }
347
348    pub fn set_loop_end(&mut self, value: f64) {
349        self.loop_state.end = value;
350        self.registration
351            .post_message(ControlMessage::LoopEnd(value));
352    }
353}
354
355struct AudioBufferRendererState {
356    buffer_time: Arc<AtomicF64>,
357    started: bool,
358    entered_loop: bool,
359    buffer_time_elapsed: f64,
360    is_aligned: bool,
361    ended: bool,
362}
363
364impl Default for AudioBufferRendererState {
365    fn default() -> Self {
366        Self {
367            buffer_time: Arc::new(AtomicF64::new(0.)),
368            started: false,
369            entered_loop: false,
370            buffer_time_elapsed: 0.,
371            is_aligned: false,
372            ended: false,
373        }
374    }
375}
376
377struct AudioBufferSourceRenderer {
378    start_time: f64,
379    stop_time: f64,
380    offset: f64,
381    duration: f64,
382    buffer: Option<AudioBuffer>,
383    detune: AudioParamId,
384    playback_rate: AudioParamId,
385    loop_state: LoopState,
386    render_state: AudioBufferRendererState,
387}
388
389impl AudioBufferSourceRenderer {
390    fn handle_control_message(&mut self, control: &ControlMessage) {
391        match control {
392            ControlMessage::StartWithOffsetAndDuration(when, offset, duration) => {
393                self.start_time = *when;
394                self.offset = *offset;
395                self.duration = *duration;
396            }
397            ControlMessage::Stop(when) => self.stop_time = *when,
398            ControlMessage::Loop(is_looping) => self.loop_state.is_looping = *is_looping,
399            ControlMessage::LoopStart(loop_start) => self.loop_state.start = *loop_start,
400            ControlMessage::LoopEnd(loop_end) => self.loop_state.end = *loop_end,
401        }
402
403        self.clamp_loop_boundaries();
404    }
405
406    fn clamp_loop_boundaries(&mut self) {
407        if let Some(buffer) = &self.buffer {
408            let duration = buffer.duration();
409
410            // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart
411            if self.loop_state.start < 0. {
412                self.loop_state.start = 0.;
413            } else if self.loop_state.start > duration {
414                self.loop_state.start = duration;
415            }
416
417            // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend
418            if self.loop_state.end <= 0. || self.loop_state.end > duration {
419                self.loop_state.end = duration;
420            }
421        }
422    }
423}
424
425impl AudioProcessor for AudioBufferSourceRenderer {
426    fn process(
427        &mut self,
428        _inputs: &[AudioRenderQuantum], // This node has no input
429        outputs: &mut [AudioRenderQuantum],
430        params: AudioParamValues<'_>,
431        scope: &AudioWorkletGlobalScope,
432    ) -> bool {
433        // Single output node
434        let output = &mut outputs[0];
435
436        if self.render_state.ended {
437            output.make_silent();
438            return false;
439        }
440
441        let sample_rate = scope.sample_rate as f64;
442        let dt = 1. / sample_rate;
443        let block_duration = dt * RENDER_QUANTUM_SIZE as f64;
444        let next_block_time = scope.current_time + block_duration;
445
446        // if start called and buffer is null, should fire ended event and ignore
447        // any subsequent buffer assignment.
448        // cf. wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/audiobuffersource-start-null-buffer.html
449        if self.buffer.is_none() && self.start_time != f64::MAX {
450            output.make_silent();
451            self.render_state.ended = true;
452            scope.send_ended_event();
453            return false;
454        }
455
456        // Return early if start_time is beyond this block
457        if self.start_time >= next_block_time {
458            output.make_silent();
459            // stop before start
460            if self.stop_time <= next_block_time {
461                self.render_state.ended = true;
462                scope.send_ended_event();
463                return false;
464            }
465
466            // #462 AudioScheduledSourceNodes that have not been scheduled to start can safely
467            // return tail_time false in order to be collected if their control handle drops.
468            return self.start_time != f64::MAX;
469        }
470
471        // If the buffer has not been set wait for it.
472        let buffer = match &self.buffer {
473            None => {
474                output.make_silent();
475                // #462 like the above arm, we can safely return tail_time false
476                // if this node has no buffer set.
477                return false;
478            }
479            Some(b) => b,
480        };
481
482        let LoopState {
483            is_looping,
484            start: loop_start,
485            end: loop_end,
486        } = self.loop_state;
487
488        // these will only be used if `loop_` is true, so no need for `Option`
489        let mut actual_loop_start = 0.;
490        let mut actual_loop_end = 0.;
491
492        // compute compound parameter at k-rate, these parameters have constraints
493        // https://webaudio.github.io/web-audio-api/#audioparam-automation-rate-constraints
494        let detune = params.get(&self.detune)[0] as f64;
495        let playback_rate = params.get(&self.playback_rate)[0] as f64;
496        let computed_playback_rate = playback_rate * (detune / 1200.).exp2();
497
498        let buffer_length = buffer.length();
499        let buffer_duration = buffer.duration();
500        // multiplier to be applied on `position` to tackle possible difference
501        // between the context and buffer sample rates. As this is an edge case,
502        // we just linearly interpolate, thus favoring performance vs quality
503        let sampling_ratio = buffer.sample_rate() as f64 / sample_rate;
504
505        // Load the buffer time from the render state.
506        // The render state has to be updated before leaving this method!
507        let mut buffer_time = self.render_state.buffer_time.load(Ordering::Relaxed);
508
509        output.set_number_of_channels(buffer.number_of_channels());
510
511        // go through the algorithm described in the spec
512        // @see <https://webaudio.github.io/web-audio-api/#playback-AudioBufferSourceNode>
513        let block_time = scope.current_time;
514
515        // prevent scheduling in the past
516        // If 0 is passed in for this value or if the value is less than
517        // currentTime, then the sound will start playing immediately
518        // cf. https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start-when-when
519        if !self.render_state.started && self.start_time < block_time {
520            self.start_time = block_time;
521        }
522
523        // Define if we can avoid the resampling interpolation in some common cases,
524        // basically when:
525        // - `src.start()` is called with `audio_context.current_time`,
526        //   i.e. start time is aligned with a render quantum block
527        // - the AudioBuffer was decoded w/ the right sample rate
528        // - no detune or playback_rate changes are made
529        // - loop boundaries have not been changed
530        if self.start_time == block_time && self.offset == 0. {
531            self.render_state.is_aligned = true;
532        }
533
534        // these two case imply resampling
535        if sampling_ratio != 1. || computed_playback_rate != 1. {
536            self.render_state.is_aligned = false;
537        }
538
539        // If loop points are not aligned on sample, they can imply resampling.
540        // For now we just consider that we can go fast track if loop points are
541        // bound to the buffer boundaries.
542        //
543        // By default, cf. clamp_loop_boundaries, loop_start == 0 && loop_end == buffer_duration,
544        if loop_start != 0. || loop_end != buffer_duration {
545            self.render_state.is_aligned = false;
546        }
547
548        // If some user defined end of rendering, i.e. explicit stop_time or duration,
549        // is within this render quantum force slow track as well. It might imply
550        // resampling e.g. if stop_time is between 2 samples
551        if buffer_time + block_duration > self.duration
552            || block_time + block_duration > self.stop_time
553        {
554            self.render_state.is_aligned = false;
555        }
556
557        if self.render_state.is_aligned {
558            // ---------------------------------------------------------------
559            // Fast track
560            // ---------------------------------------------------------------
561            if self.start_time == block_time {
562                self.render_state.started = true;
563            }
564
565            // buffer ends within this block
566            if buffer_time + block_duration > buffer_duration {
567                let end_index = buffer.length();
568                // In case of a loop point in the middle of the block, this value will
569                // be used to recompute `buffer_time` according to the actual loop point.
570                let mut loop_point_index: Option<usize> = None;
571
572                buffer
573                    .channels()
574                    .iter()
575                    .zip(output.channels_mut().iter_mut())
576                    .for_each(|(buffer_channel, output_channel)| {
577                        // we need to recompute that for each channel
578                        let buffer_channel = buffer_channel.as_slice();
579                        let mut start_index = (buffer_time * sample_rate).round() as usize;
580                        let mut offset = 0;
581
582                        for (index, o) in output_channel.iter_mut().enumerate() {
583                            let mut buffer_index = start_index + index - offset;
584
585                            *o = if buffer_index < end_index {
586                                buffer_channel[buffer_index]
587                            } else {
588                                if is_looping && buffer_index >= end_index {
589                                    loop_point_index = Some(index);
590                                    // reset values for the rest of the block
591                                    start_index = 0;
592                                    offset = index;
593                                    buffer_index = 0;
594                                }
595
596                                if is_looping {
597                                    buffer_channel[buffer_index]
598                                } else {
599                                    0.
600                                }
601                            };
602                        }
603                    });
604
605                if let Some(loop_point_index) = loop_point_index {
606                    buffer_time = ((RENDER_QUANTUM_SIZE - loop_point_index) as f64 / sample_rate)
607                        % buffer_duration;
608                } else {
609                    buffer_time += block_duration;
610                }
611            } else {
612                let start_index = (buffer_time * sample_rate).round() as usize;
613                let end_index = start_index + RENDER_QUANTUM_SIZE;
614                // we can do memcopy
615                buffer
616                    .channels()
617                    .iter()
618                    .zip(output.channels_mut().iter_mut())
619                    .for_each(|(buffer_channel, output_channel)| {
620                        let buffer_channel = buffer_channel.as_slice();
621                        output_channel.copy_from_slice(&buffer_channel[start_index..end_index]);
622                    });
623
624                buffer_time += block_duration;
625            }
626
627            self.render_state.buffer_time_elapsed += block_duration;
628        } else {
629            // ---------------------------------------------------------------
630            // Slow track
631            // ---------------------------------------------------------------
632            if is_looping {
633                if loop_start >= 0. && loop_end > 0. && loop_start < loop_end {
634                    actual_loop_start = loop_start;
635                    actual_loop_end = loop_end;
636                } else {
637                    actual_loop_start = 0.;
638                    actual_loop_end = buffer_duration;
639                }
640            } else {
641                self.render_state.entered_loop = false;
642            }
643
644            // internal buffer used to store playback infos to compute the samples
645            // according to the source buffer. (prev_sample_index, k)
646            let mut playback_infos = [None; RENDER_QUANTUM_SIZE];
647
648            // compute position for each sample and store into `self.positions`
649            for (i, playback_info) in playback_infos.iter_mut().enumerate() {
650                let current_time = block_time + i as f64 * dt;
651
652                // Sticky behavior to handle floating point errors due to start time computation
653                // cf. test_subsample_buffer_stitching
654                if !self.render_state.started && almost::equal(current_time, self.start_time) {
655                    self.start_time = current_time;
656                }
657
658                if almost::equal(self.render_state.buffer_time_elapsed, self.duration) {
659                    self.render_state.buffer_time_elapsed = self.duration;
660                }
661
662                // Handle following cases:
663                // - we are before start time
664                // - we are after stop time
665                // - explicit duration (in buffer time reference) has been given and we have reached it
666                // Note that checking against buffer duration is done below to handle looping
667                if current_time < self.start_time
668                    || current_time >= self.stop_time
669                    || self.render_state.buffer_time_elapsed >= self.duration
670                {
671                    continue; // nothing more to do for this sample
672                }
673
674                // we have now reached start time
675                if !self.render_state.started {
676                    let delta = current_time - self.start_time;
677                    // handle that start time may be between last sample and this one
678                    self.offset += delta * computed_playback_rate;
679                    // clamp offset to buffer boundaries
680                    self.offset = self.offset.max(0.).min(buffer_duration);
681
682                    if is_looping && computed_playback_rate >= 0. && self.offset > actual_loop_end {
683                        self.offset = actual_loop_end;
684                    }
685
686                    if is_looping && computed_playback_rate < 0. && self.offset < actual_loop_start
687                    {
688                        self.offset = actual_loop_start;
689                    }
690
691                    buffer_time = self.offset;
692                    self.render_state.buffer_time_elapsed = (delta * computed_playback_rate).abs();
693                    self.render_state.started = true;
694                }
695
696                if is_looping {
697                    if almost::equal(buffer_time, actual_loop_end) {
698                        buffer_time = actual_loop_end;
699                    }
700
701                    if almost::equal(buffer_time, actual_loop_start) {
702                        buffer_time = actual_loop_start;
703                    }
704
705                    if !self.render_state.entered_loop {
706                        // playback began before or within loop, and playhead is now past loop start
707                        if self.offset < actual_loop_end && buffer_time >= actual_loop_start {
708                            self.render_state.entered_loop = true;
709                        }
710
711                        // playback began after loop, and playhead is now prior to the loop end
712                        if self.offset >= actual_loop_end && buffer_time < actual_loop_end {
713                            self.render_state.entered_loop = true;
714                        }
715                    }
716
717                    // check loop boundaries
718                    if self.render_state.entered_loop {
719                        while buffer_time >= actual_loop_end {
720                            buffer_time -= actual_loop_end - actual_loop_start;
721                        }
722
723                        while buffer_time < actual_loop_start {
724                            buffer_time += actual_loop_end - actual_loop_start;
725                        }
726                    }
727                }
728
729                if almost::zero(buffer_time) {
730                    buffer_time = 0.
731                }
732
733                if buffer_time >= 0. && buffer_time < buffer_duration {
734                    let position = buffer_time * sampling_ratio;
735                    let playhead = position * sample_rate;
736                    let playhead_floored = playhead.floor();
737                    let prev_frame_index = playhead_floored as usize; // can't be < 0.
738                    let k = playhead - playhead_floored;
739
740                    // Due to how buffer_time is computed, we can still run into
741                    // floating point errors and try to access a non existing index
742                    // cf. test_end_of_file_slow_track_2
743                    if prev_frame_index < buffer_length {
744                        *playback_info = Some(PlaybackInfo {
745                            prev_frame_index,
746                            k,
747                        });
748                    }
749                }
750
751                let time_incr = dt * computed_playback_rate;
752                buffer_time += time_incr;
753                self.render_state.buffer_time_elapsed += time_incr.abs();
754            }
755
756            // fill output according to computed positions
757            buffer
758                .channels()
759                .iter()
760                .zip(output.channels_mut().iter_mut())
761                .for_each(|(buffer_channel, output_channel)| {
762                    let buffer_channel = buffer_channel.as_slice();
763
764                    playback_infos
765                        .iter()
766                        .zip(output_channel.iter_mut())
767                        .for_each(|(playhead, o)| {
768                            *o = match playhead {
769                                Some(PlaybackInfo {
770                                    prev_frame_index,
771                                    k,
772                                }) => {
773                                    // `prev_frame_index` cannot be out of bounds
774                                    let prev_sample = buffer_channel[*prev_frame_index] as f64;
775                                    let next_sample = match buffer_channel.get(prev_frame_index + 1)
776                                    {
777                                        Some(val) => *val as f64,
778                                        // End of buffer
779                                        None => {
780                                            if is_looping {
781                                                if playback_rate >= 0. {
782                                                    let start_playhead =
783                                                        actual_loop_start * sample_rate;
784                                                    let start_index = if start_playhead.floor()
785                                                        == start_playhead
786                                                    {
787                                                        start_playhead as usize
788                                                    } else {
789                                                        start_playhead as usize + 1
790                                                    };
791
792                                                    buffer_channel[start_index] as f64
793                                                } else {
794                                                    let end_playhead =
795                                                        actual_loop_end * sample_rate;
796                                                    let end_index = end_playhead as usize;
797                                                    buffer_channel[end_index] as f64
798                                                }
799                                            } else {
800                                                // Handle 2 edge cases:
801                                                // 1. We are in a case where buffer time is below buffer
802                                                // duration due to floating point errors, but where
803                                                // prev_frame_index is last index and k is near 1. We can't
804                                                // filter this case before, because it might break
805                                                // loops logic.
806                                                // 2. Buffer contains only one sample
807                                                if almost::equal(*k, 1.) || *prev_frame_index == 0 {
808                                                    0.
809                                                } else {
810                                                    // Extrapolate next sample using the last two known samples
811                                                    // cf. https://github.com/WebAudio/web-audio-api/issues/2032
812                                                    let prev_prev_sample =
813                                                        buffer_channel[*prev_frame_index - 1];
814                                                    2. * prev_sample - prev_prev_sample as f64
815                                                }
816                                            }
817                                        }
818                                    };
819
820                                    (1. - k).mul_add(prev_sample, k * next_sample) as f32
821                                }
822                                None => 0.,
823                            };
824                        });
825                });
826        }
827
828        // Update render state
829        self.render_state
830            .buffer_time
831            .store(buffer_time, Ordering::Relaxed);
832
833        // The buffer has ended within this block, if one of the following conditions holds:
834        // 1. the stop time has been reached.
835        // 2. the duration has been reached.
836        // 3. the end of the buffer has been reached.
837        if next_block_time >= self.stop_time
838            || self.render_state.buffer_time_elapsed >= self.duration
839            || !is_looping
840                && (computed_playback_rate > 0. && buffer_time >= buffer_duration
841                    || computed_playback_rate < 0. && buffer_time < 0.)
842        {
843            self.render_state.ended = true;
844            scope.send_ended_event();
845        }
846
847        true
848    }
849
850    fn onmessage(&mut self, msg: &mut dyn Any) {
851        if let Some(control) = msg.downcast_ref::<ControlMessage>() {
852            self.handle_control_message(control);
853            return;
854        };
855
856        if let Some(buffer) = msg.downcast_mut::<AudioBuffer>() {
857            if let Some(current_buffer) = &mut self.buffer {
858                // Avoid deallocation in the render thread by swapping the buffers.
859                std::mem::swap(current_buffer, buffer);
860            } else {
861                // Creating the tombstone buffer does not cause allocations.
862                let tombstone_buffer = AudioBuffer {
863                    channels: Default::default(),
864                    sample_rate: Default::default(),
865                };
866                self.buffer = Some(std::mem::replace(buffer, tombstone_buffer));
867                self.clamp_loop_boundaries();
868            }
869            return;
870        };
871
872        log::warn!("AudioBufferSourceRenderer: Dropping incoming message {msg:?}");
873    }
874
875    fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) {
876        if !self.render_state.ended
877            && (scope.current_time >= self.start_time || scope.current_time >= self.stop_time)
878        {
879            scope.send_ended_event();
880            self.render_state.ended = true;
881        }
882    }
883}
884
885#[cfg(test)]
886mod tests {
887    use float_eq::assert_float_eq;
888    use std::f32::consts::PI;
889    use std::sync::atomic::{AtomicBool, Ordering};
890    use std::sync::{Arc, Mutex};
891
892    use crate::context::{BaseAudioContext, OfflineAudioContext};
893    use crate::AudioBufferOptions;
894    use crate::RENDER_QUANTUM_SIZE;
895
896    use super::*;
897
898    #[test]
899    fn test_construct_with_options_and_run() {
900        let sample_rate = 44100.;
901        let length = RENDER_QUANTUM_SIZE;
902        let mut context = OfflineAudioContext::new(1, length, sample_rate);
903
904        let buffer = AudioBuffer::from(vec![vec![1.; RENDER_QUANTUM_SIZE]], sample_rate);
905        let options = AudioBufferSourceOptions {
906            buffer: Some(buffer),
907            ..Default::default()
908        };
909        let mut src = AudioBufferSourceNode::new(&context, options);
910        src.connect(&context.destination());
911        src.start();
912        let res = context.start_rendering_sync();
913
914        assert_float_eq!(
915            res.channel_data(0).as_slice()[..],
916            &[1.; RENDER_QUANTUM_SIZE][..],
917            abs_all <= 0.
918        );
919    }
920
921    #[test]
922    fn test_playing_some_file() {
923        let context = OfflineAudioContext::new(2, RENDER_QUANTUM_SIZE, 44_100.);
924
925        let file = std::fs::File::open("samples/sample.wav").unwrap();
926        let expected = context.decode_audio_data_sync(file).unwrap();
927
928        // 44100 will go through fast track
929        // 48000 will go through slow track
930        [44100, 48000].iter().for_each(|sr| {
931            let decoding_context = OfflineAudioContext::new(2, RENDER_QUANTUM_SIZE, *sr as f32);
932
933            let mut filename = "samples/sample-".to_owned();
934            filename.push_str(&sr.to_string());
935            filename.push_str(".wav");
936
937            let file = std::fs::File::open("samples/sample.wav").unwrap();
938            let audio_buffer = decoding_context.decode_audio_data_sync(file).unwrap();
939
940            assert_eq!(audio_buffer.sample_rate(), *sr as f32);
941
942            let mut context = OfflineAudioContext::new(2, RENDER_QUANTUM_SIZE, 44_100.);
943
944            let mut src = context.create_buffer_source();
945            src.set_buffer(audio_buffer);
946            src.connect(&context.destination());
947            src.start_at(context.current_time());
948            src.stop_at(context.current_time() + 128.);
949
950            let res = context.start_rendering_sync();
951            let diff_abs = if *sr == 44100 {
952                0. // fast track
953            } else {
954                5e-3 // slow track w/ linear interpolation
955            };
956
957            // asserting length() is meaningless as this is controlled by the context
958            assert_eq!(res.number_of_channels(), expected.number_of_channels());
959
960            // check first 128 samples in left and right channels
961            assert_float_eq!(
962                res.channel_data(0).as_slice()[..],
963                expected.get_channel_data(0)[0..128],
964                abs_all <= diff_abs
965            );
966
967            assert_float_eq!(
968                res.channel_data(1).as_slice()[..],
969                expected.get_channel_data(1)[0..128],
970                abs_all <= diff_abs
971            );
972        });
973    }
974
975    // slow track
976    #[test]
977    fn test_sub_quantum_start_1() {
978        let sample_rate = 48_000.;
979        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
980
981        let mut dirac = context.create_buffer(1, 1, sample_rate);
982        dirac.copy_to_channel(&[1.], 0);
983
984        let mut src = context.create_buffer_source();
985        src.connect(&context.destination());
986        src.set_buffer(dirac);
987        src.start_at(1. / sample_rate as f64);
988
989        let result = context.start_rendering_sync();
990        let channel = result.get_channel_data(0);
991
992        let mut expected = vec![0.; RENDER_QUANTUM_SIZE];
993        expected[1] = 1.;
994
995        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
996    }
997
998    // adapted from the-audio-api/the-audiobuffersourcenode-interface/sample-accurate-scheduling.html
999    #[test]
1000    fn test_sub_quantum_start_2() {
1001        let sample_rate = 44_100.;
1002        let length_in_seconds = 4.;
1003        let mut context =
1004            OfflineAudioContext::new(2, (length_in_seconds * sample_rate) as usize, sample_rate);
1005
1006        let mut dirac = context.create_buffer(2, 512, sample_rate);
1007        dirac.copy_to_channel(&[1.], 0);
1008        dirac.copy_to_channel(&[1.], 1);
1009
1010        let sample_offsets = [0, 3, 512, 517, 1000, 1005, 20000, 21234, 37590];
1011
1012        sample_offsets.iter().for_each(|index| {
1013            let time_in_seconds = *index as f64 / sample_rate as f64;
1014
1015            let mut src = context.create_buffer_source();
1016            src.set_buffer(dirac.clone());
1017            src.connect(&context.destination());
1018            src.start_at(time_in_seconds);
1019        });
1020
1021        let res = context.start_rendering_sync();
1022
1023        let channel_left = res.get_channel_data(0);
1024        let channel_right = res.get_channel_data(1);
1025        // assert lef and right channels are equal
1026        assert_float_eq!(channel_left[..], channel_right[..], abs_all <= 0.);
1027        // assert we got our dirac at each defined offsets
1028
1029        sample_offsets.iter().for_each(|index| {
1030            assert_ne!(
1031                channel_left[*index], 0.,
1032                "non zero sample at index {:?}",
1033                index
1034            );
1035        });
1036    }
1037
1038    #[test]
1039    fn test_sub_sample_start() {
1040        // sub sample
1041        let sample_rate = 48_000.;
1042        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1043
1044        let mut dirac = context.create_buffer(1, 1, sample_rate);
1045        dirac.copy_to_channel(&[1.], 0);
1046
1047        let mut src = context.create_buffer_source();
1048        src.connect(&context.destination());
1049        src.set_buffer(dirac);
1050        src.start_at(1.5 / sample_rate as f64);
1051
1052        let result = context.start_rendering_sync();
1053        let channel = result.get_channel_data(0);
1054
1055        let mut expected = vec![0.; RENDER_QUANTUM_SIZE];
1056        expected[2] = 0.5;
1057
1058        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1059    }
1060
1061    #[test]
1062    fn test_sub_quantum_stop_fast_track() {
1063        let sample_rate = 48_000.;
1064        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1065
1066        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1067        dirac.copy_to_channel(&[0., 0., 0., 0., 1.], 0);
1068
1069        let mut src = context.create_buffer_source();
1070        src.connect(&context.destination());
1071        src.set_buffer(dirac);
1072        src.start_at(0. / sample_rate as f64);
1073        // stop at time of dirac, should not be played
1074        src.stop_at(4. / sample_rate as f64);
1075
1076        let result = context.start_rendering_sync();
1077        let channel = result.get_channel_data(0);
1078        let expected = vec![0.; RENDER_QUANTUM_SIZE];
1079
1080        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1081    }
1082
1083    #[test]
1084    fn test_sub_quantum_stop_slow_track() {
1085        let sample_rate = 48_000.;
1086        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1087
1088        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1089        dirac.copy_to_channel(&[0., 0., 0., 1.], 0);
1090
1091        let mut src = context.create_buffer_source();
1092        src.connect(&context.destination());
1093        src.set_buffer(dirac);
1094
1095        src.start_at(1. / sample_rate as f64);
1096        src.stop_at(4. / sample_rate as f64);
1097
1098        let result = context.start_rendering_sync();
1099        let channel = result.get_channel_data(0);
1100        let expected = vec![0.; RENDER_QUANTUM_SIZE];
1101
1102        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1103    }
1104
1105    #[test]
1106    fn test_sub_sample_stop_fast_track() {
1107        let sample_rate = 48_000.;
1108        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1109
1110        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1111        dirac.copy_to_channel(&[0., 0., 0., 0., 1., 1.], 0);
1112
1113        let mut src = context.create_buffer_source();
1114        src.connect(&context.destination());
1115        src.set_buffer(dirac);
1116        src.start_at(0. / sample_rate as f64);
1117        // stop at between two diracs, only first one should be played
1118        src.stop_at(4.5 / sample_rate as f64);
1119
1120        let result = context.start_rendering_sync();
1121        let channel = result.get_channel_data(0);
1122
1123        let mut expected = vec![0.; 128];
1124        expected[4] = 1.;
1125
1126        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1127    }
1128
1129    #[test]
1130    fn test_sub_sample_stop_slow_track() {
1131        let sample_rate = 48_000.;
1132        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1133
1134        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1135        dirac.copy_to_channel(&[0., 0., 0., 0., 1., 1.], 0);
1136
1137        let mut src = context.create_buffer_source();
1138        src.connect(&context.destination());
1139        src.set_buffer(dirac);
1140        src.start_at(1. / sample_rate as f64);
1141        // stop at between two diracs, only first one should be played
1142        src.stop_at(5.5 / sample_rate as f64);
1143
1144        let result = context.start_rendering_sync();
1145        let channel = result.get_channel_data(0);
1146
1147        let mut expected = vec![0.; 128];
1148        expected[5] = 1.;
1149
1150        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1151    }
1152
1153    #[test]
1154    fn test_start_in_the_past() {
1155        let sample_rate = 48_000.;
1156        let mut context = OfflineAudioContext::new(1, 2 * RENDER_QUANTUM_SIZE, sample_rate);
1157
1158        let mut dirac = context.create_buffer(1, 1, sample_rate);
1159        dirac.copy_to_channel(&[1.], 0);
1160
1161        context.suspend_sync((128. / sample_rate).into(), |context| {
1162            let mut src = context.create_buffer_source();
1163            src.connect(&context.destination());
1164            src.set_buffer(dirac);
1165            src.start_at(0.);
1166        });
1167
1168        let result = context.start_rendering_sync();
1169        let channel = result.get_channel_data(0);
1170
1171        let mut expected = vec![0.; 2 * RENDER_QUANTUM_SIZE];
1172        expected[128] = 1.;
1173
1174        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1175    }
1176
1177    #[test]
1178    fn test_audio_buffer_resampling() {
1179        [22_500, 38_000, 43_800, 48_000, 96_000]
1180            .iter()
1181            .for_each(|sr| {
1182                let freq = 1.;
1183                let base_sr = 44_100;
1184                let mut context = OfflineAudioContext::new(1, base_sr, base_sr as f32);
1185
1186                // 1Hz sine at different sample rates
1187                let buf_sr = *sr;
1188                // safe cast for sample rate, see discussion at #113
1189                let sample_rate = buf_sr as f32;
1190                let mut buffer = context.create_buffer(1, buf_sr, sample_rate);
1191                let mut sine = vec![];
1192
1193                for i in 0..buf_sr {
1194                    let phase = freq * i as f32 / buf_sr as f32 * 2. * PI;
1195                    let sample = phase.sin();
1196                    sine.push(sample);
1197                }
1198
1199                buffer.copy_to_channel(&sine[..], 0);
1200
1201                let mut src = context.create_buffer_source();
1202                src.connect(&context.destination());
1203                src.set_buffer(buffer);
1204                src.start_at(0. / sample_rate as f64);
1205
1206                let result = context.start_rendering_sync();
1207                let channel = result.get_channel_data(0);
1208
1209                // 1Hz sine at audio context sample rate
1210                let mut expected = vec![];
1211
1212                for i in 0..base_sr {
1213                    let phase = freq * i as f32 / base_sr as f32 * 2. * PI;
1214                    let sample = phase.sin();
1215                    expected.push(sample);
1216                }
1217
1218                assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1219            });
1220    }
1221
1222    #[test]
1223    fn test_playback_rate() {
1224        let sample_rate = 44_100;
1225        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1226
1227        let mut buffer = context.create_buffer(1, sample_rate, sample_rate as f32);
1228        let mut sine = vec![];
1229
1230        // 1 Hz sine
1231        for i in 0..sample_rate {
1232            let phase = i as f32 / sample_rate as f32 * 2. * PI;
1233            let sample = phase.sin();
1234            sine.push(sample);
1235        }
1236
1237        buffer.copy_to_channel(&sine[..], 0);
1238
1239        let mut src = context.create_buffer_source();
1240        src.connect(&context.destination());
1241        src.set_buffer(buffer);
1242        src.playback_rate.set_value(0.5);
1243        src.start();
1244
1245        let result = context.start_rendering_sync();
1246        let channel = result.get_channel_data(0);
1247
1248        // 0.5 Hz sine
1249        let mut expected = vec![];
1250
1251        for i in 0..sample_rate {
1252            let phase = i as f32 / sample_rate as f32 * PI;
1253            let sample = phase.sin();
1254            expected.push(sample);
1255        }
1256
1257        assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1258    }
1259
1260    #[test]
1261    fn test_negative_playback_rate() {
1262        let sample_rate = 44_100;
1263        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1264
1265        let mut buffer = context.create_buffer(1, sample_rate, sample_rate as f32);
1266        let mut sine = vec![];
1267
1268        // 1 Hz sine
1269        for i in 0..sample_rate {
1270            let phase = i as f32 / sample_rate as f32 * 2. * PI;
1271            let sample = phase.sin();
1272            sine.push(sample);
1273        }
1274
1275        buffer.copy_to_channel(&sine[..], 0);
1276
1277        let mut src = context.create_buffer_source();
1278        src.connect(&context.destination());
1279        src.set_buffer(buffer.clone());
1280        src.playback_rate.set_value(-1.);
1281        src.start_at_with_offset(context.current_time(), buffer.duration());
1282
1283        let result = context.start_rendering_sync();
1284        let channel = result.get_channel_data(0);
1285
1286        // -1 Hz sine
1287        let mut expected: Vec<f32> = sine.into_iter().rev().collect();
1288        // offset is at duration (after last sample), then result will start
1289        // with a zero value
1290        expected.pop();
1291        expected.insert(0, 0.);
1292
1293        assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1294    }
1295
1296    #[test]
1297    fn test_detune() {
1298        let sample_rate = 44_100;
1299        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1300
1301        let mut buffer = context.create_buffer(1, sample_rate, sample_rate as f32);
1302        let mut sine = vec![];
1303
1304        // 1 Hz sine
1305        for i in 0..sample_rate {
1306            let phase = i as f32 / sample_rate as f32 * 2. * PI;
1307            let sample = phase.sin();
1308            sine.push(sample);
1309        }
1310
1311        buffer.copy_to_channel(&sine[..], 0);
1312
1313        let mut src = context.create_buffer_source();
1314        src.connect(&context.destination());
1315        src.set_buffer(buffer);
1316        src.detune.set_value(-1200.);
1317        src.start();
1318
1319        let result = context.start_rendering_sync();
1320        let channel = result.get_channel_data(0);
1321
1322        // 0.5 Hz sine
1323        let mut expected = vec![];
1324
1325        for i in 0..sample_rate {
1326            let phase = i as f32 / sample_rate as f32 * PI;
1327            let sample = phase.sin();
1328            expected.push(sample);
1329        }
1330
1331        assert_float_eq!(channel[..], expected[..], abs_all <= 1e-6);
1332    }
1333
1334    #[test]
1335    fn test_end_of_file_fast_track() {
1336        let sample_rate = 48_000.;
1337        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate);
1338
1339        let mut buffer = context.create_buffer(1, 129, sample_rate);
1340        let mut data = vec![0.; 129];
1341        data[0] = 1.;
1342        data[128] = 1.;
1343        buffer.copy_to_channel(&data, 0);
1344
1345        let mut src = context.create_buffer_source();
1346        src.connect(&context.destination());
1347        src.set_buffer(buffer);
1348        src.start_at(0. / sample_rate as f64);
1349
1350        let result = context.start_rendering_sync();
1351        let channel = result.get_channel_data(0);
1352
1353        let mut expected = vec![0.; 256];
1354        expected[0] = 1.;
1355        expected[128] = 1.;
1356
1357        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1358    }
1359
1360    #[test]
1361    fn test_end_of_file_slow_track_1() {
1362        let sample_rate = 48_000.;
1363        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate);
1364
1365        let mut buffer = context.create_buffer(1, 129, sample_rate);
1366        let mut data = vec![0.; 129];
1367        data[0] = 1.;
1368        data[128] = 1.;
1369        buffer.copy_to_channel(&data, 0);
1370
1371        let mut src = context.create_buffer_source();
1372        src.connect(&context.destination());
1373        src.set_buffer(buffer);
1374        src.start_at(1. / sample_rate as f64);
1375
1376        let result = context.start_rendering_sync();
1377        let channel = result.get_channel_data(0);
1378
1379        let mut expected = vec![0.; 256];
1380        expected[1] = 1.;
1381        expected[129] = 1.;
1382
1383        assert_float_eq!(channel[..], expected[..], abs_all <= 1e-10);
1384    }
1385
1386    #[test]
1387    fn test_with_duration_0() {
1388        let sample_rate = 48_000.;
1389        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1390
1391        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1392        dirac.copy_to_channel(&[0., 0., 0., 0., 1., 1.], 0);
1393
1394        let mut src = context.create_buffer_source();
1395        src.connect(&context.destination());
1396        src.set_buffer(dirac);
1397        // duration is between two diracs, only first one should be played
1398        src.start_at_with_offset_and_duration(0., 0., 4.5 / sample_rate as f64);
1399
1400        let result = context.start_rendering_sync();
1401        let channel = result.get_channel_data(0);
1402
1403        let mut expected = vec![0.; 128];
1404        expected[4] = 1.;
1405
1406        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1407    }
1408
1409    #[test]
1410    fn test_with_duration_1() {
1411        let sample_rate = 48_000.;
1412        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1413
1414        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1415        dirac.copy_to_channel(&[0., 0., 0., 0., 1., 1.], 0);
1416
1417        let mut src = context.create_buffer_source();
1418        src.connect(&context.destination());
1419        src.set_buffer(dirac);
1420        // duration is between two diracs, only first one should be played
1421        // as we force slow track with start == 1. / sample_rate as f64
1422        // the expected dirac will be at index 5 instead of 4
1423        src.start_at_with_offset_and_duration(
1424            1. / sample_rate as f64,
1425            0. / sample_rate as f64,
1426            4.5 / sample_rate as f64,
1427        );
1428
1429        let result = context.start_rendering_sync();
1430        let channel = result.get_channel_data(0);
1431
1432        let mut expected = vec![0.; 128];
1433        expected[5] = 1.;
1434
1435        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1436    }
1437
1438    #[test]
1439    // port from wpt - sub-sample-scheduling.html / sub-sample-grain
1440    fn test_with_duration_2() {
1441        let sample_rate = 32_768.;
1442        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1443
1444        let mut buffer = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1445        buffer.copy_to_channel(&[1.; RENDER_QUANTUM_SIZE], 0);
1446
1447        let start_grain_index = 3.1;
1448        let end_grain_index = 37.2;
1449
1450        let mut src = context.create_buffer_source();
1451        src.connect(&context.destination());
1452        src.set_buffer(buffer);
1453
1454        src.start_at_with_offset_and_duration(
1455            start_grain_index / sample_rate as f64,
1456            0.,
1457            (end_grain_index - start_grain_index) / sample_rate as f64,
1458        );
1459
1460        let result = context.start_rendering_sync();
1461        let channel = result.get_channel_data(0);
1462
1463        let mut expected = [1.; RENDER_QUANTUM_SIZE];
1464        for s in expected
1465            .iter_mut()
1466            .take(start_grain_index.floor() as usize + 1)
1467        {
1468            *s = 0.;
1469        }
1470        for s in expected
1471            .iter_mut()
1472            .take(RENDER_QUANTUM_SIZE)
1473            .skip(end_grain_index.ceil() as usize)
1474        {
1475            *s = 0.;
1476        }
1477
1478        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1479    }
1480
1481    #[test]
1482    fn test_with_offset() {
1483        // offset always bypass slow track
1484        let sample_rate = 48_000.;
1485        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1486
1487        let mut dirac = context.create_buffer(1, RENDER_QUANTUM_SIZE, sample_rate);
1488        dirac.copy_to_channel(&[0., 0., 0., 0., 1., 1.], 0);
1489
1490        let mut src = context.create_buffer_source();
1491        src.connect(&context.destination());
1492        src.set_buffer(dirac);
1493        // duration is between two diracs, only first one should be played
1494        // as we force slow track with start == 1. / sample_rate as f64
1495        // the expected dirac will be at index 5 instead of 4
1496        src.start_at_with_offset_and_duration(
1497            0. / sample_rate as f64,
1498            1. / sample_rate as f64,
1499            3.5 / sample_rate as f64,
1500        );
1501
1502        let result = context.start_rendering_sync();
1503        let channel = result.get_channel_data(0);
1504
1505        let mut expected = vec![0.; 128];
1506        expected[3] = 1.;
1507
1508        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1509    }
1510
1511    #[test]
1512    fn test_null_buffer_start_ends_before_start_time() {
1513        let sample_rate = 48_000.;
1514        let mut context = OfflineAudioContext::new(1, sample_rate as usize, sample_rate);
1515
1516        let mut src = context.create_buffer_source();
1517        src.connect(&context.destination());
1518
1519        let ended = Arc::new(AtomicBool::new(false));
1520        let ended_clone = Arc::clone(&ended);
1521        src.set_onended(move |_| {
1522            ended_clone.store(true, Ordering::Relaxed);
1523        });
1524
1525        src.start_at(0.75);
1526        context.suspend_sync(0.5, move |context| {
1527            assert!(ended.load(Ordering::Relaxed));
1528            src.set_buffer(context.create_buffer(1, 1, sample_rate));
1529        });
1530
1531        let result = context.start_rendering_sync();
1532        assert_float_eq!(
1533            result.get_channel_data(0)[..],
1534            vec![0.; sample_rate as usize][..],
1535            abs_all <= 0.
1536        );
1537    }
1538
1539    #[test]
1540    fn test_reverse_playback_with_duration() {
1541        let sample_rate = 48_000.;
1542        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1543
1544        let mut buffer = context.create_buffer(1, 5, sample_rate);
1545        buffer.copy_to_channel(&[1., 2., 3., 4., 5.], 0);
1546
1547        let mut src = context.create_buffer_source();
1548        src.connect(&context.destination());
1549        src.set_buffer(buffer.clone());
1550        src.playback_rate().set_value(-1.);
1551        src.start_at_with_offset_and_duration(0., buffer.duration(), 2. / sample_rate as f64);
1552
1553        let result = context.start_rendering_sync();
1554        let mut expected = vec![0.; RENDER_QUANTUM_SIZE];
1555        expected[1] = 5.;
1556
1557        assert_float_eq!(result.get_channel_data(0)[..], expected[..], abs_all <= 0.);
1558    }
1559
1560    #[test]
1561    fn test_offset_larger_than_buffer_duration() {
1562        let sample_rate = 48_000.;
1563        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1564        let mut buffer = context.create_buffer(1, 13, sample_rate);
1565        buffer.copy_to_channel(&[1.; 13], 0);
1566
1567        let mut src = context.create_buffer_source();
1568        src.set_buffer(buffer);
1569        src.start_at_with_offset(0., 64. / sample_rate as f64); // offset larger than buffer size
1570
1571        let result = context.start_rendering_sync();
1572        let channel = result.get_channel_data(0);
1573
1574        let expected = [0.; RENDER_QUANTUM_SIZE];
1575        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1576    }
1577
1578    #[test]
1579    fn test_fast_track_loop_mono() {
1580        let sample_rate = 48_000.;
1581        let len = RENDER_QUANTUM_SIZE * 4;
1582
1583        for buffer_len in [
1584            RENDER_QUANTUM_SIZE / 2 - 1,
1585            RENDER_QUANTUM_SIZE / 2,
1586            RENDER_QUANTUM_SIZE / 2 + 1,
1587            RENDER_QUANTUM_SIZE - 1,
1588            RENDER_QUANTUM_SIZE,
1589            RENDER_QUANTUM_SIZE + 1,
1590            RENDER_QUANTUM_SIZE * 2 - 1,
1591            RENDER_QUANTUM_SIZE * 2,
1592            RENDER_QUANTUM_SIZE * 2 + 1,
1593        ] {
1594            let mut context = OfflineAudioContext::new(1, len, sample_rate);
1595
1596            let mut dirac = context.create_buffer(1, buffer_len, sample_rate);
1597            dirac.copy_to_channel(&[1.], 0);
1598
1599            let mut src = context.create_buffer_source();
1600            src.connect(&context.destination());
1601            src.set_loop(true);
1602            src.set_buffer(dirac);
1603            src.start();
1604
1605            let result = context.start_rendering_sync();
1606            let channel = result.get_channel_data(0);
1607
1608            let mut expected = vec![0.; len];
1609            for i in (0..len).step_by(buffer_len) {
1610                expected[i] = 1.;
1611            }
1612
1613            assert_float_eq!(channel[..], expected[..], abs_all <= 1e-10);
1614        }
1615    }
1616
1617    #[test]
1618    fn test_slow_track_loop_mono() {
1619        let sample_rate = 48_000.;
1620        let len = RENDER_QUANTUM_SIZE * 4;
1621
1622        for buffer_len in [
1623            RENDER_QUANTUM_SIZE / 2 - 1,
1624            RENDER_QUANTUM_SIZE / 2,
1625            RENDER_QUANTUM_SIZE / 2 + 1,
1626            RENDER_QUANTUM_SIZE - 1,
1627            RENDER_QUANTUM_SIZE,
1628            RENDER_QUANTUM_SIZE + 1,
1629            RENDER_QUANTUM_SIZE * 2 - 1,
1630            RENDER_QUANTUM_SIZE * 2,
1631            RENDER_QUANTUM_SIZE * 2 + 1,
1632        ] {
1633            let mut context = OfflineAudioContext::new(1, len, sample_rate);
1634
1635            let mut dirac = context.create_buffer(1, buffer_len, sample_rate);
1636            dirac.copy_to_channel(&[1.], 0);
1637
1638            let mut src = context.create_buffer_source();
1639            src.connect(&context.destination());
1640            src.set_loop(true);
1641            src.set_buffer(dirac);
1642            src.start_at(1. / sample_rate as f64);
1643
1644            let result = context.start_rendering_sync();
1645            let channel = result.get_channel_data(0);
1646
1647            let mut expected = vec![0.; len];
1648            for i in (1..len).step_by(buffer_len) {
1649                expected[i] = 1.;
1650            }
1651
1652            assert_float_eq!(channel[..], expected[..], abs_all <= 1e-9);
1653        }
1654    }
1655
1656    #[test]
1657    fn test_fast_track_loop_stereo() {
1658        let sample_rate = 48_000.;
1659        let len = RENDER_QUANTUM_SIZE * 4;
1660
1661        for buffer_len in [
1662            RENDER_QUANTUM_SIZE / 2 - 1,
1663            RENDER_QUANTUM_SIZE / 2,
1664            RENDER_QUANTUM_SIZE / 2 + 1,
1665            RENDER_QUANTUM_SIZE - 1,
1666            RENDER_QUANTUM_SIZE,
1667            RENDER_QUANTUM_SIZE + 1,
1668            RENDER_QUANTUM_SIZE * 2 - 1,
1669            RENDER_QUANTUM_SIZE * 2,
1670            RENDER_QUANTUM_SIZE * 2 + 1,
1671        ] {
1672            let mut context = OfflineAudioContext::new(2, len, sample_rate);
1673            let mut dirac = context.create_buffer(2, buffer_len, sample_rate);
1674            dirac.copy_to_channel(&[1.], 0);
1675            dirac.copy_to_channel(&[0., 1.], 1);
1676
1677            let mut src = context.create_buffer_source();
1678            src.connect(&context.destination());
1679            src.set_loop(true);
1680            src.set_buffer(dirac);
1681            src.start();
1682
1683            let result = context.start_rendering_sync();
1684
1685            let mut expected_left: Vec<f32> = vec![0.; len];
1686            let mut expected_right = vec![0.; len];
1687            for i in (0..len).step_by(buffer_len) {
1688                expected_left[i] = 1.;
1689
1690                if i < expected_right.len() - 1 {
1691                    expected_right[i + 1] = 1.;
1692                }
1693            }
1694
1695            assert_float_eq!(
1696                result.get_channel_data(0)[..],
1697                expected_left[..],
1698                abs_all <= 1e-10
1699            );
1700            assert_float_eq!(
1701                result.get_channel_data(1)[..],
1702                expected_right[..],
1703                abs_all <= 1e-10
1704            );
1705        }
1706    }
1707
1708    #[test]
1709    fn test_slow_track_loop_stereo() {
1710        let sample_rate = 48_000.;
1711        let len = RENDER_QUANTUM_SIZE * 4;
1712
1713        for buffer_len in [
1714            RENDER_QUANTUM_SIZE / 2 - 1,
1715            RENDER_QUANTUM_SIZE / 2,
1716            RENDER_QUANTUM_SIZE / 2 + 1,
1717            RENDER_QUANTUM_SIZE - 1,
1718            RENDER_QUANTUM_SIZE,
1719            RENDER_QUANTUM_SIZE + 1,
1720            RENDER_QUANTUM_SIZE * 2 - 1,
1721            RENDER_QUANTUM_SIZE * 2,
1722            RENDER_QUANTUM_SIZE * 2 + 1,
1723        ] {
1724            let mut context = OfflineAudioContext::new(2, len, sample_rate);
1725            let mut dirac = context.create_buffer(2, buffer_len, sample_rate);
1726            dirac.copy_to_channel(&[1.], 0);
1727            dirac.copy_to_channel(&[0., 1.], 1);
1728
1729            let mut src = context.create_buffer_source();
1730            src.connect(&context.destination());
1731            src.set_loop(true);
1732            src.set_buffer(dirac);
1733            src.start_at(1. / sample_rate as f64);
1734
1735            let result = context.start_rendering_sync();
1736
1737            let mut expected_left: Vec<f32> = vec![0.; len];
1738            let mut expected_right = vec![0.; len];
1739            for i in (1..len).step_by(buffer_len) {
1740                expected_left[i] = 1.;
1741
1742                if i < expected_right.len() - 1 {
1743                    expected_right[i + 1] = 1.;
1744                }
1745            }
1746
1747            assert_float_eq!(
1748                result.get_channel_data(0)[..],
1749                expected_left[..],
1750                abs_all <= 1e-9
1751            );
1752            assert_float_eq!(
1753                result.get_channel_data(1)[..],
1754                expected_right[..],
1755                abs_all <= 1e-9
1756            );
1757        }
1758    }
1759
1760    #[test]
1761    fn test_reverse_loop_boundaries() {
1762        let sample_rate = 48_000.;
1763        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1764
1765        let mut buffer = context.create_buffer(1, 5, sample_rate);
1766        buffer.copy_to_channel(&[1., 2., 3., 4., 5.], 0);
1767
1768        let mut src = context.create_buffer_source();
1769        src.connect(&context.destination());
1770        src.set_buffer(buffer);
1771        src.set_loop(true);
1772        src.set_loop_start(1. / sample_rate as f64);
1773        src.set_loop_end(4. / sample_rate as f64);
1774        src.playback_rate().set_value(-1.);
1775        src.start_at_with_offset(0., 3. / sample_rate as f64);
1776
1777        let result = context.start_rendering_sync();
1778        let expected = [4., 3., 2., 4., 3., 2., 4., 3.];
1779        assert_float_eq!(result.get_channel_data(0)[..8], expected[..], abs_all <= 0.);
1780    }
1781
1782    #[test]
1783    fn test_loop_out_of_bounds() {
1784        [
1785            // these will go in fast track
1786            (-2., -1., 0.),
1787            (-1., -2., 0.),
1788            (0., 0., 0.),
1789            (-1., 2., 0.),
1790            // these will go in slow track
1791            (2., -1., 1e-10),
1792            (1., 1., 1e-10),
1793            (2., 3., 1e-10),
1794            (3., 2., 1e-10),
1795        ]
1796        .iter()
1797        .for_each(|(loop_start, loop_end, error)| {
1798            let sample_rate = 48_000.;
1799            let length = sample_rate as usize / 10;
1800            let mut context = OfflineAudioContext::new(1, length, sample_rate);
1801
1802            let buffer_size = 500;
1803            let mut buffer = context.create_buffer(1, buffer_size, sample_rate);
1804            let data = vec![1.; 1];
1805            buffer.copy_to_channel(&data, 0);
1806
1807            let mut src = context.create_buffer_source();
1808            src.connect(&context.destination());
1809            src.set_buffer(buffer);
1810
1811            src.set_loop(true);
1812            src.set_loop_start(*loop_start); // outside of buffer duration
1813            src.set_loop_end(*loop_end); // outside of buffer duration
1814            src.start();
1815
1816            let result = context.start_rendering_sync(); // should terminate
1817            let channel = result.get_channel_data(0);
1818
1819            // Both loop points will be clamped to buffer duration due to rules defined at
1820            // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopstart
1821            // https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode-loopend
1822            // Thus it violates the rule defined in
1823            // https://webaudio.github.io/web-audio-api/#playback-AudioBufferSourceNode
1824            // `loopStart >= 0 && loopEnd > 0 && loopStart < loopEnd`
1825            // Hence the whole buffer should be looped
1826
1827            let mut expected = vec![0.; length];
1828            for i in (0..length).step_by(buffer_size) {
1829                expected[i] = 1.;
1830            }
1831
1832            assert_float_eq!(channel[..], expected[..], abs_all <= error);
1833        });
1834    }
1835
1836    #[test]
1837    // regression test for #452
1838    // - duration not set so `self.duration` is `f64::MAX`
1839    // - stop time is > buffer length
1840    fn test_end_of_file_fast_track_2() {
1841        let sample_rate = 48_000.;
1842        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1843
1844        let mut buffer = context.create_buffer(1, 5, sample_rate);
1845        let data = vec![1.; 1];
1846        buffer.copy_to_channel(&data, 0);
1847
1848        let mut src = context.create_buffer_source();
1849        src.connect(&context.destination());
1850        src.set_buffer(buffer);
1851        // play in fast track
1852        src.start_at(0.);
1853        // stop after end of buffer but before the end of render quantum
1854        src.stop_at(125. / sample_rate as f64);
1855
1856        let result = context.start_rendering_sync();
1857        let channel = result.get_channel_data(0);
1858
1859        let mut expected = vec![0.; 128];
1860        expected[0] = 1.;
1861
1862        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1863    }
1864
1865    #[test]
1866    // regression test for #452
1867    // - duration not set so `self.duration` is `f64::MAX`
1868    // - stop time is > buffer length
1869    fn test_end_of_file_slow_track_2() {
1870        let sample_rate = 48_000.;
1871        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE, sample_rate);
1872
1873        let mut buffer = context.create_buffer(1, 5, sample_rate);
1874        let data = vec![1.; 1];
1875        buffer.copy_to_channel(&data, 0);
1876
1877        let mut src = context.create_buffer_source();
1878        src.connect(&context.destination());
1879        src.set_buffer(buffer);
1880        // play in fast track
1881        src.start_at(1. / sample_rate as f64);
1882        // stop after end of buffer but before the end of render quantum
1883        src.stop_at(125. / sample_rate as f64);
1884
1885        let result = context.start_rendering_sync();
1886        let channel = result.get_channel_data(0);
1887
1888        let mut expected = vec![0.; 128];
1889        expected[1] = 1.;
1890
1891        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1892    }
1893
1894    #[test]
1895    fn test_loop_no_restart_suspend() {
1896        let sample_rate = 48_000.;
1897        let result_size = RENDER_QUANTUM_SIZE * 2;
1898        let mut context = OfflineAudioContext::new(1, result_size, sample_rate);
1899
1900        let mut buffer = context.create_buffer(1, 1, sample_rate);
1901        let data = vec![1.; 1];
1902        buffer.copy_to_channel(&data, 0);
1903
1904        let mut src = context.create_buffer_source();
1905        src.connect(&context.destination());
1906        src.set_buffer(buffer);
1907        src.start_at(0.);
1908
1909        context.suspend_sync(RENDER_QUANTUM_SIZE as f64 / sample_rate as f64, move |_| {
1910            src.set_loop(true);
1911        });
1912
1913        let result = context.start_rendering_sync();
1914        let channel = result.get_channel_data(0);
1915
1916        let mut expected = vec![0.; result_size];
1917        expected[0] = 1.;
1918
1919        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1920    }
1921
1922    #[test]
1923    fn test_loop_no_restart_onended_fast_track() {
1924        let sample_rate = 48_000.;
1925        // ended event is send on second render quantum, let's take a few more to be sure
1926        let result_size = RENDER_QUANTUM_SIZE * 4;
1927        let mut context = OfflineAudioContext::new(1, result_size, sample_rate);
1928
1929        let mut buffer = context.create_buffer(1, 1, sample_rate);
1930        let data = vec![1.; 1];
1931        buffer.copy_to_channel(&data, 0);
1932
1933        let mut src = context.create_buffer_source();
1934        src.connect(&context.destination());
1935        src.set_buffer(buffer);
1936        // play in fast track
1937        src.start_at(0.);
1938
1939        let src = Arc::new(Mutex::new(src));
1940        let clone = Arc::clone(&src);
1941        src.lock().unwrap().set_onended(move |_| {
1942            clone.lock().unwrap().set_loop(true);
1943        });
1944
1945        let result = context.start_rendering_sync();
1946        let channel = result.get_channel_data(0);
1947
1948        let mut expected = vec![0.; result_size];
1949        expected[0] = 1.;
1950
1951        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1952    }
1953
1954    #[test]
1955    fn test_loop_no_restart_onended_slow_track() {
1956        let sample_rate = 48_000.;
1957        // ended event is send on second render quantum, let's take a few more to be sure
1958        let result_size = RENDER_QUANTUM_SIZE * 4;
1959        let mut context = OfflineAudioContext::new(1, result_size, sample_rate);
1960
1961        let mut buffer = context.create_buffer(1, 1, sample_rate);
1962        let data = vec![1.; 1];
1963        buffer.copy_to_channel(&data, 0);
1964
1965        let mut src = context.create_buffer_source();
1966        src.connect(&context.destination());
1967        src.set_buffer(buffer);
1968        // play in slow track
1969        src.start_at(1. / sample_rate as f64);
1970
1971        let src = Arc::new(Mutex::new(src));
1972        let clone = Arc::clone(&src);
1973        src.lock().unwrap().set_onended(move |_| {
1974            clone.lock().unwrap().set_loop(true);
1975        });
1976
1977        let result = context.start_rendering_sync();
1978        let channel = result.get_channel_data(0);
1979
1980        let mut expected = vec![0.; result_size];
1981        expected[1] = 1.;
1982
1983        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
1984    }
1985
1986    #[test]
1987    // Ported from wpt: the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html
1988    // Note that in wpt, results are tested against an oscillator node, which fails
1989    // in the (44_100., 43_800., 3.8986e-3) condition for some (yet) unknown reason
1990    fn test_subsample_buffer_stitching() {
1991        [(44_100., 44_100., 9.0957e-5), (44_100., 43_800., 3.8986e-3)]
1992            .iter()
1993            .for_each(|(sample_rate, buffer_rate, error_threshold)| {
1994                let sample_rate = *sample_rate;
1995                let buffer_rate = *buffer_rate;
1996                let buffer_length = 30;
1997                let frequency = 440.;
1998
1999                // let length = sample_rate as usize;
2000                let length = buffer_length * 15;
2001                let mut context = OfflineAudioContext::new(2, length, sample_rate);
2002
2003                let mut wave_signal = vec![0.; context.length()];
2004                let omega = 2. * PI / buffer_rate * frequency;
2005                wave_signal.iter_mut().enumerate().for_each(|(i, s)| {
2006                    *s = (omega * i as f32).sin();
2007                });
2008
2009                // Slice the sine wave into many little buffers to be assigned to ABSNs
2010                // that are started at the appropriate times to produce a final sine
2011                // wave.
2012                for k in (0..context.length()).step_by(buffer_length) {
2013                    let mut buffer = AudioBuffer::new(AudioBufferOptions {
2014                        number_of_channels: 1,
2015                        length: buffer_length,
2016                        sample_rate: buffer_rate,
2017                    });
2018                    buffer.copy_to_channel(&wave_signal[k..k + buffer_length], 0);
2019
2020                    let mut src = AudioBufferSourceNode::new(
2021                        &context,
2022                        AudioBufferSourceOptions {
2023                            buffer: Some(buffer),
2024                            ..Default::default()
2025                        },
2026                    );
2027                    src.connect(&context.destination());
2028                    src.start_at(k as f64 / buffer_rate as f64);
2029                }
2030
2031                let mut expected = vec![0.; context.length()];
2032                let omega = 2. * PI / sample_rate * frequency;
2033                expected.iter_mut().enumerate().for_each(|(i, s)| {
2034                    *s = (omega * i as f32).sin();
2035                });
2036
2037                let result = context.start_rendering_sync();
2038                let actual = result.get_channel_data(0);
2039
2040                assert_float_eq!(actual[..], expected[..], abs_all <= error_threshold);
2041            });
2042    }
2043
2044    #[test]
2045    fn test_onended_before_drop() {
2046        let sample_rate = 48_000.;
2047        let result_size = RENDER_QUANTUM_SIZE;
2048        let mut context = OfflineAudioContext::new(1, result_size, sample_rate);
2049        // buffer is larger than context output so it never goes into the ended check condition
2050        let mut buffer = context.create_buffer(1, result_size * 2, sample_rate);
2051        let data = vec![1.; 1];
2052        buffer.copy_to_channel(&data, 0);
2053
2054        let mut src = context.create_buffer_source();
2055        src.connect(&context.destination());
2056        src.set_buffer(buffer);
2057        src.start();
2058
2059        let onended_called = Arc::new(AtomicBool::new(false));
2060        let onended_called_clone = Arc::clone(&onended_called);
2061
2062        src.set_onended(move |_| {
2063            onended_called_clone.store(true, Ordering::SeqCst);
2064        });
2065
2066        let result = context.start_rendering_sync();
2067        let channel = result.get_channel_data(0);
2068
2069        let mut expected = vec![0.; result_size];
2070        expected[0] = 1.;
2071
2072        assert_float_eq!(channel[..], expected[..], abs_all <= 0.);
2073        assert!(onended_called.load(Ordering::SeqCst));
2074    }
2075}