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::reflector::reflect_dom_object_with_proto;
11use servo_media::audio::audio_node::{AudioNodeInit, AudioNodeMessage, AudioNodeType};
12use servo_media::audio::oscillator_node::{
13    OscillatorNodeMessage, OscillatorNodeOptions as ServoMediaOscillatorOptions,
14    OscillatorType as ServoMediaOscillatorType,
15};
16use servo_media::audio::param::ParamType;
17use servo_media::audio::periodic_wave::PeriodicWave as ServoMediaPeriodicWave;
18
19use crate::conversions::Convert;
20use crate::dom::audio::audionode::AudioNodeOptionsHelper;
21use crate::dom::audio::audioparam::AudioParam;
22use crate::dom::audio::audioscheduledsourcenode::AudioScheduledSourceNode;
23use crate::dom::audio::baseaudiocontext::BaseAudioContext;
24use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
25    ChannelCountMode, ChannelInterpretation,
26};
27use crate::dom::bindings::codegen::Bindings::AudioParamBinding::AutomationRate;
28use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::{
29    OscillatorNodeMethods, OscillatorOptions, OscillatorType,
30};
31use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
32use crate::dom::bindings::root::{Dom, DomRoot};
33use crate::dom::types::PeriodicWave;
34use crate::dom::window::Window;
35
36#[dom_struct]
37pub(crate) struct OscillatorNode {
38    source_node: AudioScheduledSourceNode,
39    detune: Dom<AudioParam>,
40    frequency: Dom<AudioParam>,
41    oscillator_type: Cell<OscillatorType>,
42}
43
44impl OscillatorNode {
45    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
46    pub(crate) fn new_inherited(
47        cx: &mut JSContext,
48        window: &Window,
49        context: &BaseAudioContext,
50        options: &OscillatorOptions,
51    ) -> Fallible<OscillatorNode> {
52        if matches!(options.type_, OscillatorType::Custom) && options.periodicWave.is_none() {
53            return Err(Error::InvalidState(Some(String::from(
54                "Can not set oscillator type to custom without providing a periodic wave",
55            ))));
56        }
57
58        let oscillator_type = if options.periodicWave.is_some() {
59            OscillatorType::Custom
60        } else {
61            options.type_
62        };
63
64        let node_options =
65            options
66                .parent
67                .unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers);
68
69        let maybe_periodic_wave = options
70            .periodicWave
71            .as_ref()
72            .map(|x| (*x.as_traced()).convert());
73
74        let options = ServoMediaOscillatorOptions {
75            oscillator_type: convert_oscillator_options(oscillator_type, maybe_periodic_wave)?,
76            freq: *options.frequency,
77            detune: *options.detune,
78        };
79        let source_node = AudioScheduledSourceNode::new_inherited(
80            cx,
81            AudioNodeInit::OscillatorNode(options),
82            context,
83            node_options,
84            0, /* inputs */
85            1, /* outputs */
86        )?;
87        let node_id = source_node.node().node_id();
88        let frequency = AudioParam::new(
89            cx,
90            window,
91            context,
92            node_id,
93            AudioNodeType::OscillatorNode,
94            ParamType::Frequency,
95            AutomationRate::A_rate,
96            440.,
97            f32::MIN,
98            f32::MAX,
99        );
100        let detune = AudioParam::new(
101            cx,
102            window,
103            context,
104            node_id,
105            AudioNodeType::OscillatorNode,
106            ParamType::Detune,
107            AutomationRate::A_rate,
108            0.,
109            -440. / 2.,
110            440. / 2.,
111        );
112        Ok(OscillatorNode {
113            source_node,
114            oscillator_type: Cell::new(oscillator_type),
115            frequency: Dom::from_ref(&frequency),
116            detune: Dom::from_ref(&detune),
117        })
118    }
119
120    pub(crate) fn new(
121        cx: &mut JSContext,
122        window: &Window,
123        context: &BaseAudioContext,
124        options: &OscillatorOptions,
125    ) -> Fallible<DomRoot<OscillatorNode>> {
126        Self::new_with_proto(cx, window, None, context, options)
127    }
128
129    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
130    fn new_with_proto(
131        cx: &mut JSContext,
132        window: &Window,
133        proto: Option<HandleObject>,
134        context: &BaseAudioContext,
135        options: &OscillatorOptions,
136    ) -> Fallible<DomRoot<OscillatorNode>> {
137        let node = OscillatorNode::new_inherited(cx, window, context, options)?;
138        Ok(reflect_dom_object_with_proto(
139            cx,
140            Box::new(node),
141            window,
142            proto,
143        ))
144    }
145}
146
147impl OscillatorNodeMethods<crate::DomTypeHolder> for OscillatorNode {
148    /// <https://webaudio.github.io/web-audio-api/#dom-oscillatornode-oscillatornode>
149    fn Constructor(
150        cx: &mut JSContext,
151        window: &Window,
152        proto: Option<HandleObject>,
153        context: &BaseAudioContext,
154        options: &OscillatorOptions,
155    ) -> Fallible<DomRoot<OscillatorNode>> {
156        OscillatorNode::new_with_proto(cx, window, proto, context, options)
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}