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