diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 598d6457a0f..e1ae31aa534 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -787,6 +787,7 @@ set(SOURCES WebAudio/GainNode.cpp WebAudio/OfflineAudioContext.cpp WebAudio/OscillatorNode.cpp + WebAudio/PannerNode.cpp WebAudio/PeriodicWave.cpp WebDriver/Actions.cpp WebDriver/Capabilities.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 98f27d2e5a8..1dbc892ad51 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -822,6 +822,7 @@ class DynamicsCompressorNode; class GainNode; class OfflineAudioContext; class OscillatorNode; +class PannerNode; class PeriodicWave; enum class AudioContextState; diff --git a/Libraries/LibWeb/WebAudio/PannerNode.cpp b/Libraries/LibWeb/WebAudio/PannerNode.cpp new file mode 100644 index 00000000000..4caec4852a1 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +GC_DEFINE_ALLOCATOR(PannerNode); + +PannerNode::~PannerNode() = default; + +WebIDL::ExceptionOr> PannerNode::create(JS::Realm& realm, GC::Ref context, PannerOptions const& options) +{ + return construct_impl(realm, context, options); +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-pannernode +WebIDL::ExceptionOr> PannerNode::construct_impl(JS::Realm& realm, GC::Ref context, PannerOptions const& options) +{ + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + // A RangeError exception MUST be thrown if this is set to a negative value. + if (options.ref_distance < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "refDistance cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + // A RangeError exception MUST be thrown if this is set to a negative value. + if (options.rolloff_factor < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "rolloffFactor cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + // A RangeError exception MUST be thrown if this is set to a non-positive value. + if (options.max_distance < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "maxDistance cannot be negative"sv }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + // It is a linear value (not dB) in the range [0, 1]. An InvalidStateError MUST be thrown if the parameter is outside this range. + if (options.cone_outer_gain < 0.0 || options.cone_outer_gain > 1.0) + return WebIDL::InvalidStateError::create(realm, "coneOuterGain must be in the range of [0, 1]"_string); + + // 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/#PannerNode + 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 maybe + + TRY(node->initialize_audio_node_options(options, default_options)); + return node; +} + +PannerNode::PannerNode(JS::Realm& realm, GC::Ref context, PannerOptions const& options) + : AudioNode(realm, context) + , m_position_x(AudioParam::create(realm, options.position_x, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_position_y(AudioParam::create(realm, options.position_y, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_position_z(AudioParam::create(realm, options.position_z, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_x(AudioParam::create(realm, options.orientation_x, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_y(AudioParam::create(realm, options.orientation_y, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_orientation_z(AudioParam::create(realm, options.orientation_z, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::ARate)) + , m_ref_distance(options.ref_distance) + , m_max_distance(options.max_distance) + , m_rolloff_factor(options.rolloff_factor) + , m_cone_inner_angle(options.cone_inner_angle) + , m_cone_outer_angle(options.cone_outer_angle) + , m_cone_outer_gain(options.cone_outer_gain) +{ +} + +void PannerNode::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(PannerNode); +} + +void PannerNode::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_position_x); + visitor.visit(m_position_y); + visitor.visit(m_position_z); + visitor.visit(m_orientation_x); + visitor.visit(m_orientation_y); + visitor.visit(m_orientation_z); +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance +WebIDL::ExceptionOr PannerNode::set_ref_distance(double value) +{ + // A RangeError exception MUST be thrown if this is set to a negative value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "refDistance cannot be negative"sv }; + + m_ref_distance = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance +WebIDL::ExceptionOr PannerNode::set_max_distance(double value) +{ + // A RangeError exception MUST be thrown if this is set to a non-positive value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "maxDistance cannot be negative"sv }; + + m_max_distance = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor +WebIDL::ExceptionOr PannerNode::set_rolloff_factor(double value) +{ + // A RangeError exception MUST be thrown if this is set to a negative value. + if (value < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "rolloffFactor cannot be negative"sv }; + + m_rolloff_factor = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain +WebIDL::ExceptionOr PannerNode::set_cone_outer_gain(double value) +{ + // It is a linear value (not dB) in the range [0, 1]. An InvalidStateError MUST be thrown if the parameter is outside this range. + if (value < 0.0 || value > 1.0) + return WebIDL::InvalidStateError::create(realm(), "coneOuterGain must be in the range of [0, 1]"_string); + + m_cone_outer_gain = value; + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-setposition +WebIDL::ExceptionOr PannerNode::set_position(float x, float y, float z) +{ + // This method is DEPRECATED. It is equivalent to setting positionX.value, positionY.value, and positionZ.value + // attribute directly with the x, y and z parameters, respectively. + // FIXME: Consequently, if any of the positionX, positionY, and positionZ AudioParams have an automation curve + // set using setValueCurveAtTime() at the time this method is called, a NotSupportedError MUST be thrown. + m_position_x->set_value(x); + m_position_y->set_value(y); + m_position_z->set_value(z); + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-pannernode-setorientation +WebIDL::ExceptionOr PannerNode::set_orientation(float x, float y, float z) +{ + // This method is DEPRECATED. It is equivalent to setting orientationX.value, orientationY.value, and + // orientationZ.value attribute directly, with the x, y and z parameters, respectively. + // FIXME: Consequently, if any of the orientationX, orientationY, and orientationZ AudioParams have an automation + // curve set using setValueCurveAtTime() at the time this method is called, a NotSupportedError MUST be thrown. + m_orientation_x->set_value(x); + m_orientation_y->set_value(y); + m_orientation_z->set_value(z); + return {}; +} + +} diff --git a/Libraries/LibWeb/WebAudio/PannerNode.h b/Libraries/LibWeb/WebAudio/PannerNode.h new file mode 100644 index 00000000000..4be601d6168 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::WebAudio { + +// https://webaudio.github.io/web-audio-api/#PannerOptions +struct PannerOptions : AudioNodeOptions { + Bindings::PanningModelType panning_model { Bindings::PanningModelType::Equalpower }; + Bindings::DistanceModelType distance_model { Bindings::DistanceModelType::Inverse }; + float position_x { 0.0f }; + float position_y { 0.0f }; + float position_z { 0.0f }; + float orientation_x { 1.0f }; + float orientation_y { 0.0f }; + float orientation_z { 0.0f }; + double ref_distance { 1.0 }; + double max_distance { 10000.0 }; + double rolloff_factor { 1.0 }; + double cone_inner_angle { 360.0 }; + double cone_outer_angle { 360.0 }; + double cone_outer_gain { 0.0 }; +}; + +// https://webaudio.github.io/web-audio-api/#PannerNode +class PannerNode final : public AudioNode { + WEB_PLATFORM_OBJECT(PannerNode, AudioNode); + GC_DECLARE_ALLOCATOR(PannerNode); + +public: + virtual ~PannerNode() override; + + static WebIDL::ExceptionOr> create(JS::Realm&, GC::Ref, PannerOptions const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref, PannerOptions const& = {}); + + WebIDL::UnsignedLong number_of_inputs() override { return 1; } + WebIDL::UnsignedLong number_of_outputs() override { return 1; } + + GC::Ref position_x() const { return m_position_x; } + GC::Ref position_y() const { return m_position_y; } + GC::Ref position_z() const { return m_position_z; } + GC::Ref orientation_x() const { return m_orientation_x; } + GC::Ref orientation_y() const { return m_orientation_y; } + GC::Ref orientation_z() const { return m_orientation_z; } + + Bindings::PanningModelType panning_model() const { return m_panning_model; } + void set_panning_model(Bindings::PanningModelType value) { m_panning_model = value; } + + Bindings::DistanceModelType distance_model() const { return m_distance_model; } + void set_distance_model(Bindings::DistanceModelType value) { m_distance_model = value; } + + double ref_distance() const { return m_ref_distance; } + WebIDL::ExceptionOr set_ref_distance(double); + + double max_distance() const { return m_max_distance; } + WebIDL::ExceptionOr set_max_distance(double); + + double rolloff_factor() const { return m_rolloff_factor; } + WebIDL::ExceptionOr set_rolloff_factor(double); + + double cone_inner_angle() const { return m_cone_inner_angle; } + void set_cone_inner_angle(double value) { m_cone_inner_angle = value; } + + double cone_outer_angle() const { return m_cone_outer_angle; } + void set_cone_outer_angle(double value) { m_cone_outer_angle = value; } + + double cone_outer_gain() const { return m_cone_outer_gain; } + WebIDL::ExceptionOr set_cone_outer_gain(double); + + WebIDL::ExceptionOr set_position(float x, float y, float z); + WebIDL::ExceptionOr set_orientation(float x, float y, float z); + +protected: + PannerNode(JS::Realm&, GC::Ref, PannerOptions const& = {}); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + +private: + // https://webaudio.github.io/web-audio-api/#dom-pannernode-panningmodel + Bindings::PanningModelType m_panning_model { Bindings::PanningModelType::Equalpower }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionx + GC::Ref m_position_x; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positiony + GC::Ref m_position_y; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-positionz + GC::Ref m_position_z; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationx + GC::Ref m_orientation_x; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationy + GC::Ref m_orientation_y; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-orientationz + GC::Ref m_orientation_z; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-distancemodel + Bindings::DistanceModelType m_distance_model { Bindings::DistanceModelType::Inverse }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-refdistance + double m_ref_distance { 1.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-maxdistance + double m_max_distance { 10000.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-rollofffactor + double m_rolloff_factor { 1.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneinnerangle + double m_cone_inner_angle { 360.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneouterangle + double m_cone_outer_angle { 360.0 }; + + // https://webaudio.github.io/web-audio-api/#dom-pannernode-coneoutergain + double m_cone_outer_gain { 0.0 }; +}; + +} diff --git a/Libraries/LibWeb/WebAudio/PannerNode.idl b/Libraries/LibWeb/WebAudio/PannerNode.idl new file mode 100644 index 00000000000..39213a1ec57 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/PannerNode.idl @@ -0,0 +1,56 @@ +#import +#import +#import + +// https://webaudio.github.io/web-audio-api/#enumdef-panningmodeltype +enum PanningModelType { + "equalpower", + "HRTF" +}; + +// https://webaudio.github.io/web-audio-api/#enumdef-distancemodeltype +enum DistanceModelType { + "linear", + "inverse", + "exponential" +}; + +// https://webaudio.github.io/web-audio-api/#PannerOptions +dictionary PannerOptions : AudioNodeOptions { + PanningModelType panningModel = "equalpower"; + DistanceModelType distanceModel = "inverse"; + float positionX = 0; + float positionY = 0; + float positionZ = 0; + float orientationX = 1; + float orientationY = 0; + float orientationZ = 0; + double refDistance = 1; + double maxDistance = 10000; + double rolloffFactor = 1; + double coneInnerAngle = 360; + double coneOuterAngle = 360; + double coneOuterGain = 0; +}; + +// https://webaudio.github.io/web-audio-api/#PannerNode +[Exposed=Window] +interface PannerNode : AudioNode { + constructor(BaseAudioContext context, optional PannerOptions options = {}); + attribute PanningModelType panningModel; + readonly attribute AudioParam positionX; + readonly attribute AudioParam positionY; + readonly attribute AudioParam positionZ; + readonly attribute AudioParam orientationX; + readonly attribute AudioParam orientationY; + readonly attribute AudioParam orientationZ; + attribute DistanceModelType distanceModel; + attribute double refDistance; + attribute double maxDistance; + attribute double rolloffFactor; + attribute double coneInnerAngle; + attribute double coneOuterAngle; + attribute double coneOuterGain; + undefined setPosition(float x, float y, float z); + undefined setOrientation(float x, float y, float z); +}; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index b85359fbd6d..83d0aad42ea 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -368,6 +368,7 @@ libweb_js_bindings(WebAudio/GainNode) libweb_js_bindings(WebAudio/ChannelMergerNode) libweb_js_bindings(WebAudio/OfflineAudioContext) libweb_js_bindings(WebAudio/OscillatorNode) +libweb_js_bindings(WebAudio/PannerNode) libweb_js_bindings(WebAudio/PeriodicWave) 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 4eab6f474f7..52d5ae7ddfb 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -261,6 +261,7 @@ OfflineAudioContext Option OscillatorNode PageTransitionEvent +PannerNode Path2D Performance PerformanceEntry