web_audio_api/node/
oscillator.rs

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