From c87f80454becfdb7c7ec141c9a20a2bf8119be4c Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sun, 19 Jan 2025 03:47:18 +0000 Subject: [PATCH] LibWeb/WebAudio: Implement automation rate constraints Some nodes have parameters whose automation rate is not allowed to be changed. This change enforces that constraint for all parameters it applies to. --- .../LibWeb/WebAudio/AudioBufferSourceNode.cpp | 4 +- Libraries/LibWeb/WebAudio/AudioParam.cpp | 11 +- Libraries/LibWeb/WebAudio/AudioParam.h | 10 +- .../WebAudio/DynamicsCompressorNode.cpp | 10 +- .../automation-rate.txt | 127 +++++++++++++ .../automation-rate.html | 167 ++++++++++++++++++ 6 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html diff --git a/Libraries/LibWeb/WebAudio/AudioBufferSourceNode.cpp b/Libraries/LibWeb/WebAudio/AudioBufferSourceNode.cpp index db1bca0851a..418ca326e1e 100644 --- a/Libraries/LibWeb/WebAudio/AudioBufferSourceNode.cpp +++ b/Libraries/LibWeb/WebAudio/AudioBufferSourceNode.cpp @@ -18,8 +18,8 @@ GC_DEFINE_ALLOCATOR(AudioBufferSourceNode); AudioBufferSourceNode::AudioBufferSourceNode(JS::Realm& realm, GC::Ref context, AudioBufferSourceOptions const& options) : AudioScheduledSourceNode(realm, context) , m_buffer(options.buffer) - , m_playback_rate(AudioParam::create(realm, context, options.playback_rate, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::KRate)) - , m_detune(AudioParam::create(realm, context, options.detune, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::KRate)) + , m_playback_rate(AudioParam::create(realm, context, options.playback_rate, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) + , m_detune(AudioParam::create(realm, context, options.detune, NumericLimits::lowest(), NumericLimits::max(), Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) , m_loop(options.loop) , m_loop_start(options.loop_start) , m_loop_end(options.loop_end) diff --git a/Libraries/LibWeb/WebAudio/AudioParam.cpp b/Libraries/LibWeb/WebAudio/AudioParam.cpp index 60a18f1363c..4071c1e6e72 100644 --- a/Libraries/LibWeb/WebAudio/AudioParam.cpp +++ b/Libraries/LibWeb/WebAudio/AudioParam.cpp @@ -14,7 +14,7 @@ namespace Web::WebAudio { GC_DEFINE_ALLOCATOR(AudioParam); -AudioParam::AudioParam(JS::Realm& realm, GC::Ref context, float default_value, float min_value, float max_value, Bindings::AutomationRate automation_rate) +AudioParam::AudioParam(JS::Realm& realm, GC::Ref context, float default_value, float min_value, float max_value, Bindings::AutomationRate automation_rate, FixedAutomationRate fixed_automation_rate) : Bindings::PlatformObject(realm) , m_context(context) , m_current_value(default_value) @@ -22,12 +22,13 @@ AudioParam::AudioParam(JS::Realm& realm, GC::Ref context, floa , m_min_value(min_value) , m_max_value(max_value) , m_automation_rate(automation_rate) + , m_fixed_automation_rate(fixed_automation_rate) { } -GC::Ref AudioParam::create(JS::Realm& realm, GC::Ref context, float default_value, float min_value, float max_value, Bindings::AutomationRate automation_rate) +GC::Ref AudioParam::create(JS::Realm& realm, GC::Ref context, float default_value, float min_value, float max_value, Bindings::AutomationRate automation_rate, FixedAutomationRate fixed_automation_rate) { - return realm.create(realm, context, default_value, min_value, max_value, automation_rate); + return realm.create(realm, context, default_value, min_value, max_value, automation_rate, fixed_automation_rate); } AudioParam::~AudioParam() = default; @@ -56,7 +57,9 @@ Bindings::AutomationRate AudioParam::automation_rate() const // https://webaudio.github.io/web-audio-api/#dom-audioparam-automationrate WebIDL::ExceptionOr AudioParam::set_automation_rate(Bindings::AutomationRate automation_rate) { - dbgln("FIXME: Fully implement AudioParam::set_automation_rate"); + if (automation_rate != m_automation_rate && m_fixed_automation_rate == FixedAutomationRate::Yes) + return WebIDL::InvalidStateError::create(realm(), "Automation rate cannot be changed"_string); + m_automation_rate = automation_rate; return {}; } diff --git a/Libraries/LibWeb/WebAudio/AudioParam.h b/Libraries/LibWeb/WebAudio/AudioParam.h index 43ccbd9ded6..87b889a4b98 100644 --- a/Libraries/LibWeb/WebAudio/AudioParam.h +++ b/Libraries/LibWeb/WebAudio/AudioParam.h @@ -18,7 +18,11 @@ class AudioParam final : public Bindings::PlatformObject { GC_DECLARE_ALLOCATOR(AudioParam); public: - static GC::Ref create(JS::Realm&, GC::Ref, float default_value, float min_value, float max_value, Bindings::AutomationRate); + enum class FixedAutomationRate { + No, + Yes, + }; + static GC::Ref create(JS::Realm&, GC::Ref, float default_value, float min_value, float max_value, Bindings::AutomationRate, FixedAutomationRate = FixedAutomationRate::No); virtual ~AudioParam() override; @@ -43,7 +47,7 @@ public: WebIDL::ExceptionOr> cancel_and_hold_at_time(double cancel_time); private: - AudioParam(JS::Realm&, GC::Ref, float default_value, float min_value, float max_value, Bindings::AutomationRate); + AudioParam(JS::Realm&, GC::Ref, float default_value, float min_value, float max_value, Bindings::AutomationRate, FixedAutomationRate = FixedAutomationRate::No); GC::Ref m_context; @@ -57,6 +61,8 @@ private: Bindings::AutomationRate m_automation_rate {}; + FixedAutomationRate m_fixed_automation_rate { FixedAutomationRate::No }; + virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; }; diff --git a/Libraries/LibWeb/WebAudio/DynamicsCompressorNode.cpp b/Libraries/LibWeb/WebAudio/DynamicsCompressorNode.cpp index 957865c6000..acb25aee4d1 100644 --- a/Libraries/LibWeb/WebAudio/DynamicsCompressorNode.cpp +++ b/Libraries/LibWeb/WebAudio/DynamicsCompressorNode.cpp @@ -41,11 +41,11 @@ WebIDL::ExceptionOr> DynamicsCompressorNode::con DynamicsCompressorNode::DynamicsCompressorNode(JS::Realm& realm, GC::Ref context, DynamicsCompressorOptions const& options) : AudioNode(realm, context) - , m_threshold(AudioParam::create(realm, context, options.threshold, -100, 0, Bindings::AutomationRate::KRate)) - , m_knee(AudioParam::create(realm, context, options.knee, 0, 40, Bindings::AutomationRate::KRate)) - , m_ratio(AudioParam::create(realm, context, options.ratio, 1, 20, Bindings::AutomationRate::KRate)) - , m_attack(AudioParam::create(realm, context, options.attack, 0, 1, Bindings::AutomationRate::KRate)) - , m_release(AudioParam::create(realm, context, options.release, 0, 1, Bindings::AutomationRate::KRate)) + , m_threshold(AudioParam::create(realm, context, options.threshold, -100, 0, Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) + , m_knee(AudioParam::create(realm, context, options.knee, 0, 40, Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) + , m_ratio(AudioParam::create(realm, context, options.ratio, 1, 20, Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) + , m_attack(AudioParam::create(realm, context, options.attack, 0, 1, Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) + , m_release(AudioParam::create(realm, context, options.release, 0, 1, Bindings::AutomationRate::KRate, AudioParam::FixedAutomationRate::Yes)) { } diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.txt new file mode 100644 index 00000000000..75a01e165e6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.txt @@ -0,0 +1,127 @@ +Harness status: OK + +Found 122 tests + +122 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "AudioBufferSourceNode" +Pass Executing "BiquadFilterNode" +Pass Executing "ConstantSourceNode" +Pass Executing "DelayNode" +Pass Executing "DynamicsCompressorNode" +Pass Executing "GainNode" +Pass Executing "OscillatorNode" +Pass Executing "PannerNode" +Pass Executing "StereoPannerNode" +Pass Executing "AudioListener" +Pass Audit report +Pass > [AudioBufferSourceNode] +Pass Default AudioBufferSourceNode.detune.automationRate is equal to k-rate. +Pass Set AudioBufferSourceNode.detune.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass Default AudioBufferSourceNode.playbackRate.automationRate is equal to k-rate. +Pass Set AudioBufferSourceNode.playbackRate.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass < [AudioBufferSourceNode] All assertions passed. (total 4 assertions) +Pass > [BiquadFilterNode] +Pass Default BiquadFilterNode.frequency.automationRate is equal to a-rate. +Pass Set BiquadFilterNode.frequency.automationRate to "k-rate" did not throw an exception. +Pass BiquadFilterNode.frequency.automationRate is equal to k-rate. +Pass Default BiquadFilterNode.detune.automationRate is equal to a-rate. +Pass Set BiquadFilterNode.detune.automationRate to "k-rate" did not throw an exception. +Pass BiquadFilterNode.detune.automationRate is equal to k-rate. +Pass Default BiquadFilterNode.Q.automationRate is equal to a-rate. +Pass Set BiquadFilterNode.Q.automationRate to "k-rate" did not throw an exception. +Pass BiquadFilterNode.Q.automationRate is equal to k-rate. +Pass Default BiquadFilterNode.gain.automationRate is equal to a-rate. +Pass Set BiquadFilterNode.gain.automationRate to "k-rate" did not throw an exception. +Pass BiquadFilterNode.gain.automationRate is equal to k-rate. +Pass < [BiquadFilterNode] All assertions passed. (total 12 assertions) +Pass > [ConstantSourceNode] +Pass Default ConstantSourceNode.offset.automationRate is equal to a-rate. +Pass Set ConstantSourceNode.offset.automationRate to "k-rate" did not throw an exception. +Pass ConstantSourceNode.offset.automationRate is equal to k-rate. +Pass < [ConstantSourceNode] All assertions passed. (total 3 assertions) +Pass > [DelayNode] +Pass Default DelayNode.delayTime.automationRate is equal to a-rate. +Pass Set DelayNode.delayTime.automationRate to "k-rate" did not throw an exception. +Pass DelayNode.delayTime.automationRate is equal to k-rate. +Pass < [DelayNode] All assertions passed. (total 3 assertions) +Pass > [DynamicsCompressorNode] +Pass Default DynamicsCompressorNode.threshold.automationRate is equal to k-rate. +Pass Set DynamicsCompressorNode.threshold.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass Default DynamicsCompressorNode.knee.automationRate is equal to k-rate. +Pass Set DynamicsCompressorNode.knee.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass Default DynamicsCompressorNode.ratio.automationRate is equal to k-rate. +Pass Set DynamicsCompressorNode.ratio.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass Default DynamicsCompressorNode.attack.automationRate is equal to k-rate. +Pass Set DynamicsCompressorNode.attack.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass Default DynamicsCompressorNode.release.automationRate is equal to k-rate. +Pass Set DynamicsCompressorNode.release.automationRate to "a-rate" threw InvalidStateError: "Automation rate cannot be changed". +Pass < [DynamicsCompressorNode] All assertions passed. (total 10 assertions) +Pass > [GainNode] +Pass Default GainNode.gain.automationRate is equal to a-rate. +Pass Set GainNode.gain.automationRate to "k-rate" did not throw an exception. +Pass GainNode.gain.automationRate is equal to k-rate. +Pass < [GainNode] All assertions passed. (total 3 assertions) +Pass > [OscillatorNode] +Pass Default OscillatorNode.frequency.automationRate is equal to a-rate. +Pass Set OscillatorNode.frequency.automationRate to "k-rate" did not throw an exception. +Pass OscillatorNode.frequency.automationRate is equal to k-rate. +Pass Default OscillatorNode.detune.automationRate is equal to a-rate. +Pass Set OscillatorNode.detune.automationRate to "k-rate" did not throw an exception. +Pass OscillatorNode.detune.automationRate is equal to k-rate. +Pass < [OscillatorNode] All assertions passed. (total 6 assertions) +Pass > [PannerNode] +Pass Default PannerNode.positionX.automationRate is equal to a-rate. +Pass Set PannerNode.positionX.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.positionX.automationRate is equal to k-rate. +Pass Default PannerNode.positionY.automationRate is equal to a-rate. +Pass Set PannerNode.positionY.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.positionY.automationRate is equal to k-rate. +Pass Default PannerNode.positionZ.automationRate is equal to a-rate. +Pass Set PannerNode.positionZ.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.positionZ.automationRate is equal to k-rate. +Pass Default PannerNode.orientationX.automationRate is equal to a-rate. +Pass Set PannerNode.orientationX.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.orientationX.automationRate is equal to k-rate. +Pass Default PannerNode.orientationY.automationRate is equal to a-rate. +Pass Set PannerNode.orientationY.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.orientationY.automationRate is equal to k-rate. +Pass Default PannerNode.orientationZ.automationRate is equal to a-rate. +Pass Set PannerNode.orientationZ.automationRate to "k-rate" did not throw an exception. +Pass PannerNode.orientationZ.automationRate is equal to k-rate. +Pass < [PannerNode] All assertions passed. (total 18 assertions) +Pass > [StereoPannerNode] +Pass Default StereoPannerNode.pan.automationRate is equal to a-rate. +Pass Set StereoPannerNode.pan.automationRate to "k-rate" did not throw an exception. +Pass StereoPannerNode.pan.automationRate is equal to k-rate. +Pass < [StereoPannerNode] All assertions passed. (total 3 assertions) +Pass > [AudioListener] +Pass Default AudioListener.positionX.automationRate is equal to a-rate. +Pass Set AudioListener.positionX.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.positionX.automationRate is equal to k-rate. +Pass Default AudioListener.positionY.automationRate is equal to a-rate. +Pass Set AudioListener.positionY.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.positionY.automationRate is equal to k-rate. +Pass Default AudioListener.positionZ.automationRate is equal to a-rate. +Pass Set AudioListener.positionZ.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.positionZ.automationRate is equal to k-rate. +Pass Default AudioListener.forwardX.automationRate is equal to a-rate. +Pass Set AudioListener.forwardX.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.forwardX.automationRate is equal to k-rate. +Pass Default AudioListener.forwardY.automationRate is equal to a-rate. +Pass Set AudioListener.forwardY.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.forwardY.automationRate is equal to k-rate. +Pass Default AudioListener.forwardZ.automationRate is equal to a-rate. +Pass Set AudioListener.forwardZ.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.forwardZ.automationRate is equal to k-rate. +Pass Default AudioListener.upX.automationRate is equal to a-rate. +Pass Set AudioListener.upX.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.upX.automationRate is equal to k-rate. +Pass Default AudioListener.upY.automationRate is equal to a-rate. +Pass Set AudioListener.upY.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.upY.automationRate is equal to k-rate. +Pass Default AudioListener.upZ.automationRate is equal to a-rate. +Pass Set AudioListener.upZ.automationRate to "k-rate" did not throw an exception. +Pass AudioListener.upZ.automationRate is equal to k-rate. +Pass < [AudioListener] All assertions passed. (total 27 assertions) +Pass # AUDIT TASK RUNNER FINISHED: 10 tasks ran successfully. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html new file mode 100644 index 00000000000..557c46f6365 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-audioparam-interface/automation-rate.html @@ -0,0 +1,167 @@ + + + + AudioParam.automationRate tests + + + + + + + + + +