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