servo_media_audio/
oscillator_node.rs

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