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