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;
6use num_traits::cast::NumCast;
7
8use crate::block::{Chunk, Tick};
9use crate::node::{
10    AudioNodeEngine, AudioNodeType, AudioScheduledSourceNodeMessage, BlockInfo, ChannelInfo,
11    OnEndedCallback, ShouldPlay,
12};
13use crate::param::{Param, ParamType};
14
15#[derive(Clone, Debug, MallocSizeOf)]
16pub struct PeriodicWaveOptions {
17    // XXX https://webaudio.github.io/web-audio-api/#dictdef-periodicwaveoptions
18}
19#[derive(Clone, Debug, MallocSizeOf)]
20pub enum OscillatorType {
21    Sine,
22    Square,
23    Sawtooth,
24    Triangle,
25    Custom,
26}
27
28#[derive(Clone, Debug, MallocSizeOf)]
29pub struct OscillatorNodeOptions {
30    pub oscillator_type: OscillatorType,
31    pub freq: f32,
32    pub detune: f32,
33    pub periodic_wave_options: Option<PeriodicWaveOptions>,
34}
35
36impl Default for OscillatorNodeOptions {
37    fn default() -> Self {
38        OscillatorNodeOptions {
39            oscillator_type: OscillatorType::Sine,
40            freq: 440.,
41            detune: 0.,
42            periodic_wave_options: None,
43        }
44    }
45}
46
47#[derive(Clone, Debug, MallocSizeOf)]
48pub enum OscillatorNodeMessage {
49    SetOscillatorType(OscillatorType),
50}
51
52#[derive(AudioScheduledSourceNode, AudioNodeCommon)]
53pub(crate) struct OscillatorNode {
54    channel_info: ChannelInfo,
55    oscillator_type: OscillatorType,
56    frequency: Param,
57    detune: Param,
58    phase: f64,
59    /// Time at which the source should start playing.
60    start_at: Option<Tick>,
61    /// Time at which the source should stop playing.
62    stop_at: Option<Tick>,
63    /// The ended event callback.
64    onended_callback: Option<OnEndedCallback>,
65}
66
67impl OscillatorNode {
68    pub fn new(options: OscillatorNodeOptions, channel_info: ChannelInfo) -> Self {
69        Self {
70            channel_info,
71            oscillator_type: options.oscillator_type,
72            frequency: Param::new(options.freq),
73            detune: Param::new(options.detune),
74            phase: 0.,
75            start_at: None,
76            stop_at: None,
77            onended_callback: None,
78        }
79    }
80
81    pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
82        self.frequency.update(info, tick)
83    }
84
85    fn handle_oscillator_message(&mut self, message: OscillatorNodeMessage, _sample_rate: f32) {
86        match message {
87            OscillatorNodeMessage::SetOscillatorType(o) => {
88                self.oscillator_type = o;
89            },
90        }
91    }
92}
93
94impl AudioNodeEngine for OscillatorNode {
95    fn node_type(&self) -> AudioNodeType {
96        AudioNodeType::OscillatorNode
97    }
98
99    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
100        // XXX Implement this properly and according to self.options
101        // as defined in https://webaudio.github.io/web-audio-api/#oscillatornode
102        use std::f64::consts::PI;
103        debug_assert!(inputs.is_empty());
104        inputs.blocks.push(Default::default());
105        let (start_at, stop_at) = match self.should_play_at(info.frame) {
106            ShouldPlay::No => {
107                return inputs;
108            },
109            ShouldPlay::Between(start, end) => (start, end),
110        };
111
112        {
113            inputs.blocks[0].explicit_silence();
114            let mut iter = inputs.blocks[0].iter();
115
116            // Convert all our parameters to the target type for calculations
117            let vol: f32 = 1.0;
118            let sample_rate = info.sample_rate as f64;
119            let two_pi = 2.0 * PI;
120
121            // We're carrying a phase with up to 2pi around instead of working
122            // on the sample offset. High sample offsets cause too much inaccuracy when
123            // converted to floating point numbers and then iterated over in 1-steps
124            //
125            // Also, if the frequency changes the phase should not
126            let mut step = two_pi * self.frequency.value() as f64 / sample_rate;
127            while let Some(mut frame) = iter.next() {
128                let tick = frame.tick();
129                if tick < start_at {
130                    continue;
131                } else if tick > stop_at {
132                    break;
133                }
134
135                if self.update_parameters(info, tick) {
136                    step = two_pi * self.frequency.value() as f64 / sample_rate;
137                }
138                let mut value = vol;
139                match self.oscillator_type {
140                    OscillatorType::Sine => {
141                        value = vol * f32::sin(NumCast::from(self.phase).unwrap());
142                    },
143
144                    OscillatorType::Square => {
145                        if self.phase >= PI && self.phase < two_pi {
146                            value = vol * 1.0;
147                        } else if self.phase > 0.0 && self.phase < PI {
148                            value = -vol;
149                        }
150                    },
151
152                    OscillatorType::Sawtooth => {
153                        value = vol * (self.phase / (PI)) as f32;
154                    },
155
156                    OscillatorType::Triangle => {
157                        if self.phase >= 0. && self.phase < PI / 2. {
158                            value = vol * 2.0 * (self.phase / (PI)) as f32;
159                        } else if self.phase >= PI / 2. && self.phase < PI {
160                            value = vol * (1. - ((self.phase - (PI / 2.)) * (2. / PI)) as f32);
161                        } else if self.phase >= PI && self.phase < (3. * PI / 2.) {
162                            value = -vol * (1. - ((self.phase - (PI / 2.)) * (2. / PI)) as f32);
163                        } else if self.phase >= 3. * PI / 2. && self.phase < 2. * PI {
164                            value = vol * (-2.0) * (self.phase / (PI)) as f32;
165                        }
166                    },
167
168                    OscillatorType::Custom => {},
169                }
170
171                frame.mutate_with(|sample, _| *sample = value);
172
173                self.phase += step;
174                if self.phase >= two_pi {
175                    self.phase -= two_pi;
176                }
177            }
178        }
179        inputs
180    }
181
182    fn input_count(&self) -> u32 {
183        0
184    }
185
186    fn get_param(&mut self, id: ParamType) -> &mut Param {
187        match id {
188            ParamType::Frequency => &mut self.frequency,
189            ParamType::Detune => &mut self.detune,
190            _ => panic!("Unknown param {:?} for OscillatorNode", id),
191        }
192    }
193    make_message_handler!(
194        AudioScheduledSourceNode: handle_source_node_message,
195        OscillatorNode: handle_oscillator_message
196    );
197}