From 56907e2de65c3806c9758a6bd8a89cd1fa7175d9 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Fri, 17 Jan 2025 14:56:20 +0000 Subject: [PATCH] LibWeb: Add `StereoPannerNode` interface --- Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/WebAudio/StereoPannerNode.cpp | 85 ++++++++++++ Libraries/LibWeb/WebAudio/StereoPannerNode.h | 49 +++++++ .../LibWeb/WebAudio/StereoPannerNode.idl | 15 ++ Libraries/LibWeb/idl_files.cmake | 1 + .../Text/expected/all-window-properties.txt | 1 + .../ctor-stereopanner.txt | 56 ++++++++ .../ctor-stereopanner.html | 131 ++++++++++++++++++ 8 files changed, 339 insertions(+) create mode 100644 Libraries/LibWeb/WebAudio/StereoPannerNode.cpp create mode 100644 Libraries/LibWeb/WebAudio/StereoPannerNode.h create mode 100644 Libraries/LibWeb/WebAudio/StereoPannerNode.idl create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index dc96a8f325e..247581966ff 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -805,6 +805,7 @@ set(SOURCES WebAudio/OscillatorNode.cpp WebAudio/PannerNode.cpp WebAudio/PeriodicWave.cpp + WebAudio/StereoPannerNode.cpp WebDriver/Actions.cpp WebDriver/Capabilities.cpp WebDriver/Client.cpp diff --git a/Libraries/LibWeb/WebAudio/StereoPannerNode.cpp b/Libraries/LibWeb/WebAudio/StereoPannerNode.cpp new file mode 100644 index 00000000000..9e43b5525bc --- /dev/null +++ b/Libraries/LibWeb/WebAudio/StereoPannerNode.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +GC_DEFINE_ALLOCATOR(StereoPannerNode); + +StereoPannerNode::~StereoPannerNode() = default; + +WebIDL::ExceptionOr> StereoPannerNode::create(JS::Realm& realm, GC::Ref context, StereoPannerOptions const& options) +{ + return construct_impl(realm, context, options); +} + +// https://webaudio.github.io/web-audio-api/#dom-stereopannernode-stereopannernode +WebIDL::ExceptionOr> StereoPannerNode::construct_impl(JS::Realm& realm, GC::Ref context, StereoPannerOptions const& options) +{ + // Create the node and allocate memory + auto node = realm.create(realm, context, options); + + // Default options for channel count and interpretation + // https://webaudio.github.io/web-audio-api/#stereopannernode + AudioNodeDefaultOptions default_options; + default_options.channel_count_mode = Bindings::ChannelCountMode::ClampedMax; + default_options.channel_interpretation = Bindings::ChannelInterpretation::Speakers; + default_options.channel_count = 2; + // FIXME: Set tail-time to no + + TRY(node->initialize_audio_node_options(options, default_options)); + return node; +} + +// https://webaudio.github.io/web-audio-api/#dom-audionode-channelcountmode +WebIDL::ExceptionOr StereoPannerNode::set_channel_count_mode(Bindings::ChannelCountMode mode) +{ + // https://webaudio.github.io/web-audio-api/#audionode-channelcountmode-constraints + // The channel count mode cannot be set to "max", and a NotSupportedError exception MUST be thrown for any attempt to set it to "max". + if (mode == Bindings::ChannelCountMode::Max) { + return WebIDL::NotSupportedError::create(realm(), "StereoPannerNode does not support 'max' as channelCountMode."_string); + } + + // If the mode is valid, call the base class implementation + return AudioNode::set_channel_count_mode(mode); +} + +// https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount +WebIDL::ExceptionOr StereoPannerNode::set_channel_count(WebIDL::UnsignedLong channel_count) +{ + // https://webaudio.github.io/web-audio-api/#audionode-channelcount-constraints + // The channel count cannot be greater than two, and a NotSupportedError exception MUST be thrown for any attempt to change it to a value greater than two. + if (channel_count > 2) { + return WebIDL::NotSupportedError::create(realm(), "StereoPannerNode does not support channel count greater than 2"_string); + } + + return AudioNode::set_channel_count(channel_count); +} + +StereoPannerNode::StereoPannerNode(JS::Realm& realm, GC::Ref context, StereoPannerOptions const& options) + : AudioNode(realm, context) + , m_pan(AudioParam::create(realm, context, options.pan, -1, 1, Bindings::AutomationRate::ARate)) +{ +} + +void StereoPannerNode::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(StereoPannerNode); +} + +void StereoPannerNode::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_pan); +} + +} diff --git a/Libraries/LibWeb/WebAudio/StereoPannerNode.h b/Libraries/LibWeb/WebAudio/StereoPannerNode.h new file mode 100644 index 00000000000..dae9ad1c7b3 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/StereoPannerNode.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Tim Ledbetter + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::WebAudio { + +// https://webaudio.github.io/web-audio-api/#StereoPannerOptions +struct StereoPannerOptions : AudioNodeOptions { + float pan { 0 }; +}; + +// https://webaudio.github.io/web-audio-api/#stereopannernode +class StereoPannerNode : public AudioNode { + WEB_PLATFORM_OBJECT(StereoPannerNode, AudioNode); + GC_DECLARE_ALLOCATOR(StereoPannerNode); + +public: + virtual ~StereoPannerNode() override; + + static WebIDL::ExceptionOr> create(JS::Realm&, GC::Ref, StereoPannerOptions const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref, StereoPannerOptions const& = {}); + + WebIDL::UnsignedLong number_of_inputs() override { return 1; } + WebIDL::UnsignedLong number_of_outputs() override { return 1; } + + WebIDL::ExceptionOr set_channel_count_mode(Bindings::ChannelCountMode) override; + WebIDL::ExceptionOr set_channel_count(WebIDL::UnsignedLong) override; + + GC::Ref pan() const { return m_pan; } + +protected: + StereoPannerNode(JS::Realm&, GC::Ref, StereoPannerOptions const& = {}); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + +private: + // https://webaudio.github.io/web-audio-api/#dom-stereopannernode-pan + GC::Ref m_pan; +}; + +} diff --git a/Libraries/LibWeb/WebAudio/StereoPannerNode.idl b/Libraries/LibWeb/WebAudio/StereoPannerNode.idl new file mode 100644 index 00000000000..8c1feaafe03 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/StereoPannerNode.idl @@ -0,0 +1,15 @@ +#import +#import +#import + +// https://webaudio.github.io/web-audio-api/#StereoPannerOptions +dictionary StereoPannerOptions : AudioNodeOptions { + float pan = 0; +}; + +// https://webaudio.github.io/web-audio-api/#stereopannernode +[Exposed=Window] +interface StereoPannerNode : AudioNode { + constructor (BaseAudioContext context, optional StereoPannerOptions options = {}); + readonly attribute AudioParam pan; +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 3faa51f3f95..0a36712793a 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -379,6 +379,7 @@ libweb_js_bindings(WebAudio/OfflineAudioContext) libweb_js_bindings(WebAudio/OscillatorNode) libweb_js_bindings(WebAudio/PannerNode) libweb_js_bindings(WebAudio/PeriodicWave) +libweb_js_bindings(WebAudio/StereoPannerNode) libweb_js_bindings(WebGL/ANGLEInstancedArrays) libweb_js_bindings(WebGL/WebGL2RenderingContext) libweb_js_bindings(WebGL/WebGLActiveInfo) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 3ec6a049a16..bf2ed7af277 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -362,6 +362,7 @@ SharedArrayBuffer SourceBuffer SourceBufferList StaticRange +StereoPannerNode Storage StorageEvent StorageManager diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.txt new file mode 100644 index 00000000000..2595da0e967 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.txt @@ -0,0 +1,56 @@ +Harness status: OK + +Found 51 tests + +51 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "initialize" +Pass Executing "invalid constructor" +Pass Executing "default constructor" +Pass Executing "test AudioNodeOptions" +Pass Executing "constructor with 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 StereoPannerNode() threw TypeError: "StereoPannerNode() needs one argument". +Pass new StereoPannerNode(1) threw TypeError: "Not an object of type BaseAudioContext". +Pass new StereoPannerNode(context, 42) threw TypeError: "Not an object of type StereoPannerOptions". +Pass < [invalid constructor] All assertions passed. (total 3 assertions) +Pass > [default constructor] +Pass node0 = new StereoPannerNode(context) did not throw an exception. +Pass node0 instanceof StereoPannerNode is equal to true. +Pass node0.numberOfInputs is equal to 1. +Pass node0.numberOfOutputs is equal to 1. +Pass node0.channelCount is equal to 2. +Pass node0.channelCountMode is equal to clamped-max. +Pass node0.channelInterpretation is equal to speakers. +Pass node0.pan.value is equal to 0. +Pass < [default constructor] All assertions passed. (total 8 assertions) +Pass > [test AudioNodeOptions] +Pass new StereoPannerNode(c, {"channelCount":1}) did not throw an exception. +Pass node.channelCount is equal to 1. +Pass new StereoPannerNode(c, {"channelCount":2}) did not throw an exception. +Pass node.channelCount is equal to 2. +Pass new StereoPannerNode(c, {"channelCount":0}) threw NotSupportedError: "Invalid channel count". +Pass new StereoPannerNode(c, {"channelCount":3}) threw NotSupportedError: "StereoPannerNode does not support channel count greater than 2". +Pass new StereoPannerNode(c, {"channelCount":99}) threw NotSupportedError: "StereoPannerNode does not support channel count greater than 2". +Pass new StereoPannerNode(c, {"channelCountMode":"clamped-max"}) did not throw an exception. +Pass node.channelCountMode is equal to clamped-max. +Pass new StereoPannerNode(c, {"channelCountMode":"explicit"}) did not throw an exception. +Pass node.channelCountMode is equal to explicit. +Pass new StereoPannerNode(c, {"channelCountMode":"max"}) threw NotSupportedError: "StereoPannerNode does not support 'max' as channelCountMode.". +Pass new StereoPannerNode(c, {"channelCountMode":"foobar"}) threw TypeError: "Invalid value 'foobar' for enumeration type 'ChannelCountMode'". +Pass new StereoPannerNode(c, {"channelInterpretation":"speakers"}) did not throw an exception. +Pass node.channelInterpretation is equal to speakers. +Pass new StereoPannerNode(c, {"channelInterpretation":"discrete"}) did not throw an exception. +Pass node.channelInterpretation is equal to discrete. +Pass new StereoPannerNode(c, {"channelInterpretation":"foobar"}) threw TypeError: "Invalid value 'foobar' for enumeration type 'ChannelInterpretation'". +Pass < [test AudioNodeOptions] All assertions passed. (total 18 assertions) +Pass > [constructor with options] +Pass node1 = new StereoPannerNode(, {"pan":0.75}) did not throw an exception. +Pass node1 instanceof StereoPannerNode is equal to true. +Pass node1.pan.value is equal to 0.75. +Pass < [constructor with options] All assertions passed. (total 3 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-stereopanner-interface/ctor-stereopanner.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html new file mode 100644 index 00000000000..02d8e522963 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html @@ -0,0 +1,131 @@ + + + + + Test Constructor: StereoPanner + + + + + + + + + + +