Skip to main content

web_audio_api/node/
oscillator.rs

1use std::any::Any;
2use std::f32::consts::PI;
3use std::fmt::Debug;
4use std::sync::OnceLock;
5
6use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
7use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
8use crate::render::{
9    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
10};
11use crate::PeriodicWave;
12use crate::{assert_valid_time_value, RENDER_QUANTUM_SIZE};
13
14use super::{AudioNode, AudioNodeOptions, AudioScheduledSourceNode, ChannelConfig};
15
16const SINE_TABLE_LENGTH_USIZE: usize = 2048;
17const SINE_TABLE_LENGTH_F32: f32 = SINE_TABLE_LENGTH_USIZE as f32;
18
19/// Precomputed sine table
20fn precomputed_sine_table() -> &'static [f32] {
21    static INSTANCE: OnceLock<Vec<f32>> = OnceLock::new();
22    INSTANCE.get_or_init(|| {
23        // Compute one period sine wavetable of size SINE_TABLE_LENGTH.
24        (0..SINE_TABLE_LENGTH_USIZE)
25            .map(|x| ((x as f32) * 2.0 * PI * (1. / (SINE_TABLE_LENGTH_F32))).sin())
26            .collect()
27    })
28}
29
30fn get_computed_freq(freq: f32, detune: f32) -> f64 {
31    freq as f64 * (detune as f64 / 1200.).exp2()
32}
33
34/// Options for constructing an [`OscillatorNode`]
35// dictionary OscillatorOptions : AudioNodeOptions {
36//   OscillatorType type = "sine";
37//   float frequency = 440;
38//   float detune = 0;
39//   PeriodicWave periodicWave;
40// };
41//
42// @note - Does extend AudioNodeOptions but they are useless for source nodes as
43// they instruct how to upmix the inputs.
44// This is a common source of confusion, see e.g. https://github.com/mdn/content/pull/18472, and
45// an issue in the spec, see discussion in https://github.com/WebAudio/web-audio-api/issues/2496
46#[derive(Clone, Debug)]
47pub struct OscillatorOptions {
48    /// The shape of the periodic waveform
49    pub type_: OscillatorType,
50    /// The frequency of the fundamental frequency.
51    pub frequency: f32,
52    /// A detuning value (in cents) which will offset the frequency by the given amount.
53    pub detune: f32,
54    /// Optional custom waveform, if specified (set `type` to "custom")
55    pub periodic_wave: Option<PeriodicWave>,
56    /// channel config options
57    pub audio_node_options: AudioNodeOptions,
58}
59
60impl Default for OscillatorOptions {
61    fn default() -> Self {
62        Self {
63            type_: OscillatorType::default(),
64            frequency: 440.,
65            detune: 0.,
66            periodic_wave: None,
67            audio_node_options: AudioNodeOptions::default(),
68        }
69    }
70}
71
72/// Type of the waveform rendered by an `OscillatorNode`
73#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
74pub enum OscillatorType {
75    /// Sine wave
76    #[default]
77    Sine,
78    /// Square wave
79    Square,
80    /// Sawtooth wave
81    Sawtooth,
82    /// Triangle wave
83    Triangle,
84    /// type used when periodic_wave is specified
85    Custom,
86}
87
88impl From<u32> for OscillatorType {
89    fn from(i: u32) -> Self {
90        match i {
91            0 => OscillatorType::Sine,
92            1 => OscillatorType::Square,
93            2 => OscillatorType::Sawtooth,
94            3 => OscillatorType::Triangle,
95            4 => OscillatorType::Custom,
96            _ => unreachable!(),
97        }
98    }
99}
100
101/// Instructions to start or stop processing
102#[derive(Debug, Copy, Clone)]
103enum Schedule {
104    Start(f64),
105    Stop(f64),
106}
107
108/// `OscillatorNode` represents an audio source generating a periodic waveform.
109/// It can generate a few common waveforms (i.e. sine, square, sawtooth, triangle),
110/// or can be set to an arbitrary periodic waveform using a [`PeriodicWave`] object.
111///
112/// - MDN documentation: <https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode>
113/// - specification: <https://webaudio.github.io/web-audio-api/#OscillatorNode>
114/// - see also: [`BaseAudioContext::create_oscillator`]
115/// - see also: [`PeriodicWave`]
116///
117/// # Usage
118///
119/// ```no_run
120/// use web_audio_api::context::{BaseAudioContext, AudioContext};
121/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};
122///
123/// let context = AudioContext::default();
124///
125/// let mut osc = context.create_oscillator();
126/// osc.frequency().set_value(200.);
127/// osc.connect(&context.destination());
128/// osc.start();
129/// ```
130///
131/// # Examples
132///
133/// - `cargo run --release --example oscillators`
134/// - `cargo run --release --example many_oscillators_with_env`
135/// - `cargo run --release --example amplitude_modulation`
136///
137#[derive(Debug)]
138pub struct OscillatorNode {
139    /// Represents the node instance and its associated audio context
140    registration: AudioContextRegistration,
141    /// Infos about audio node channel configuration
142    channel_config: ChannelConfig,
143    /// The frequency of the fundamental frequency.
144    frequency: AudioParam,
145    /// A detuning value (in cents) which will offset the frequency by the given amount.
146    detune: AudioParam,
147    /// Waveform of an oscillator
148    type_: OscillatorType,
149    /// Tracks whether `start` has been called already.
150    has_start: bool,
151}
152
153impl AudioNode for OscillatorNode {
154    fn registration(&self) -> &AudioContextRegistration {
155        &self.registration
156    }
157
158    fn channel_config(&self) -> &ChannelConfig {
159        &self.channel_config
160    }
161
162    /// `OscillatorNode` is a source node. A source node is by definition with no input
163    fn number_of_inputs(&self) -> usize {
164        0
165    }
166
167    /// `OscillatorNode` is a mono source node.
168    fn number_of_outputs(&self) -> usize {
169        1
170    }
171}
172
173impl AudioScheduledSourceNode for OscillatorNode {
174    fn start(&mut self) {
175        let when = self.registration.context().current_time();
176        self.start_at(when);
177    }
178
179    fn start_at(&mut self, when: f64) {
180        assert_valid_time_value(when);
181        assert!(
182            !self.has_start,
183            "InvalidStateError - Cannot call `start` twice"
184        );
185
186        self.has_start = true;
187        self.registration.post_message(Schedule::Start(when));
188    }
189
190    fn stop(&mut self) {
191        let when = self.registration.context().current_time();
192        self.stop_at(when);
193    }
194
195    fn stop_at(&mut self, when: f64) {
196        assert_valid_time_value(when);
197        assert!(
198            self.has_start,
199            "InvalidStateError - cannot stop before start"
200        );
201
202        self.registration.post_message(Schedule::Stop(when));
203    }
204}
205
206impl OscillatorNode {
207    /// Returns an `OscillatorNode`
208    ///
209    /// # Arguments:
210    ///
211    /// * `context` - The `AudioContext`
212    /// * `options` - The OscillatorOptions
213    pub fn new<C: BaseAudioContext>(context: &C, options: OscillatorOptions) -> Self {
214        let OscillatorOptions {
215            type_,
216            frequency,
217            detune,
218            audio_node_options: channel_config,
219            periodic_wave,
220        } = options;
221
222        let mut node = context.base().register(move |registration| {
223            let sample_rate = context.sample_rate();
224            let nyquist = sample_rate / 2.;
225
226            // frequency audio parameter
227            let freq_param_options = AudioParamDescriptor {
228                name: String::new(),
229                min_value: -nyquist,
230                max_value: nyquist,
231                default_value: 440.,
232                automation_rate: AutomationRate::A,
233            };
234            let (f_param, f_proc) = context.create_audio_param(freq_param_options, &registration);
235            f_param.set_value(frequency);
236
237            // detune audio parameter
238            let det_param_options = AudioParamDescriptor {
239                name: String::new(),
240                min_value: -153_600.,
241                max_value: 153_600.,
242                default_value: 0.,
243                automation_rate: AutomationRate::A,
244            };
245            let (det_param, det_proc) =
246                context.create_audio_param(det_param_options, &registration);
247            det_param.set_value(detune);
248
249            let renderer = OscillatorRenderer {
250                type_,
251                frequency: f_proc,
252                detune: det_proc,
253                phase: 0.,
254                start_time: f64::MAX,
255                stop_time: f64::MAX,
256                started: false,
257                periodic_wave: None,
258                ended_triggered: false,
259                sine_table: precomputed_sine_table(),
260            };
261
262            let node = Self {
263                registration,
264                channel_config: channel_config.into(),
265                frequency: f_param,
266                detune: det_param,
267                type_,
268                has_start: false,
269            };
270
271            (node, Box::new(renderer))
272        });
273
274        // renderer has been sent to render thread, we can send it messages
275        if let Some(p_wave) = periodic_wave {
276            node.set_periodic_wave(p_wave);
277        }
278
279        node
280    }
281
282    /// A-rate [`AudioParam`] that defines the fundamental frequency of the
283    /// oscillator, expressed in Hz
284    ///
285    /// The final frequency is calculated as follow: frequency * 2^(detune/1200)
286    #[must_use]
287    pub fn frequency(&self) -> &AudioParam {
288        &self.frequency
289    }
290
291    /// A-rate [`AudioParam`] that defines a transposition according to the
292    /// frequency, expressed in cents.
293    ///
294    /// see <https://en.wikipedia.org/wiki/Cent_(music)>
295    ///
296    /// The final frequency is calculated as follow: frequency * 2^(detune/1200)
297    #[must_use]
298    pub fn detune(&self) -> &AudioParam {
299        &self.detune
300    }
301
302    /// Returns the oscillator type
303    #[must_use]
304    pub fn type_(&self) -> OscillatorType {
305        self.type_
306    }
307
308    /// Set the oscillator type
309    ///
310    /// # Arguments
311    ///
312    /// * `type_` - oscillator type (sine, square, triangle, sawtooth)
313    ///
314    /// # Panics
315    ///
316    /// if `type_` is `OscillatorType::Custom`
317    pub fn set_type(&mut self, type_: OscillatorType) {
318        assert_ne!(
319            type_,
320            OscillatorType::Custom,
321            "InvalidStateError: Custom type cannot be set manually"
322        );
323
324        // if periodic wave has been set specified, type_ changes are ignored
325        if self.type_ == OscillatorType::Custom {
326            return;
327        }
328
329        self.type_ = type_;
330        self.registration.post_message(type_);
331    }
332
333    /// Sets a `PeriodicWave` which describes a waveform to be used by the oscillator.
334    ///
335    /// Calling this sets the oscillator type to `custom`, once set to `custom`
336    /// the oscillator cannot be reverted back to a standard waveform.
337    pub fn set_periodic_wave(&mut self, periodic_wave: PeriodicWave) {
338        self.type_ = OscillatorType::Custom;
339        self.registration.post_message(periodic_wave);
340    }
341}
342
343/// Rendering component of the oscillator node
344struct OscillatorRenderer {
345    /// The shape of the periodic waveform
346    type_: OscillatorType,
347    /// The frequency of the fundamental frequency.
348    frequency: AudioParamId,
349    /// A detuning value (in cents) which will offset the frequency by the given amount.
350    detune: AudioParamId,
351    /// current phase of the oscillator
352    phase: f64,
353    /// start time
354    start_time: f64,
355    /// end time
356    stop_time: f64,
357    /// defines if the oscillator has started
358    started: bool,
359    /// wavetable placeholder for custom oscillators
360    periodic_wave: Option<PeriodicWave>,
361    /// defines if the `ended` events was already dispatched
362    ended_triggered: bool,
363    /// Precomputed sine table
364    sine_table: &'static [f32],
365}
366
367impl AudioProcessor for OscillatorRenderer {
368    fn process(
369        &mut self,
370        _inputs: &[AudioRenderQuantum],
371        outputs: &mut [AudioRenderQuantum],
372        params: AudioParamValues<'_>,
373        scope: &AudioWorkletGlobalScope,
374    ) -> bool {
375        // single output node
376        let output = &mut outputs[0];
377        // 1 channel output
378        output.set_number_of_channels(1);
379
380        let sample_rate = scope.sample_rate as f64;
381        let dt = 1. / sample_rate;
382        let num_frames = RENDER_QUANTUM_SIZE;
383        let next_block_time = scope.current_time + dt * num_frames as f64;
384
385        if self.stop_time <= scope.current_time {
386            output.make_silent();
387
388            if !self.ended_triggered {
389                scope.send_ended_event();
390                self.ended_triggered = true;
391            }
392
393            return false;
394        } else if self.start_time >= next_block_time {
395            output.make_silent();
396
397            if self.stop_time <= next_block_time {
398                if !self.ended_triggered {
399                    scope.send_ended_event();
400                    self.ended_triggered = true;
401                }
402
403                return false;
404            }
405
406            // #462 AudioScheduledSourceNodes that have not been scheduled to start can safely
407            // return tail_time false in order to be collected if their control handle drops.
408            return self.start_time != f64::MAX;
409        }
410
411        let channel_data = output.channel_data_mut(0);
412        let frequency_values = params.get(&self.frequency);
413        let detune_values = params.get(&self.detune);
414
415        let mut current_time = scope.current_time;
416
417        // Prevent scheduling in the past
418        //
419        // [spec] If 0 is passed in for this value or if the value is less than
420        // currentTime, then the sound will start playing immediately
421        // cf. https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start-when-when
422        if !self.started && self.start_time < current_time {
423            self.start_time = current_time;
424        }
425
426        let nyquist = sample_rate / 2.;
427
428        // fast path for scalar AudioParam values
429        if frequency_values.len() == 1 && detune_values.len() == 1 {
430            let freq = frequency_values[0];
431            let detune = detune_values[0];
432            let computed_freq = get_computed_freq(freq, detune);
433            let phase_incr = computed_freq / sample_rate;
434            let outside_nyquist = computed_freq.abs() >= nyquist;
435            let fully_active = self.started
436                && self.start_time <= scope.current_time
437                && self.stop_time >= next_block_time;
438
439            if fully_active && !outside_nyquist {
440                channel_data.iter_mut().for_each(|output| {
441                    *output = self.generate_waveform_sample(phase_incr);
442                    self.phase = Self::unroll_phase(self.phase + phase_incr);
443                });
444            } else {
445                channel_data.iter_mut().for_each(|output| {
446                    current_time =
447                        self.generate_sample(output, outside_nyquist, phase_incr, current_time, dt);
448                });
449            }
450        } else {
451            channel_data
452                .iter_mut()
453                .zip(frequency_values.iter().cycle())
454                .zip(detune_values.iter().cycle())
455                .for_each(|((output, &freq), &detune)| {
456                    let computed_freq = get_computed_freq(freq, detune);
457                    let phase_incr = computed_freq / sample_rate;
458                    let outside_nyquist = computed_freq.abs() >= nyquist;
459                    current_time =
460                        self.generate_sample(output, outside_nyquist, phase_incr, current_time, dt)
461                });
462        }
463
464        if self.stop_time <= next_block_time {
465            if !self.ended_triggered {
466                scope.send_ended_event();
467                self.ended_triggered = true;
468            }
469
470            return false;
471        }
472
473        true
474    }
475
476    fn onmessage(&mut self, msg: &mut dyn Any) {
477        if let Some(&type_) = msg.downcast_ref::<OscillatorType>() {
478            self.type_ = type_;
479            return;
480        }
481
482        if let Some(&schedule) = msg.downcast_ref::<Schedule>() {
483            match schedule {
484                Schedule::Start(v) => self.start_time = v,
485                Schedule::Stop(v) => self.stop_time = v,
486            }
487            return;
488        }
489
490        if let Some(periodic_wave) = msg.downcast_mut::<PeriodicWave>() {
491            if let Some(current_periodic_wave) = &mut self.periodic_wave {
492                // Avoid deallocation in the render thread by swapping the wavetable buffers.
493                std::mem::swap(current_periodic_wave, periodic_wave)
494            } else {
495                // The default wavetable buffer is empty and does not cause allocations.
496                self.periodic_wave = Some(std::mem::take(periodic_wave));
497            }
498            self.type_ = OscillatorType::Custom; // shared type is already updated by control
499            return;
500        }
501
502        log::warn!("OscillatorRenderer: Dropping incoming message {msg:?}");
503    }
504
505    fn before_drop(&mut self, scope: &AudioWorkletGlobalScope) {
506        if !self.ended_triggered
507            && (scope.current_time >= self.start_time || scope.current_time >= self.stop_time)
508        {
509            scope.send_ended_event();
510            self.ended_triggered = true;
511        }
512    }
513}
514impl OscillatorRenderer {
515    #[inline]
516    fn generate_sample(
517        &mut self,
518        output: &mut f32,
519        outside_nyquist: bool,
520        phase_incr: f64,
521        current_time: f64,
522        dt: f64,
523    ) -> f64 {
524        if current_time < self.start_time || current_time >= self.stop_time {
525            *output = 0.;
526            return current_time + dt;
527        }
528
529        // first sample to render
530        if !self.started {
531            // if start time was between last frame and current frame
532            // we need to adjust the phase first
533            if current_time > self.start_time {
534                let ratio = (current_time - self.start_time) / dt;
535                self.phase = if outside_nyquist {
536                    Self::unroll_phase_unbounded(phase_incr * ratio)
537                } else {
538                    Self::unroll_phase(phase_incr * ratio)
539                };
540            }
541
542            self.started = true;
543        }
544
545        *output = if outside_nyquist {
546            // Output silence when the computed oscillator frequency is outside the
547            // nominal [-nyquist, nyquist] range. Timing and phase still advance so
548            // automation can re-enter the audible range without resetting phase.
549            0.
550        } else {
551            self.generate_waveform_sample(phase_incr)
552        };
553
554        self.phase = if outside_nyquist {
555            Self::unroll_phase_unbounded(self.phase + phase_incr)
556        } else {
557            Self::unroll_phase(self.phase + phase_incr)
558        };
559
560        current_time + dt
561    }
562
563    #[inline]
564    fn generate_waveform_sample(&mut self, phase_incr: f64) -> f32 {
565        match self.type_ {
566            OscillatorType::Sine => self.generate_sine(),
567            OscillatorType::Sawtooth => self.generate_sawtooth(phase_incr),
568            OscillatorType::Square => self.generate_square(phase_incr),
569            OscillatorType::Triangle => self.generate_triangle(),
570            OscillatorType::Custom => self.generate_custom(),
571        }
572    }
573
574    #[inline]
575    fn generate_sine(&mut self) -> f32 {
576        let position = self.phase * SINE_TABLE_LENGTH_USIZE as f64;
577        let floored = position.floor();
578
579        let prev_index = floored as usize;
580        let mut next_index = prev_index + 1;
581        if next_index == SINE_TABLE_LENGTH_USIZE {
582            next_index = 0;
583        }
584
585        // linear interpolation into lookup table
586        let k = (position - floored) as f32;
587        self.sine_table[prev_index].mul_add(1. - k, self.sine_table[next_index] * k)
588    }
589
590    #[inline]
591    fn generate_sawtooth(&mut self, phase_incr: f64) -> f32 {
592        // offset phase to start at 0. (not -1.)
593        let phase = Self::unroll_phase(self.phase + 0.5);
594        let mut sample = 2.0 * phase - 1.0;
595        sample -= Self::poly_blep(phase, phase_incr, cfg!(test));
596
597        sample as f32
598    }
599
600    #[inline]
601    fn generate_square(&mut self, phase_incr: f64) -> f32 {
602        let mut sample = if self.phase < 0.5 { 1.0 } else { -1.0 };
603        sample += Self::poly_blep(self.phase, phase_incr, cfg!(test));
604
605        let shift_phase = Self::unroll_phase(self.phase + 0.5);
606        sample -= Self::poly_blep(shift_phase, phase_incr, cfg!(test));
607
608        sample as f32
609    }
610
611    #[inline]
612    fn generate_triangle(&mut self) -> f32 {
613        let mut sample = -4. * self.phase + 2.;
614
615        if sample > 1. {
616            sample = 2. - sample;
617        } else if sample < -1. {
618            sample = -2. - sample;
619        }
620
621        sample as f32
622    }
623
624    #[inline]
625    fn generate_custom(&mut self) -> f32 {
626        let periodic_wave = self.periodic_wave.as_ref().unwrap().as_slice();
627        let table_length = periodic_wave.len();
628        let position = self.phase * table_length as f64;
629        let floored = position.floor();
630
631        let prev_index = floored as usize;
632        let mut next_index = prev_index + 1;
633        if next_index == table_length {
634            next_index = 0;
635        }
636
637        // linear interpolation into lookup table
638        let k = (position - floored) as f32;
639        periodic_wave[prev_index].mul_add(1. - k, periodic_wave[next_index] * k)
640    }
641
642    // computes the `polyBLEP` corrections to apply to aliasing signal
643    // `polyBLEP` stands for `polyBandLimitedstEP`
644    // This basically soften the sharp edges in square and sawtooth signals
645    // to avoid infinite frequencies impulses (jumps from -1 to 1 or inverse).
646    // cf. http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/
647    //
648    // @note: do not apply in tests so we can avoid relying on snapshots
649    #[inline]
650    fn poly_blep(mut t: f64, dt: f64, is_test: bool) -> f64 {
651        if is_test {
652            0.
653        } else if t < dt {
654            t /= dt;
655            t + t - t * t - 1.0
656        } else if t > 1.0 - dt {
657            t = (t - 1.0) / dt;
658            t.mul_add(t, t) + t + 1.0
659        } else {
660            0.0
661        }
662    }
663
664    #[inline]
665    fn unroll_phase(phase: f64) -> f64 {
666        if phase >= 1. {
667            phase - 1.
668        } else if phase < 0. {
669            phase + 1.
670        } else {
671            phase
672        }
673    }
674
675    #[inline]
676    fn unroll_phase_unbounded(phase: f64) -> f64 {
677        phase.rem_euclid(1.)
678    }
679}
680
681#[cfg(test)]
682mod tests {
683    use float_eq::assert_float_eq;
684    use std::f64::consts::PI;
685
686    use crate::context::{BaseAudioContext, OfflineAudioContext};
687    use crate::node::{AudioNode, AudioScheduledSourceNode};
688    use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions};
689    use crate::RENDER_QUANTUM_SIZE;
690
691    use super::{OscillatorNode, OscillatorOptions, OscillatorRenderer, OscillatorType};
692
693    #[test]
694    fn assert_osc_default_build_with_factory_func() {
695        let default_freq = 440.;
696        let default_det = 0.;
697        let default_type = OscillatorType::Sine;
698
699        let mut context = OfflineAudioContext::new(2, 1, 44_100.);
700
701        let mut osc = context.create_oscillator();
702
703        let freq = osc.frequency.value();
704        assert_float_eq!(freq, default_freq, abs_all <= 0.);
705
706        let det = osc.detune.value();
707        assert_float_eq!(det, default_det, abs_all <= 0.);
708
709        assert_eq!(osc.type_(), default_type);
710
711        // should not panic when run
712        osc.start();
713        osc.connect(&context.destination());
714        let _ = context.start_rendering_sync();
715    }
716
717    #[test]
718    fn assert_osc_default_build() {
719        let default_freq = 440.;
720        let default_det = 0.;
721        let default_type = OscillatorType::Sine;
722
723        let mut context = OfflineAudioContext::new(2, 1, 44_100.);
724
725        let mut osc = OscillatorNode::new(&context, OscillatorOptions::default());
726
727        let freq = osc.frequency.value();
728        assert_float_eq!(freq, default_freq, abs_all <= 0.);
729
730        let det = osc.detune.value();
731        assert_float_eq!(det, default_det, abs_all <= 0.);
732
733        assert_eq!(osc.type_(), default_type);
734
735        // should not panic when run
736        osc.start();
737        osc.connect(&context.destination());
738        let _ = context.start_rendering_sync();
739    }
740
741    #[test]
742    #[should_panic]
743    fn set_type_to_custom_should_panic() {
744        let context = OfflineAudioContext::new(2, 1, 44_100.);
745        let mut osc = OscillatorNode::new(&context, OscillatorOptions::default());
746        osc.set_type(OscillatorType::Custom);
747    }
748
749    #[test]
750    fn type_is_custom_when_periodic_wave_is_some() {
751        let expected_type = OscillatorType::Custom;
752
753        let mut context = OfflineAudioContext::new(2, 1, 44_100.);
754
755        let periodic_wave = PeriodicWave::new(&context, PeriodicWaveOptions::default());
756
757        let options = OscillatorOptions {
758            periodic_wave: Some(periodic_wave),
759            ..OscillatorOptions::default()
760        };
761
762        let mut osc = OscillatorNode::new(&context, options);
763
764        assert_eq!(osc.type_(), expected_type);
765
766        // should not panic when run
767        osc.start();
768        osc.connect(&context.destination());
769        let _ = context.start_rendering_sync();
770    }
771
772    #[test]
773    fn set_type_is_ignored_when_periodic_wave_is_some() {
774        let expected_type = OscillatorType::Custom;
775
776        let mut context = OfflineAudioContext::new(2, 1, 44_100.);
777
778        let periodic_wave = PeriodicWave::new(&context, PeriodicWaveOptions::default());
779
780        let options = OscillatorOptions {
781            periodic_wave: Some(periodic_wave),
782            ..OscillatorOptions::default()
783        };
784
785        let mut osc = OscillatorNode::new(&context, options);
786
787        osc.set_type(OscillatorType::Sine);
788        assert_eq!(osc.type_(), expected_type);
789
790        // should not panic when run
791        osc.start();
792        osc.connect(&context.destination());
793        let _ = context.start_rendering_sync();
794    }
795
796    // # Test waveforms
797    //
798    // - for `square`, `triangle` and `sawtooth` the tests may appear a bit
799    //   tautological (and they actually are) as the code from the test is the
800    //   mostly as same as in the renderer, just written in a more compact way.
801    //   However they should help to prevent regressions, and/or allow testing
802    //   against trusted and simple implementation in case of future changes
803    //   in the renderer impl, e.g. performance improvements or spec compliance:
804    //   https://webaudio.github.io/web-audio-api/#oscillator-coefficients.
805    //
806    // - PolyBlep is not applied on `square` and `triangle` for tests, so we can
807    //   compare according to a crude waveforms
808
809    #[test]
810    fn sine_raw() {
811        // 1, 10, 100, 1_000, 10_000 Hz
812        for i in 0..5 {
813            let freq = 10_f32.powf(i as f32);
814            let sample_rate = 44_100;
815
816            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
817
818            let mut osc = context.create_oscillator();
819            osc.connect(&context.destination());
820            osc.frequency().set_value(freq);
821            osc.start_at(0.);
822
823            let output = context.start_rendering_sync();
824            let result = output.get_channel_data(0);
825
826            let mut expected = Vec::<f32>::with_capacity(sample_rate);
827            let mut phase: f64 = 0.;
828            let phase_incr = freq as f64 / sample_rate as f64;
829
830            for _i in 0..sample_rate {
831                let sample = (phase * 2. * PI).sin();
832
833                expected.push(sample as f32);
834
835                phase += phase_incr;
836                if phase >= 1. {
837                    phase -= 1.;
838                }
839            }
840
841            assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
842        }
843    }
844
845    #[test]
846    fn sine_raw_exact_phase() {
847        // 1, 10, 100, 1_000, 10_000 Hz
848        for i in 0..5 {
849            let freq = 10_f32.powf(i as f32);
850            let sample_rate = 44_100;
851
852            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
853
854            let mut osc = context.create_oscillator();
855            osc.connect(&context.destination());
856            osc.frequency().set_value(freq);
857            osc.start_at(0.);
858
859            let output = context.start_rendering_sync();
860            let result = output.get_channel_data(0);
861            let mut expected = Vec::<f32>::with_capacity(sample_rate);
862
863            for i in 0..sample_rate {
864                let phase = freq as f64 * i as f64 / sample_rate as f64;
865                let sample = (phase * 2. * PI).sin();
866                // phase += phase_incr;
867                expected.push(sample as f32);
868            }
869
870            assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
871        }
872    }
873
874    #[test]
875    fn square_raw() {
876        // 1, 10, 100, 1_000, 10_000 Hz
877        for i in 0..5 {
878            let freq = 10_f32.powf(i as f32);
879            let sample_rate = 44100;
880
881            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
882
883            let mut osc = context.create_oscillator();
884            osc.connect(&context.destination());
885            osc.frequency().set_value(freq);
886            osc.set_type(OscillatorType::Square);
887            osc.start_at(0.);
888
889            let output = context.start_rendering_sync();
890            let result = output.get_channel_data(0);
891
892            let mut expected = Vec::<f32>::with_capacity(sample_rate);
893            let mut phase: f64 = 0.;
894            let phase_incr = freq as f64 / sample_rate as f64;
895
896            for _i in 0..sample_rate {
897                // 0.5 belongs to the second half of the waveform
898                let sample = if phase < 0.5 { 1. } else { -1. };
899
900                expected.push(sample as f32);
901
902                phase += phase_incr;
903                if phase >= 1. {
904                    phase -= 1.;
905                }
906            }
907
908            assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
909        }
910    }
911
912    #[test]
913    fn triangle_raw() {
914        // 1, 10, 100, 1_000, 10_000 Hz
915        for i in 0..5 {
916            let freq = 10_f32.powf(i as f32);
917            let sample_rate = 44_100;
918
919            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
920
921            let mut osc = context.create_oscillator();
922            osc.connect(&context.destination());
923            osc.frequency().set_value(freq);
924            osc.set_type(OscillatorType::Triangle);
925            osc.start_at(0.);
926
927            let output = context.start_rendering_sync();
928            let result = output.get_channel_data(0);
929
930            let mut expected = Vec::<f32>::with_capacity(sample_rate);
931            let mut phase: f64 = 0.;
932            let phase_incr = freq as f64 / sample_rate as f64;
933
934            for _i in 0..sample_rate {
935                // triangle starts a 0.
936                // [0., 1.]  between [0, 0.25]
937                // [1., -1.] between [0.25, 0.75]
938                // [-1., 0.] between [0.75, 1]
939                let mut sample = -4. * phase + 2.;
940
941                if sample > 1. {
942                    sample = 2. - sample;
943                } else if sample < -1. {
944                    sample = -2. - sample;
945                }
946
947                expected.push(sample as f32);
948
949                phase += phase_incr;
950                if phase >= 1. {
951                    phase -= 1.;
952                }
953            }
954
955            assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
956        }
957    }
958
959    #[test]
960    fn sawtooth_raw() {
961        // 1, 10, 100, 1_000, 10_000 Hz
962        for i in 0..5 {
963            let freq = 10_f32.powf(i as f32);
964            let sample_rate = 44_100;
965
966            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
967
968            let mut osc = context.create_oscillator();
969            osc.connect(&context.destination());
970            osc.frequency().set_value(freq);
971            osc.set_type(OscillatorType::Sawtooth);
972            osc.start_at(0.);
973
974            let output = context.start_rendering_sync();
975            let result = output.get_channel_data(0);
976
977            let mut expected = Vec::<f32>::with_capacity(sample_rate);
978            let mut phase: f64 = 0.;
979            let phase_incr = freq as f64 / sample_rate as f64;
980
981            for _i in 0..sample_rate {
982                // triangle starts a 0.
983                // [0, 1] between [0, 0.5]
984                // [-1, 0] between [0.5, 1]
985                let mut offset_phase = phase + 0.5;
986                if offset_phase >= 1. {
987                    offset_phase -= 1.;
988                }
989                let sample = 2. * offset_phase - 1.;
990
991                expected.push(sample as f32);
992
993                phase += phase_incr;
994                if phase >= 1. {
995                    phase -= 1.;
996                }
997            }
998
999            assert_float_eq!(result[..], expected[..], abs_all <= 1e-10);
1000        }
1001    }
1002
1003    #[test]
1004    // this one should output exactly the same thing as sine_raw
1005    fn periodic_wave_1f() {
1006        // 1, 10, 100, 1_000, 10_000 Hz
1007        for i in 0..5 {
1008            let freq = 10_f32.powf(i as f32);
1009            let sample_rate = 44_100;
1010
1011            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1012
1013            let options = PeriodicWaveOptions {
1014                real: Some(vec![0., 0.]),
1015                imag: Some(vec![0., 1.]), // sine is in imaginary component
1016                disable_normalization: false,
1017            };
1018
1019            let periodic_wave = context.create_periodic_wave(options);
1020
1021            let mut osc = context.create_oscillator();
1022            osc.connect(&context.destination());
1023            osc.set_periodic_wave(periodic_wave);
1024            osc.frequency().set_value(freq);
1025            osc.set_type(OscillatorType::Sawtooth);
1026            osc.start_at(0.);
1027
1028            let output = context.start_rendering_sync();
1029            let result = output.get_channel_data(0);
1030
1031            let mut expected = Vec::<f32>::with_capacity(sample_rate);
1032            let mut phase: f64 = 0.;
1033            let phase_incr = freq as f64 / sample_rate as f64;
1034
1035            for _i in 0..sample_rate {
1036                let sample = (phase * 2. * PI).sin();
1037
1038                expected.push(sample as f32);
1039
1040                phase += phase_incr;
1041                if phase >= 1. {
1042                    phase -= 1.;
1043                }
1044            }
1045
1046            assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1047        }
1048    }
1049
1050    #[test]
1051    fn periodic_wave_2f() {
1052        // 1, 10, 100, 1_000, 10_000 Hz
1053        for i in 0..5 {
1054            let freq = 10_f32.powf(i as f32);
1055            let sample_rate = 44_100;
1056
1057            let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1058
1059            let options = PeriodicWaveOptions {
1060                real: Some(vec![0., 0., 0.]),
1061                imag: Some(vec![0., 0.5, 0.5]),
1062                // disable norm, is already tested in `PeriodicWave`
1063                disable_normalization: true,
1064            };
1065
1066            let periodic_wave = context.create_periodic_wave(options);
1067
1068            let mut osc = context.create_oscillator();
1069            osc.connect(&context.destination());
1070            osc.set_periodic_wave(periodic_wave);
1071            osc.frequency().set_value(freq);
1072            osc.start_at(0.);
1073
1074            let output = context.start_rendering_sync();
1075            let result = output.get_channel_data(0);
1076
1077            let mut expected = Vec::<f32>::with_capacity(sample_rate);
1078            let mut phase: f64 = 0.;
1079            let phase_incr = freq as f64 / sample_rate as f64;
1080
1081            for _i in 0..sample_rate {
1082                let mut sample = 0.;
1083                sample += 0.5 * (1. * phase * 2. * PI).sin();
1084                sample += 0.5 * (2. * phase * 2. * PI).sin();
1085
1086                expected.push(sample as f32);
1087
1088                phase += phase_incr;
1089                if phase >= 1. {
1090                    phase -= 1.;
1091                }
1092            }
1093
1094            assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1095        }
1096    }
1097
1098    #[test]
1099    fn polyblep_isolated() {
1100        // @note: Only first branch of the polyblep seems to be used here.
1101        // May be due on the simplicity of the test itself where everything is
1102        // well aligned.
1103
1104        // square
1105        {
1106            let mut signal = [1., 1., 1., 1., -1., -1., -1., -1.];
1107            let len = signal.len() as f64;
1108            let dt = 1. / len;
1109
1110            for (index, s) in signal.iter_mut().enumerate() {
1111                let phase = index as f64 / len;
1112
1113                *s += OscillatorRenderer::poly_blep(phase, dt, false);
1114                *s -= OscillatorRenderer::poly_blep((phase + 0.5) % 1., dt, false);
1115            }
1116
1117            let expected = [0., 1., 1., 1., 0., -1., -1., -1.];
1118
1119            assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
1120        }
1121
1122        // sawtooth
1123        {
1124            let mut signal = [0., 0.25, 0.75, 1., -1., -0.75, -0.5, -0.25];
1125            let len = signal.len() as f64;
1126            let dt = 1. / len;
1127
1128            for (index, s) in signal.iter_mut().enumerate() {
1129                let phase = index as f64 / len;
1130                *s -= OscillatorRenderer::poly_blep((phase + 0.5) % 1., dt, false);
1131            }
1132
1133            let expected = [0., 0.25, 0.75, 1., 0., -0.75, -0.5, -0.25];
1134            assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
1135        }
1136    }
1137
1138    #[test]
1139    fn osc_sub_quantum_start() {
1140        let freq = 1.25;
1141        let sample_rate = 44_100;
1142
1143        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1144        let mut osc = context.create_oscillator();
1145        osc.connect(&context.destination());
1146        osc.frequency().set_value(freq);
1147        osc.start_at(2. / sample_rate as f64);
1148
1149        let output = context.start_rendering_sync();
1150        let result = output.get_channel_data(0);
1151
1152        let mut expected = Vec::<f32>::with_capacity(sample_rate);
1153        let mut phase: f64 = 0.;
1154        let phase_incr = freq as f64 / sample_rate as f64;
1155
1156        expected.push(0.);
1157        expected.push(0.);
1158
1159        for _i in 2..sample_rate {
1160            let sample = (phase * 2. * PI).sin();
1161            phase += phase_incr;
1162            expected.push(sample as f32);
1163        }
1164
1165        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1166    }
1167
1168    // # Test scheduling
1169
1170    #[test]
1171    fn osc_sub_sample_start() {
1172        let freq = 1.;
1173        let sample_rate = 96000;
1174
1175        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1176        let mut osc = context.create_oscillator();
1177        osc.connect(&context.destination());
1178        osc.frequency().set_value(freq);
1179        // start between second and third sample
1180        osc.start_at(1.3 / sample_rate as f64);
1181
1182        let output = context.start_rendering_sync();
1183        let result = output.get_channel_data(0);
1184
1185        let mut expected = Vec::<f32>::with_capacity(sample_rate);
1186        let phase_incr = freq as f64 / sample_rate as f64;
1187        // on first computed sample, phase is 0.7 (e.g. 2. - 1.3) * phase_incr
1188        let mut phase: f64 = 0.7 * phase_incr;
1189
1190        expected.push(0.);
1191        expected.push(0.);
1192
1193        for _i in 2..sample_rate {
1194            let sample = (phase * 2. * PI).sin();
1195            phase += phase_incr;
1196            expected.push(sample as f32);
1197        }
1198
1199        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1200    }
1201
1202    #[test]
1203    fn osc_sub_quantum_stop() {
1204        let freq = 2345.6;
1205        let sample_rate = 44_100;
1206
1207        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1208        let mut osc = context.create_oscillator();
1209        osc.connect(&context.destination());
1210        osc.frequency().set_value(freq);
1211        osc.start_at(0.);
1212        osc.stop_at(6. / sample_rate as f64);
1213
1214        let output = context.start_rendering_sync();
1215        let result = output.get_channel_data(0);
1216
1217        let mut expected = Vec::<f32>::with_capacity(sample_rate);
1218        let mut phase: f64 = 0.;
1219        let phase_incr = freq as f64 / sample_rate as f64;
1220
1221        for i in 0..sample_rate {
1222            if i < 6 {
1223                let sample = (phase * 2. * PI).sin();
1224                phase += phase_incr;
1225                expected.push(sample as f32);
1226            } else {
1227                expected.push(0.);
1228            }
1229        }
1230
1231        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1232    }
1233
1234    #[test]
1235    fn osc_stop_disarms_future_start() {
1236        let sample_rate = 44_100;
1237        let future_start = 2. / sample_rate as f64;
1238
1239        let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1240        let mut osc = context.create_oscillator();
1241        osc.connect(&context.destination());
1242        osc.start_at(future_start);
1243        osc.stop();
1244
1245        let output = context.start_rendering_sync();
1246        let result = output.get_channel_data(0);
1247
1248        assert_float_eq!(result[..], vec![0.; 128][..], abs_all <= 0.);
1249    }
1250
1251    #[test]
1252    fn osc_stop_before_start_triggers_onended_without_waiting_for_start_time() {
1253        use std::sync::atomic::{AtomicBool, Ordering};
1254        use std::sync::Arc;
1255
1256        let sample_rate = 44_100.;
1257        let future_start = 2. * RENDER_QUANTUM_SIZE as f64 / sample_rate;
1258        let suspend_at = RENDER_QUANTUM_SIZE as f64 / sample_rate;
1259
1260        let ended = Arc::new(AtomicBool::new(false));
1261        let ended_in_callback = Arc::clone(&ended);
1262        let ended_after_render = Arc::clone(&ended);
1263
1264        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 4, sample_rate as f32);
1265        let mut osc = context.create_oscillator();
1266        osc.connect(&context.destination());
1267        osc.start_at(future_start);
1268        osc.set_onended(move |_| {
1269            ended_in_callback.store(true, Ordering::Relaxed);
1270        });
1271        osc.stop();
1272
1273        context.suspend_sync(suspend_at, move |_| {
1274            assert!(ended_after_render.load(Ordering::Relaxed));
1275        });
1276
1277        let _ = context.start_rendering_sync();
1278        assert!(ended.load(Ordering::Relaxed));
1279    }
1280
1281    #[test]
1282    fn osc_sub_sample_stop() {
1283        let freq = 8910.1;
1284        let sample_rate = 44_100;
1285
1286        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1287        let mut osc = context.create_oscillator();
1288        osc.connect(&context.destination());
1289        osc.frequency().set_value(freq);
1290        osc.start_at(0.);
1291        osc.stop_at(19.4 / sample_rate as f64);
1292
1293        let output = context.start_rendering_sync();
1294        let result = output.get_channel_data(0);
1295
1296        let mut expected = Vec::<f32>::with_capacity(sample_rate);
1297        let mut phase: f64 = 0.;
1298        let phase_incr = freq as f64 / sample_rate as f64;
1299
1300        for i in 0..sample_rate {
1301            if i < 20 {
1302                let sample = (phase * 2. * PI).sin();
1303                phase += phase_incr;
1304                expected.push(sample as f32);
1305            } else {
1306                expected.push(0.);
1307            }
1308        }
1309
1310        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1311    }
1312
1313    #[test]
1314    fn test_start_in_the_past() {
1315        let freq = 8910.1;
1316        let sample_rate = 44_100;
1317
1318        let mut context = OfflineAudioContext::new(1, sample_rate, sample_rate as f32);
1319
1320        context.suspend_sync(128. / sample_rate as f64, move |context| {
1321            let mut osc = context.create_oscillator();
1322            osc.connect(&context.destination());
1323            osc.frequency().set_value(freq);
1324            osc.start_at(0.);
1325        });
1326
1327        let output = context.start_rendering_sync();
1328        let result = output.get_channel_data(0);
1329
1330        let mut expected = Vec::<f32>::with_capacity(sample_rate);
1331        let mut phase: f64 = 0.;
1332        let phase_incr = freq as f64 / sample_rate as f64;
1333
1334        for i in 0..sample_rate {
1335            if i < 128 {
1336                expected.push(0.);
1337            } else {
1338                let sample = (phase * 2. * PI).sin();
1339                expected.push(sample as f32);
1340                phase += phase_incr;
1341            }
1342        }
1343
1344        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1345    }
1346
1347    #[test]
1348    fn compute_freq_above_nyquist_outputs_zero() {
1349        let freq = 20000.;
1350        let detune = 1200.; // one octave upper, then computed feq is 40000Hz
1351        let sample_rate = 44_100;
1352
1353        let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1354
1355        let mut osc = context.create_oscillator();
1356        osc.connect(&context.destination());
1357        osc.frequency().set_value(freq);
1358        osc.detune().set_value(detune);
1359        osc.start_at(0.);
1360
1361        let output = context.start_rendering_sync();
1362        let result = output.get_channel_data(0);
1363
1364        assert_float_eq!(result[..], [0.; 128], abs_all <= 1e-5);
1365    }
1366
1367    #[test]
1368    fn compute_freq_below_negative_nyquist_outputs_zero() {
1369        let freq = -20000.;
1370        let detune = 1200.; // one octave lower, then computed feq is -40000Hz
1371        let sample_rate = 44_100;
1372
1373        let mut context = OfflineAudioContext::new(1, 128, sample_rate as f32);
1374
1375        let mut osc = context.create_oscillator();
1376        osc.connect(&context.destination());
1377        osc.frequency().set_value(freq);
1378        osc.detune().set_value(detune);
1379        osc.start_at(0.);
1380
1381        let output = context.start_rendering_sync();
1382        let result = output.get_channel_data(0);
1383
1384        assert_float_eq!(result[..], [0.; 128], abs_all <= 1e-5);
1385    }
1386
1387    #[test]
1388    fn oscillator_can_reenter_audible_range_after_large_phase_increments() {
1389        let sample_rate = 44_100;
1390        let mut context = OfflineAudioContext::new(1, 256, sample_rate as f32);
1391
1392        let mut osc = context.create_oscillator();
1393        osc.connect(&context.destination());
1394        osc.frequency().set_value(20_000.);
1395        osc.detune().set_value(2400.); // computed frequency is 80_000Hz
1396        osc.detune()
1397            .set_value_at_time(0., RENDER_QUANTUM_SIZE as f64 / sample_rate as f64);
1398        osc.start_at(0.);
1399
1400        let output = context.start_rendering_sync();
1401        let result = output.get_channel_data(0);
1402
1403        assert_float_eq!(
1404            result[..RENDER_QUANTUM_SIZE],
1405            [0.; RENDER_QUANTUM_SIZE],
1406            abs_all <= 1e-5
1407        );
1408        assert!(result[RENDER_QUANTUM_SIZE..].iter().all(|v| v.is_finite()));
1409        assert!(result[RENDER_QUANTUM_SIZE..].iter().any(|&v| v != 0.));
1410    }
1411
1412    #[test]
1413    fn oscillator_delayed_start_renders_first_fully_active_block() {
1414        let sample_rate = 44_100;
1415        let start_time = RENDER_QUANTUM_SIZE as f64 / sample_rate as f64;
1416        let mut context = OfflineAudioContext::new(1, RENDER_QUANTUM_SIZE * 2, sample_rate as f32);
1417
1418        let mut osc = context.create_oscillator();
1419        osc.connect(&context.destination());
1420        osc.start_at(start_time);
1421
1422        let output = context.start_rendering_sync();
1423        let result = output.get_channel_data(0);
1424
1425        assert_float_eq!(
1426            result[..RENDER_QUANTUM_SIZE],
1427            [0.; RENDER_QUANTUM_SIZE],
1428            abs_all <= 1e-5
1429        );
1430        assert!(result[RENDER_QUANTUM_SIZE..].iter().any(|&v| v != 0.));
1431    }
1432
1433    #[test]
1434    fn sine_negative_frequency() {
1435        let freq = -100.;
1436        let sample_rate = 44_100;
1437        let length = sample_rate as usize;
1438
1439        let mut context = OfflineAudioContext::new(1, length, sample_rate as f32);
1440
1441        let mut osc = context.create_oscillator();
1442        osc.connect(&context.destination());
1443        osc.frequency().set_value(freq);
1444        osc.start_at(0.);
1445
1446        let output = context.start_rendering_sync();
1447        let result = output.get_channel_data(0);
1448        let mut expected = Vec::<f32>::with_capacity(length);
1449
1450        for i in 0..length {
1451            let phase = freq as f64 * i as f64 / sample_rate as f64;
1452            let sample = (phase * 2. * PI).sin();
1453            // phase += phase_incr;
1454            expected.push(sample as f32);
1455        }
1456
1457        assert_float_eq!(result[..], expected[..], abs_all <= 1e-5);
1458    }
1459}