forked from LadybirdBrowser/ladybird
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LibWeb: Add
StereoPannerNode
interface
- Loading branch information
Showing
8 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* Copyright (c) 2025, Tim Ledbetter <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibWeb/Bindings/Intrinsics.h> | ||
#include <LibWeb/WebAudio/AudioNode.h> | ||
#include <LibWeb/WebAudio/AudioParam.h> | ||
#include <LibWeb/WebAudio/BaseAudioContext.h> | ||
#include <LibWeb/WebAudio/StereoPannerNode.h> | ||
|
||
namespace Web::WebAudio { | ||
|
||
GC_DEFINE_ALLOCATOR(StereoPannerNode); | ||
|
||
StereoPannerNode::~StereoPannerNode() = default; | ||
|
||
WebIDL::ExceptionOr<GC::Ref<StereoPannerNode>> StereoPannerNode::create(JS::Realm& realm, GC::Ref<BaseAudioContext> context, StereoPannerOptions const& options) | ||
{ | ||
return construct_impl(realm, context, options); | ||
} | ||
|
||
// https://webaudio.github.io/web-audio-api/#dom-stereopannernode-stereopannernode | ||
WebIDL::ExceptionOr<GC::Ref<StereoPannerNode>> StereoPannerNode::construct_impl(JS::Realm& realm, GC::Ref<BaseAudioContext> context, StereoPannerOptions const& options) | ||
{ | ||
// Create the node and allocate memory | ||
auto node = realm.create<StereoPannerNode>(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<void> 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<void> 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<BaseAudioContext> 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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright (c) 2025, Tim Ledbetter <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <LibWeb/Bindings/StereoPannerNodePrototype.h> | ||
#include <LibWeb/WebAudio/AudioNode.h> | ||
|
||
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<GC::Ref<StereoPannerNode>> create(JS::Realm&, GC::Ref<BaseAudioContext>, StereoPannerOptions const& = {}); | ||
static WebIDL::ExceptionOr<GC::Ref<StereoPannerNode>> construct_impl(JS::Realm&, GC::Ref<BaseAudioContext>, StereoPannerOptions const& = {}); | ||
|
||
WebIDL::UnsignedLong number_of_inputs() override { return 1; } | ||
WebIDL::UnsignedLong number_of_outputs() override { return 1; } | ||
|
||
WebIDL::ExceptionOr<void> set_channel_count_mode(Bindings::ChannelCountMode) override; | ||
WebIDL::ExceptionOr<void> set_channel_count(WebIDL::UnsignedLong) override; | ||
|
||
GC::Ref<AudioParam const> pan() const { return m_pan; } | ||
|
||
protected: | ||
StereoPannerNode(JS::Realm&, GC::Ref<BaseAudioContext>, 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<AudioParam> m_pan; | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#import <WebAudio/AudioNode.idl> | ||
#import <WebAudio/AudioParam.idl> | ||
#import <WebAudio/BaseAudioContext.idl> | ||
|
||
// 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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
...pected/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
131 changes: 131 additions & 0 deletions
131
...input/wpt-import/webaudio/the-audio-api/the-stereopanner-interface/ctor-stereopanner.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title> | ||
Test Constructor: StereoPanner | ||
</title> | ||
<script src="../../../resources/testharness.js"></script> | ||
<script src="../../../resources/testharnessreport.js"></script> | ||
<script src="../../../webaudio/resources/audit-util.js"></script> | ||
<script src="../../../webaudio/resources/audit.js"></script> | ||
<script src="../../../webaudio/resources/audionodeoptions.js"></script> | ||
</head> | ||
<body> | ||
<script id="layout-test-code"> | ||
let context; | ||
|
||
let audit = Audit.createTaskRunner(); | ||
|
||
audit.define('initialize', (task, should) => { | ||
context = initializeContext(should); | ||
task.done(); | ||
}); | ||
|
||
audit.define('invalid constructor', (task, should) => { | ||
testInvalidConstructor(should, 'StereoPannerNode', context); | ||
task.done(); | ||
}); | ||
|
||
audit.define('default constructor', (task, should) => { | ||
let prefix = 'node0'; | ||
let node = testDefaultConstructor(should, 'StereoPannerNode', context, { | ||
prefix: prefix, | ||
numberOfInputs: 1, | ||
numberOfOutputs: 1, | ||
channelCount: 2, | ||
channelCountMode: 'clamped-max', | ||
channelInterpretation: 'speakers' | ||
}); | ||
|
||
testDefaultAttributes(should, node, prefix, [{name: 'pan', value: 0}]); | ||
|
||
task.done(); | ||
}); | ||
|
||
audit.define('test AudioNodeOptions', (task, should) => { | ||
// Can't use testAudioNodeOptions because the constraints for this node | ||
// are not supported there. | ||
let node; | ||
|
||
// An array of tests. | ||
[{ | ||
// Test that we can set the channel count to 1 or 2 and that other | ||
// channel counts throw an error. | ||
attribute: 'channelCount', | ||
tests: [ | ||
{value: 1}, {value: 2}, {value: 0, error: 'NotSupportedError'}, | ||
{value: 3, error: 'NotSupportedError'}, | ||
{value: 99, error: 'NotSupportedError'} | ||
] | ||
}, | ||
{ | ||
// Test channelCountMode. A mode of "max" is illegal, but others are | ||
// ok. But also throw an error of unknown values. | ||
attribute: 'channelCountMode', | ||
tests: [ | ||
{value: 'clamped-max'}, {value: 'explicit'}, | ||
{value: 'max', error: 'NotSupportedError'}, | ||
{value: 'foobar', error: TypeError} | ||
] | ||
}, | ||
{ | ||
// Test channelInterpretation can be set for valid values and an | ||
// error is thrown for others. | ||
attribute: 'channelInterpretation', | ||
tests: [ | ||
{value: 'speakers'}, {value: 'discrete'}, | ||
{value: 'foobar', error: TypeError} | ||
] | ||
}].forEach(entry => { | ||
entry.tests.forEach(testItem => { | ||
let options = {}; | ||
options[entry.attribute] = testItem.value; | ||
|
||
const testFunction = () => { | ||
node = new StereoPannerNode(context, options); | ||
}; | ||
const testDescription = | ||
`new StereoPannerNode(c, ${JSON.stringify(options)})`; | ||
|
||
if (testItem.error) { | ||
testItem.error === TypeError | ||
? should(testFunction, testDescription).throw(TypeError) | ||
: should(testFunction, testDescription) | ||
.throw(DOMException, 'NotSupportedError'); | ||
} else { | ||
should(testFunction, testDescription).notThrow(); | ||
should(node[entry.attribute], `node.${entry.attribute}`) | ||
.beEqualTo(options[entry.attribute]); | ||
} | ||
}); | ||
}); | ||
|
||
task.done(); | ||
}); | ||
|
||
audit.define('constructor with options', (task, should) => { | ||
let node; | ||
let options = { | ||
pan: 0.75, | ||
}; | ||
|
||
should( | ||
() => { | ||
node = new StereoPannerNode(context, options); | ||
}, | ||
'node1 = new StereoPannerNode(, ' + JSON.stringify(options) + ')') | ||
.notThrow(); | ||
should( | ||
node instanceof StereoPannerNode, | ||
'node1 instanceof StereoPannerNode') | ||
.beEqualTo(true); | ||
|
||
should(node.pan.value, 'node1.pan.value').beEqualTo(options.pan); | ||
|
||
task.done(); | ||
}); | ||
|
||
audit.run(); | ||
</script> | ||
</body> | ||
</html> |