Skip to main content

servo_media_audio/
oscillator_node.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use malloc_size_of_derive::MallocSizeOf;
6
7use crate::audio_node::{
8    AudioNodeEngine, AudioNodeType, AudioScheduledSourceNodeMessage, BlockInfo, ChannelInfo,
9    OnEndedCallback, ShouldPlay,
10};
11use crate::block::{Chunk, Tick};
12use crate::param::{Param, ParamType};
13use crate::periodic_wave::PeriodicWave;
14
15#[derive(Clone, Debug, MallocSizeOf)]
16pub enum OscillatorType {
17    Sine,
18    Square,
19    Sawtooth,
20    Triangle,
21    Custom(PeriodicWave),
22}
23
24#[derive(Clone, Debug, MallocSizeOf)]
25pub struct OscillatorNodeOptions {
26    pub oscillator_type: OscillatorType,
27    pub freq: f32,
28    pub detune: f32,
29}
30
31impl Default for OscillatorNodeOptions {
32    fn default() -> Self {
33        OscillatorNodeOptions {
34            oscillator_type: OscillatorType::Sine,
35            freq: 440.,
36            detune: 0.,
37        }
38    }
39}
40
41#[derive(Clone, Debug, MallocSizeOf)]
42pub enum OscillatorNodeMessage {
43    SetOscillatorType(OscillatorType),
44    SetPeriodicWave(PeriodicWave),
45}
46
47#[derive(AudioScheduledSourceNode, AudioNodeCommon)]
48pub(crate) struct OscillatorNode {
49    channel_info: ChannelInfo,
50    periodic_wave: PeriodicWave,
51    frequency: Param,
52    detune: Param,
53    phase: f64,
54    /// Time at which the source should start playing.
55    start_at: Option<Tick>,
56    /// Time at which the source should stop playing.
57    stop_at: Option<Tick>,
58    /// The ended event callback.
59    onended_callback: Option<OnEndedCallback>,
60}
61
62impl OscillatorNode {
63    pub fn new(options: OscillatorNodeOptions, channel_info: ChannelInfo) -> Self {
64        Self {
65            channel_info,
66            periodic_wave: PeriodicWave::generate_waveform_coefficients(options.oscillator_type),
67            frequency: Param::new(options.freq),
68            detune: Param::new(options.detune),
69            phase: 0.,
70            start_at: None,
71            stop_at: None,
72            onended_callback: None,
73        }
74    }
75
76    pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
77        let (frequency_updated, detune_updated) = (
78            self.frequency.update(info, tick),
79            self.detune.update(info, tick),
80        );
81        frequency_updated || detune_updated
82    }
83
84    fn compute_oscillator_frequency(&self, sample_rate: f64) -> f64 {
85        // Clamp params based on web audio specs
86        // <https://www.w3.org/TR/webaudio-1.1/#dom-oscillatornode-detune>
87        // <https://www.w3.org/TR/webaudio-1.1/#dom-oscillatornode-frequency>
88        let mut detune = self.detune.value() as f64;
89        let critical_detune = 1200.0 * f64::MAX.log2();
90        detune = detune.clamp(-critical_detune, critical_detune);
91        let nyquist = sample_rate / 2.0;
92        let mut frequency = (self.frequency.value() as f64).clamp(-nyquist, nyquist);
93        frequency *= (detune / 1200.0).exp2();
94        // Clamp to nyquist
95        frequency.clamp(-nyquist, nyquist)
96    }
97
98    fn handle_oscillator_message(&mut self, message: OscillatorNodeMessage, _sample_rate: f32) {
99        match message {
100            OscillatorNodeMessage::SetOscillatorType(o) => {
101                self.periodic_wave = PeriodicWave::generate_waveform_coefficients(o);
102            },
103            OscillatorNodeMessage::SetPeriodicWave(w) => {
104                self.periodic_wave = w;
105            },
106        }
107    }
108}
109
110impl AudioNodeEngine for OscillatorNode {
111    fn node_type(&self) -> AudioNodeType {
112        AudioNodeType::OscillatorNode
113    }
114
115    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
116        use std::f64::consts::PI;
117        debug_assert!(inputs.is_empty());
118        inputs.blocks.push(Default::default());
119        let (start_at, stop_at) = match self.should_play_at(info.frame) {
120            ShouldPlay::No => {
121                return inputs;
122            },
123            ShouldPlay::Between(start, end) => (start, end),
124        };
125
126        {
127            inputs.blocks[0].explicit_silence();
128            let mut iter = inputs.blocks[0].iter();
129
130            // Convert all our parameters to the target type for calculations
131            let vol: f32 = 1.0;
132            let sample_rate = info.sample_rate as f64;
133            let two_pi = 2.0 * PI;
134
135            // We're carrying a phase with up to 2pi around instead of working
136            // on the sample offset. High sample offsets cause too much inaccuracy when
137            // converted to floating point numbers and then iterated over in 1-steps
138            //
139            // Also, if the frequency changes the phase should not
140            let mut oscillator_frequency = self.compute_oscillator_frequency(sample_rate);
141            let mut step = two_pi * oscillator_frequency / sample_rate;
142            while let Some(mut frame) = iter.next() {
143                let tick = frame.tick();
144                if tick < start_at {
145                    continue;
146                } else if tick > stop_at {
147                    break;
148                }
149
150                if self.update_parameters(info, tick) {
151                    oscillator_frequency = self.compute_oscillator_frequency(sample_rate);
152                    step = two_pi * oscillator_frequency / sample_rate;
153                }
154                let value = vol *
155                    self.periodic_wave.calculate_waveform(
156                        oscillator_frequency,
157                        sample_rate,
158                        self.phase,
159                    ) as f32;
160
161                frame.mutate_with(|sample, _| *sample = value);
162                // Wrap phase if necessary in order to keep it in radians
163                self.phase = (self.phase + step).rem_euclid(two_pi);
164            }
165        }
166        inputs
167    }
168
169    fn input_count(&self) -> u32 {
170        0
171    }
172
173    fn get_param(&mut self, id: ParamType) -> &mut Param {
174        match id {
175            ParamType::Frequency => &mut self.frequency,
176            ParamType::Detune => &mut self.detune,
177            _ => panic!("Unknown param {:?} for OscillatorNode", id),
178        }
179    }
180    make_message_handler!(
181        AudioScheduledSourceNode: handle_source_node_message,
182        OscillatorNode: handle_oscillator_message
183    );
184}