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