diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 04db006e130..8da4cbb79e8 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -793,6 +793,7 @@ set(SOURCES WebAudio/BaseAudioContext.cpp WebAudio/BiquadFilterNode.cpp WebAudio/ChannelMergerNode.cpp + WebAudio/ChannelSplitterNode.cpp WebAudio/DynamicsCompressorNode.cpp WebAudio/GainNode.cpp WebAudio/OfflineAudioContext.cpp diff --git a/Libraries/LibWeb/WebAudio/AudioNode.h b/Libraries/LibWeb/WebAudio/AudioNode.h index 4818e7cbe99..90736361423 100644 --- a/Libraries/LibWeb/WebAudio/AudioNode.h +++ b/Libraries/LibWeb/WebAudio/AudioNode.h @@ -65,7 +65,7 @@ public: virtual WebIDL::ExceptionOr set_channel_count_mode(Bindings::ChannelCountMode); Bindings::ChannelCountMode channel_count_mode(); - WebIDL::ExceptionOr set_channel_interpretation(Bindings::ChannelInterpretation); + virtual WebIDL::ExceptionOr set_channel_interpretation(Bindings::ChannelInterpretation); Bindings::ChannelInterpretation channel_interpretation(); WebIDL::ExceptionOr initialize_audio_node_options(AudioNodeOptions const& given_options, AudioNodeDefaultOptions const& default_options); diff --git a/Libraries/LibWeb/WebAudio/ChannelSplitterNode.cpp b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.cpp new file mode 100644 index 00000000000..e181a6b44bc --- /dev/null +++ b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::WebAudio { + +GC_DEFINE_ALLOCATOR(ChannelSplitterNode); + +ChannelSplitterNode::ChannelSplitterNode(JS::Realm& realm, GC::Ref context, ChannelSplitterOptions const& options) + : AudioNode(realm, context) + , m_number_of_outputs(options.number_of_outputs) +{ +} + +ChannelSplitterNode::~ChannelSplitterNode() = default; + +WebIDL::ExceptionOr> ChannelSplitterNode::create(JS::Realm& realm, GC::Ref context, ChannelSplitterOptions const& options) +{ + return construct_impl(realm, context, options); +} + +WebIDL::ExceptionOr> ChannelSplitterNode::construct_impl(JS::Realm& realm, GC::Ref context, ChannelSplitterOptions const& options) +{ + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter + // An IndexSizeError exception MUST be thrown if numberOfOutputs is less than 1 or is greater than the number of supported channels. + if (options.number_of_outputs < 1 || options.number_of_outputs > BaseAudioContext::MAX_NUMBER_OF_CHANNELS) + return WebIDL::IndexSizeError::create(realm, "Invalid number of outputs"_string); + + auto node = realm.create(realm, context, options); + + // Default options for channel count and interpretation + // https://webaudio.github.io/web-audio-api/#ChannelSplitterNode + AudioNodeDefaultOptions default_options; + default_options.channel_count_mode = Bindings::ChannelCountMode::Explicit; + default_options.channel_interpretation = Bindings::ChannelInterpretation::Discrete; + default_options.channel_count = node->number_of_outputs(); + // FIXME: Set tail-time to no + + TRY(node->initialize_audio_node_options(options, default_options)); + + return node; +} + +void ChannelSplitterNode::initialize(JS::Realm& realm) +{ + AudioNode::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(ChannelSplitterNode); +} + +WebIDL::ExceptionOr ChannelSplitterNode::set_channel_count(WebIDL::UnsignedLong channel_count) +{ + // https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints + // The channel count cannot be changed, and an InvalidStateError exception MUST be thrown for any attempt to change the value. + if (channel_count != m_number_of_outputs) + return WebIDL::InvalidStateError::create(realm(), "Channel count must be equal to number of outputs"_string); + + return AudioNode::set_channel_count(channel_count); +} + +WebIDL::ExceptionOr ChannelSplitterNode::set_channel_count_mode(Bindings::ChannelCountMode channel_count_mode) +{ + // https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints + // The channel count mode cannot be changed from "explicit" and an InvalidStateError exception MUST be thrown for any attempt to change the value. + if (channel_count_mode != Bindings::ChannelCountMode::Explicit) + return WebIDL::InvalidStateError::create(realm(), "Channel count mode must be 'explicit'"_string); + + return AudioNode::set_channel_count_mode(channel_count_mode); +} + +WebIDL::ExceptionOr ChannelSplitterNode::set_channel_interpretation(Bindings::ChannelInterpretation channel_interpretation) +{ + // https://webaudio.github.io/web-audio-api/#audionode-channelinterpretation-constraints + // The channel intepretation can not be changed from "discrete" and a InvalidStateError exception MUST be thrown for any attempt to change the value. + if (channel_interpretation != Bindings::ChannelInterpretation::Discrete) + return WebIDL::InvalidStateError::create(realm(), "Channel interpretation must be 'discrete'"_string); + + return AudioNode::set_channel_interpretation(channel_interpretation); +} + +} diff --git a/Libraries/LibWeb/WebAudio/ChannelSplitterNode.h b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.h new file mode 100644 index 00000000000..9b7aa1dd475 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::WebAudio { + +// https://webaudio.github.io/web-audio-api/#ChannelSplitterOptions +struct ChannelSplitterOptions : AudioNodeOptions { + WebIDL::UnsignedLong number_of_outputs { 6 }; +}; + +/// https://webaudio.github.io/web-audio-api/#ChannelSplitterNode +class ChannelSplitterNode final : public AudioNode { + WEB_PLATFORM_OBJECT(ChannelSplitterNode, AudioNode); + GC_DECLARE_ALLOCATOR(ChannelSplitterNode); + +public: + virtual ~ChannelSplitterNode() override; + + static WebIDL::ExceptionOr> create(JS::Realm&, GC::Ref, ChannelSplitterOptions const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref, ChannelSplitterOptions const& = {}); + + virtual WebIDL::UnsignedLong number_of_inputs() override { return 1; } + virtual WebIDL::UnsignedLong number_of_outputs() override { return m_number_of_outputs; } + + virtual WebIDL::ExceptionOr set_channel_count(WebIDL::UnsignedLong) override; + virtual WebIDL::ExceptionOr set_channel_count_mode(Bindings::ChannelCountMode) override; + virtual WebIDL::ExceptionOr set_channel_interpretation(Bindings::ChannelInterpretation) override; + +private: + ChannelSplitterNode(JS::Realm&, GC::Ref, ChannelSplitterOptions const&); + + virtual void initialize(JS::Realm&) override; + + WebIDL::UnsignedLong m_number_of_outputs; +}; + +} diff --git a/Libraries/LibWeb/WebAudio/ChannelSplitterNode.idl b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.idl new file mode 100644 index 00000000000..639e6ae0d4c --- /dev/null +++ b/Libraries/LibWeb/WebAudio/ChannelSplitterNode.idl @@ -0,0 +1,13 @@ +#import +#import + +// https://webaudio.github.io/web-audio-api/#ChannelSplitterNode +[Exposed=Window] +interface ChannelSplitterNode : AudioNode { + constructor (BaseAudioContext context, optional ChannelSplitterOptions options = {}); +}; + +// https://webaudio.github.io/web-audio-api/#ChannelSplitterOptions +dictionary ChannelSplitterOptions : AudioNodeOptions { + unsigned long numberOfOutputs = 6; +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index f28e335f6b5..8c06aa588ff 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -370,6 +370,7 @@ libweb_js_bindings(WebAudio/BiquadFilterNode) libweb_js_bindings(WebAudio/DynamicsCompressorNode) libweb_js_bindings(WebAudio/GainNode) libweb_js_bindings(WebAudio/ChannelMergerNode) +libweb_js_bindings(WebAudio/ChannelSplitterNode) libweb_js_bindings(WebAudio/OfflineAudioContext) libweb_js_bindings(WebAudio/OscillatorNode) libweb_js_bindings(WebAudio/PannerNode) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 83c75af1c98..224db943aba 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -57,6 +57,7 @@ CanvasGradient CanvasPattern CanvasRenderingContext2D ChannelMergerNode +ChannelSplitterNode CharacterData Clipboard ClipboardEvent diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.txt new file mode 100644 index 00000000000..07107b4c677 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.txt @@ -0,0 +1,52 @@ +Harness status: OK + +Found 47 tests + +47 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "initialize" +Pass Executing "invalid constructor" +Pass Executing "default constructor" +Pass Executing "test AudioNodeOptions" +Pass Executing "constructor options" +Pass Audit report +Pass > [initialize] +Pass context = new OfflineAudioContext(...) did not throw an exception. +Pass < [initialize] All assertions passed. (total 1 assertions) +Pass > [invalid constructor] +Pass new ChannelSplitterNode() threw TypeError: "ChannelSplitterNode() needs one argument". +Pass new ChannelSplitterNode(1) threw TypeError: "Not an object of type BaseAudioContext". +Pass new ChannelSplitterNode(context, 42) threw TypeError: "Not an object of type ChannelSplitterOptions". +Pass < [invalid constructor] All assertions passed. (total 3 assertions) +Pass > [default constructor] +Pass node0 = new ChannelSplitterNode(context) did not throw an exception. +Pass node0 instanceof ChannelSplitterNode is equal to true. +Pass node0.numberOfInputs is equal to 1. +Pass node0.numberOfOutputs is equal to 6. +Pass node0.channelCount is equal to 6. +Pass node0.channelCountMode is equal to explicit. +Pass node0.channelInterpretation is equal to discrete. +Pass < [default constructor] All assertions passed. (total 7 assertions) +Pass > [test AudioNodeOptions] +Pass new ChannelSplitterNode(c, {channelCount: 6}) did not throw an exception. +Pass node.channelCount is equal to 6. +Pass new ChannelSplitterNode(c, {channelCount: 7}) threw InvalidStateError: "Channel count must be equal to number of outputs". +Pass (new ChannelSplitterNode(c, {channelCount: 6})).channelCount = 6 did not throw an exception. +Pass new ChannelSplitterNode(c, {channelCountMode: "explicit"} did not throw an exception. +Pass node.channelCountMode is equal to explicit. +Pass new ChannelSplitterNode(c, {channelCountMode: "max"}) threw InvalidStateError: "Channel count mode must be 'explicit'". +Pass new ChannelSplitterNode(c, {channelCountMode: "clamped-max"}) threw InvalidStateError: "Channel count mode must be 'explicit'". +Pass (new ChannelSplitterNode(c, {channelCountMode: "explicit"})).channelCountMode = "explicit" did not throw an exception. +Pass new ChannelSplitterNode(c, {channelInterpretation: "speakers"}) threw InvalidStateError: "Channel interpretation must be 'discrete'". +Pass (new ChannelSplitterNode(c, {channelInterpretation: "discrete"})).channelInterpretation = "discrete" did not throw an exception. +Pass < [test AudioNodeOptions] All assertions passed. (total 11 assertions) +Pass > [constructor options] +Pass node1 = new ChannelSplitterNode(context, {"numberOfInputs":3,"numberOfOutputs":9,"channelInterpretation":"discrete"}) did not throw an exception. +Pass node1.numberOfInputs is equal to 1. +Pass node1.numberOfOutputs is equal to 9. +Pass node1.channelInterpretation is equal to discrete. +Pass new ChannelSplitterNode(c, {"numberOfOutputs":99}) threw IndexSizeError: "Invalid number of outputs". +Pass new ChannelSplitterNode(c, {"channelCount":3}) threw InvalidStateError: "Channel count must be equal to number of outputs". +Pass new ChannelSplitterNode(c, {"channelCountMode":"max"}) threw InvalidStateError: "Channel count mode must be 'explicit'". +Pass < [constructor options] All assertions passed. (total 7 assertions) +Pass # AUDIT TASK RUNNER FINISHED: 5 tasks ran successfully. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html new file mode 100644 index 00000000000..7185509c17e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-channelsplitternode-interface/ctor-channelsplitter.html @@ -0,0 +1,115 @@ + + + + + Test Constructor: ChannelSplitter + + + + + + + + + + +