1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-10 10:01:13 +09:00
ladybird/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp
Andreas Kling a6dfc74e93 LibWeb: Only set prototype once for object with IDL interface
Before this change, we were going through the chain of base classes for
each IDL interface object and having them set the prototype to their
prototype.

Instead of doing that, reorder things so that we set the right prototype
immediately in Foo::initialize(), and then don't bother in all the base
class overrides.

This knocks off a ~1% profile item on Speedometer 3.
2025-04-20 18:43:11 +02:00

327 lines
14 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/BaseAudioContextPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/WebAudio/AnalyserNode.h>
#include <LibWeb/WebAudio/AudioBuffer.h>
#include <LibWeb/WebAudio/AudioBufferSourceNode.h>
#include <LibWeb/WebAudio/AudioDestinationNode.h>
#include <LibWeb/WebAudio/BaseAudioContext.h>
#include <LibWeb/WebAudio/BiquadFilterNode.h>
#include <LibWeb/WebAudio/ChannelMergerNode.h>
#include <LibWeb/WebAudio/DynamicsCompressorNode.h>
#include <LibWeb/WebAudio/GainNode.h>
#include <LibWeb/WebAudio/OscillatorNode.h>
#include <LibWeb/WebAudio/PannerNode.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::WebAudio {
BaseAudioContext::BaseAudioContext(JS::Realm& realm, float sample_rate)
: DOM::EventTarget(realm)
, m_sample_rate(sample_rate)
, m_listener(AudioListener::create(realm, *this))
{
}
BaseAudioContext::~BaseAudioContext() = default;
void BaseAudioContext::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(BaseAudioContext);
Base::initialize(realm);
}
void BaseAudioContext::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_destination);
visitor.visit(m_pending_promises);
visitor.visit(m_listener);
}
void BaseAudioContext::set_onstatechange(WebIDL::CallbackType* event_handler)
{
set_event_handler_attribute(HTML::EventNames::statechange, event_handler);
}
WebIDL::CallbackType* BaseAudioContext::onstatechange()
{
return event_handler_attribute(HTML::EventNames::statechange);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser
WebIDL::ExceptionOr<GC::Ref<AnalyserNode>> BaseAudioContext::create_analyser()
{
return AnalyserNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter
WebIDL::ExceptionOr<GC::Ref<BiquadFilterNode>> BaseAudioContext::create_biquad_filter()
{
// Factory method for a BiquadFilterNode representing a second order filter which can be configured as one of several common filter types.
return BiquadFilterNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
WebIDL::ExceptionOr<GC::Ref<AudioBuffer>> BaseAudioContext::create_buffer(WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate)
{
// Creates an AudioBuffer of the given size. The audio data in the buffer will be zero-initialized (silent).
// A NotSupportedError exception MUST be thrown if any of the arguments is negative, zero, or outside its nominal range.
return AudioBuffer::create(realm(), number_of_channels, length, sample_rate);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffersource
WebIDL::ExceptionOr<GC::Ref<AudioBufferSourceNode>> BaseAudioContext::create_buffer_source()
{
// Factory method for a AudioBufferSourceNode.
return AudioBufferSourceNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelmerger
WebIDL::ExceptionOr<GC::Ref<ChannelMergerNode>> BaseAudioContext::create_channel_merger(WebIDL::UnsignedLong number_of_inputs)
{
ChannelMergerOptions options;
options.number_of_inputs = number_of_inputs;
return ChannelMergerNode::create(realm(), *this, options);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createconstantsource
WebIDL::ExceptionOr<GC::Ref<ConstantSourceNode>> BaseAudioContext::create_constant_source()
{
return ConstantSourceNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createdelay
WebIDL::ExceptionOr<GC::Ref<DelayNode>> BaseAudioContext::create_delay(double max_delay_time)
{
DelayOptions options;
options.max_delay_time = max_delay_time;
return DelayNode::create(realm(), *this, options);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter
WebIDL::ExceptionOr<GC::Ref<ChannelSplitterNode>> BaseAudioContext::create_channel_splitter(WebIDL::UnsignedLong number_of_outputs)
{
ChannelSplitterOptions options;
options.number_of_outputs = number_of_outputs;
return ChannelSplitterNode::create(realm(), *this, options);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createoscillator
WebIDL::ExceptionOr<GC::Ref<OscillatorNode>> BaseAudioContext::create_oscillator()
{
// Factory method for an OscillatorNode.
return OscillatorNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createdynamicscompressor
WebIDL::ExceptionOr<GC::Ref<DynamicsCompressorNode>> BaseAudioContext::create_dynamics_compressor()
{
// Factory method for a DynamicsCompressorNode.
return DynamicsCompressorNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-creategain
WebIDL::ExceptionOr<GC::Ref<GainNode>> BaseAudioContext::create_gain()
{
// Factory method for GainNode.
return GainNode::create(realm(), *this);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner
WebIDL::ExceptionOr<GC::Ref<PannerNode>> BaseAudioContext::create_panner()
{
// Factory method for a PannerNode.
return PannerNode::create(realm(), *this);
}
WebIDL::ExceptionOr<GC::Ref<PeriodicWave>> BaseAudioContext::create_periodic_wave(Vector<float> const& real, Vector<float> const& imag, Optional<PeriodicWaveConstraints> const& constraints)
{
PeriodicWaveOptions options;
options.real = real;
options.imag = imag;
if (constraints.has_value())
options.disable_normalization = constraints->disable_normalization;
return PeriodicWave::construct_impl(realm(), *this, options);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createstereopanner
WebIDL::ExceptionOr<GC::Ref<StereoPannerNode>> BaseAudioContext::create_stereo_panner()
{
// Factory method for a StereoPannerNode.
return StereoPannerNode::create(realm(), *this);
}
WebIDL::ExceptionOr<void> BaseAudioContext::verify_audio_options_inside_nominal_range(JS::Realm& realm, float sample_rate)
{
if (sample_rate < MIN_SAMPLE_RATE || sample_rate > MAX_SAMPLE_RATE)
return WebIDL::NotSupportedError::create(realm, "Sample rate is outside of allowed range"_string);
return {};
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
WebIDL::ExceptionOr<void> BaseAudioContext::verify_audio_options_inside_nominal_range(JS::Realm& realm, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate)
{
// A NotSupportedError exception MUST be thrown if any of the arguments is negative, zero, or outside its nominal range.
if (number_of_channels == 0)
return WebIDL::NotSupportedError::create(realm, "Number of channels must not be '0'"_string);
if (number_of_channels > MAX_NUMBER_OF_CHANNELS)
return WebIDL::NotSupportedError::create(realm, "Number of channels is greater than allowed range"_string);
if (length == 0)
return WebIDL::NotSupportedError::create(realm, "Length of buffer must be at least 1"_string);
TRY(verify_audio_options_inside_nominal_range(realm, sample_rate));
return {};
}
void BaseAudioContext::queue_a_media_element_task(GC::Ref<GC::Function<void()>> steps)
{
auto task = HTML::Task::create(vm(), m_media_element_event_task_source.source, HTML::current_principal_settings_object().responsible_document(), steps);
HTML::main_thread_event_loop().task_queue().add(task);
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata
GC::Ref<WebIDL::Promise> BaseAudioContext::decode_audio_data(GC::Root<WebIDL::BufferSource> audio_data, GC::Ptr<WebIDL::CallbackType> success_callback, GC::Ptr<WebIDL::CallbackType> error_callback)
{
auto& realm = this->realm();
// FIXME: When decodeAudioData is called, the following steps MUST be performed on the control thread:
// 1. If this's relevant global object's associated Document is not fully active then return a
// promise rejected with "InvalidStateError" DOMException.
auto const& associated_document = as<HTML::Window>(HTML::relevant_global_object(*this)).associated_document();
if (!associated_document.is_fully_active()) {
auto error = WebIDL::InvalidStateError::create(realm, "The document is not fully active."_string);
return WebIDL::create_rejected_promise_from_exception(realm, error);
}
// 2. Let promise be a new Promise.
auto promise = WebIDL::create_promise(realm);
// FIXME: 3. If audioData is detached, execute the following steps:
if (true) {
// 3.1. Append promise to [[pending promises]].
m_pending_promises.append(promise);
// FIXME: 3.2. Detach the audioData ArrayBuffer. If this operations throws, jump to the step 3.
// 3.3. Queue a decoding operation to be performed on another thread.
queue_a_decoding_operation(promise, move(audio_data), success_callback, error_callback);
}
// 4. Else, execute the following error steps:
else {
// 4.1. Let error be a DataCloneError.
auto error = WebIDL::DataCloneError::create(realm, "Audio data is not detached."_string);
// 4.2. Reject promise with error, and remove it from [[pending promises]].
WebIDL::reject_promise(realm, promise, error);
m_pending_promises.remove_first_matching([&promise](auto& pending_promise) {
return pending_promise == promise;
});
// 4.3. Queue a media element task to invoke errorCallback with error.
if (error_callback) {
queue_a_media_element_task(GC::create_function(heap(), [&realm, error_callback, error] {
auto completion = WebIDL::invoke_callback(*error_callback, {}, { { error } });
if (completion.is_abrupt())
HTML::report_exception(completion, realm);
}));
}
}
// 5. Return promise.
return promise;
}
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata
void BaseAudioContext::queue_a_decoding_operation(GC::Ref<JS::PromiseCapability> promise, [[maybe_unused]] GC::Root<WebIDL::BufferSource> audio_data, GC::Ptr<WebIDL::CallbackType> success_callback, GC::Ptr<WebIDL::CallbackType> error_callback)
{
auto& realm = this->realm();
// FIXME: When queuing a decoding operation to be performed on another thread, the following steps
// MUST happen on a thread that is not the control thread nor the rendering thread, called
// the decoding thread.
// 1. Let can decode be a boolean flag, initially set to true.
auto can_decode { true };
// FIXME: 2. Attempt to determine the MIME type of audioData, using MIME Sniffing §6.2 Matching an
// audio or video type pattern. If the audio or video type pattern matching algorithm returns
// undefined, set can decode to false.
// 3. If can decode is true,
if (can_decode) {
// FIXME: attempt to decode the encoded audioData into linear PCM. In case of
// failure, set can decode to false.
// FIXME: If the media byte-stream contains multiple audio tracks, only decode the first track to linear pcm.
}
// 4. If can decode is false,
if (!can_decode) {
// queue a media element task to execute the following steps:
queue_a_media_element_task(GC::create_function(heap(), [this, &realm, promise, error_callback] {
// 4.1. Let error be a DOMException whose name is EncodingError.
auto error = WebIDL::EncodingError::create(realm, "Unable to decode."_string);
// 4.1.2. Reject promise with error, and remove it from [[pending promises]].
WebIDL::reject_promise(realm, promise, error);
m_pending_promises.remove_first_matching([&promise](auto& pending_promise) {
return pending_promise == promise;
});
// 4.2. If errorCallback is not missing, invoke errorCallback with error.
if (error_callback) {
auto completion = WebIDL::invoke_callback(*error_callback, {}, { { error } });
if (completion.is_abrupt())
HTML::report_exception(completion, realm);
}
}));
}
// 5. Otherwise:
else {
// FIXME: 5.1. Take the result, representing the decoded linear PCM audio data, and resample it to the
// sample-rate of the BaseAudioContext if it is different from the sample-rate of
// audioData.
// FIXME: 5.2. queue a media element task to execute the following steps:
// FIXME: 5.2.1. Let buffer be an AudioBuffer containing the final result (after possibly performing
// sample-rate conversion).
auto buffer = MUST(create_buffer(2, 1, 44100));
// 5.2.2. Resolve promise with buffer.
WebIDL::resolve_promise(realm, promise, buffer);
// 5.2.3. If successCallback is not missing, invoke successCallback with buffer.
if (success_callback) {
auto completion = WebIDL::invoke_callback(*success_callback, {}, { { buffer } });
if (completion.is_abrupt())
HTML::report_exception(completion, realm);
}
}
}
}