Skip to main content

script/dom/audio/
oscillatornode.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 std::cell::Cell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::rust::HandleObject;
10use script_bindings::codegen::GenericBindings::BaseAudioContextBinding::BaseAudioContextMethods;
11use script_bindings::reflector::reflect_dom_object_with_proto;
12use servo_media::audio::audio_node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
13use servo_media::audio::graph::NodeId;
14use servo_media::audio::oscillator_node::{
15    OscillatorNodeMessage, OscillatorNodeOptions as ServoMediaOscillatorOptions,
16    OscillatorType as ServoMediaOscillatorType,
17};
18use servo_media::audio::param::ParamType;
19use servo_media::audio::periodic_wave::PeriodicWave as ServoMediaPeriodicWave;
20
21use crate::conversions::Convert;
22use crate::dom::audio::audionode::{AudioNode, AudioNodeOptionsHelper};
23use crate::dom::audio::audioparam::AudioParam;
24use crate::dom::audio::audioscheduledsourcenode::AudioScheduledSourceNode;
25use crate::dom::audio::baseaudiocontext::BaseAudioContext;
26use crate::dom::audionode::UnwrappedAudioNodeOptions;
27use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
28    ChannelCountMode, ChannelInterpretation,
29};
30use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate;
31use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::{
32    OscillatorNodeMethods, OscillatorOptions, OscillatorType,
33};
34use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
35use crate::dom::bindings::root::{Dom, DomRoot};
36use crate::dom::types::PeriodicWave;
37use crate::dom::window::Window;
38
39#[dom_struct]
40pub(crate) struct OscillatorNode {
41    source_node: AudioScheduledSourceNode,
42    detune: Dom<AudioParam>,
43    frequency: Dom<AudioParam>,
44    oscillator_type: Cell<OscillatorType>,
45}
46
47impl OscillatorNode {
48    fn new_inherited(
49        node_id: Option<NodeId>,
50        context: &BaseAudioContext,
51        node_options: UnwrappedAudioNodeOptions,
52        oscillator_type: OscillatorType,
53        frequency: &AudioParam,
54        detune: &AudioParam,
55    ) -> Self {
56        OscillatorNode {
57            source_node: AudioScheduledSourceNode::new_inherited(
58                node_id,
59                context,
60                node_options,
61                0, /* inputs */
62                1, /* outputs */
63            ),
64            oscillator_type: Cell::new(oscillator_type),
65            frequency: Dom::from_ref(frequency),
66            detune: Dom::from_ref(detune),
67        }
68    }
69}
70
71impl OscillatorNodeMethods<crate::DomTypeHolder> for OscillatorNode {
72    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-oscillatornode>
73    fn Constructor(
74        cx: &mut JSContext,
75        window: &Window,
76        proto: Option<HandleObject>,
77        context: &BaseAudioContext,
78        options: &OscillatorOptions,
79    ) -> Fallible<DomRoot<OscillatorNode>> {
80        if matches!(options.type_, OscillatorType::Custom) && options.periodicWave.is_none() {
81            return Err(Error::InvalidState(Some(String::from(
82                "Can not set oscillator type to custom without providing a periodic wave",
83            ))));
84        }
85
86        let oscillator_type = if options.periodicWave.is_some() {
87            OscillatorType::Custom
88        } else {
89            options.type_
90        };
91
92        let node_options =
93            options
94                .parent
95                .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers);
96        if AudioNode::channel_count_check(node_options.count) {
97            return Err(Error::NotSupported(Some(String::from(
98                "Channel count not supported",
99            ))));
100        }
101
102        let maybe_periodic_wave = options
103            .periodicWave
104            .as_ref()
105            .map(|x| (*x.as_traced()).convert());
106
107        let options = ServoMediaOscillatorOptions {
108            oscillator_type: convert_oscillator_options(oscillator_type, maybe_periodic_wave)?,
109            freq: *options.frequency,
110            detune: *options.detune,
111        };
112        let audio_node_init = AudioNodeInit::OscillatorNode(options);
113        let node_id =
114            AudioNode::generate_audio_node_id(cx, &node_options, audio_node_init, context);
115
116        let nyquist = (*context.SampleRate()) / 2.;
117        // <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-frequency>
118        let frequency = AudioParam::new(
119            cx,
120            window,
121            context,
122            node_id,
123            AudioNodeType::OscillatorNode,
124            ParamType::Frequency,
125            AutomationRate::A_rate,
126            440.,
127            -nyquist,
128            nyquist,
129        );
130        // <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-detune>
131        let detune_max = 1200. * f32::MAX.log2();
132        let detune = AudioParam::new(
133            cx,
134            window,
135            context,
136            node_id,
137            AudioNodeType::OscillatorNode,
138            ParamType::Detune,
139            AutomationRate::A_rate,
140            0.,
141            -detune_max,
142            detune_max,
143        );
144        Ok(reflect_dom_object_with_proto(
145            cx,
146            Box::new(OscillatorNode::new_inherited(
147                node_id,
148                context,
149                node_options,
150                oscillator_type,
151                &frequency,
152                &detune,
153            )),
154            window,
155            proto,
156        ))
157    }
158
159    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-setperiodicwave>
160    fn SetPeriodicWave(&self, periodic_wave: &PeriodicWave) -> ErrorResult {
161        self.oscillator_type.set(OscillatorType::Custom);
162        self.source_node
163            .node()
164            .message(AudioNodeMessage::OscillatorNode(
165                OscillatorNodeMessage::SetPeriodicWave(periodic_wave.convert()),
166            ));
167        Ok(())
168    }
169
170    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-frequency>
171    fn Frequency(&self) -> DomRoot<AudioParam> {
172        DomRoot::from_ref(&self.frequency)
173    }
174
175    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-detune>
176    fn Detune(&self) -> DomRoot<AudioParam> {
177        DomRoot::from_ref(&self.detune)
178    }
179
180    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-type>
181    fn Type(&self) -> OscillatorType {
182        self.oscillator_type.get()
183    }
184
185    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-type>
186    fn SetType(&self, type_: OscillatorType) -> ErrorResult {
187        if type_ == OscillatorType::Custom {
188            return Err(Error::InvalidState(None));
189        }
190        self.oscillator_type.set(type_);
191        self.source_node
192            .node()
193            .message(AudioNodeMessage::OscillatorNode(
194                OscillatorNodeMessage::SetOscillatorType(convert_oscillator_options(type_, None)?),
195            ));
196        Ok(())
197    }
198}
199
200// Helper function because Convert trait is not sufficient to handle Custom variant
201fn convert_oscillator_options(
202    oscillator_type: OscillatorType,
203    maybe_periodic_wave: Option<ServoMediaPeriodicWave>,
204) -> Fallible<ServoMediaOscillatorType> {
205    match oscillator_type {
206        OscillatorType::Sine => Ok(ServoMediaOscillatorType::Sine),
207        OscillatorType::Square => Ok(ServoMediaOscillatorType::Square),
208        OscillatorType::Sawtooth => Ok(ServoMediaOscillatorType::Sawtooth),
209        OscillatorType::Triangle => Ok(ServoMediaOscillatorType::Triangle),
210        OscillatorType::Custom => {
211            let Some(periodic_wave) = maybe_periodic_wave else {
212                return Err(Error::InvalidState(Some(String::from(
213                    "Can not have oscillator type custom without a periodic wave",
214                ))));
215            };
216            Ok(ServoMediaOscillatorType::Custom(periodic_wave))
217        },
218    }
219}