mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb/WebAudio: Implement AudioNode::disconnect()
Destubs AudioNode::disconnect() and its related overloads. Ensures that inverse connections are properly removed when disconnecting AudioNodeConnections. Adds associated WPT but skips them for now because they rely on OfflineRenderContext::start_rendering to be fully implemented.
This commit is contained in:
parent
75d26b1610
commit
3aff12bbab
Notes:
github-actions[bot]
2025-07-09 23:53:15 +00:00
Author: https://github.com/Prospero23
Commit: 3aff12bbab
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5144
Reviewed-by: https://github.com/ADKaster ✅
Reviewed-by: https://github.com/gmta
7 changed files with 630 additions and 16 deletions
|
@ -119,7 +119,16 @@ WebIDL::ExceptionOr<void> AudioNode::connect(GC::Ref<AudioParam> destination_par
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect
|
||||||
void AudioNode::disconnect()
|
void AudioNode::disconnect()
|
||||||
{
|
{
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect()");
|
while (!m_output_connections.is_empty()) {
|
||||||
|
auto connection = m_output_connections.take_last();
|
||||||
|
auto destination = connection.destination_node;
|
||||||
|
|
||||||
|
destination->m_input_connections.remove_all_matching([&](AudioNodeConnection& input_connection) {
|
||||||
|
return input_connection.destination_node.ptr() == this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_param_connections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-output
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-output
|
||||||
|
@ -132,35 +141,81 @@ WebIDL::ExceptionOr<void> AudioNode::disconnect(WebIDL::UnsignedLong output)
|
||||||
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(output)");
|
m_output_connections.remove_all_matching([&](AudioNodeConnection& connection) {
|
||||||
|
if (connection.output != output)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connection.destination_node->m_input_connections.remove_all_matching([&](AudioNodeConnection& reverse_connection) {
|
||||||
|
return reverse_connection.destination_node.ptr() == this && reverse_connection.output == output;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_param_connections.remove_all_matching([&](AudioParamConnection& connection) {
|
||||||
|
return connection.output == output;
|
||||||
|
});
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode
|
||||||
void AudioNode::disconnect(GC::Ref<AudioNode> destination_node)
|
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_node)
|
||||||
{
|
{
|
||||||
(void)destination_node;
|
// The destinationNode parameter is the AudioNode to disconnect.
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(destination_node)");
|
// It disconnects all outgoing connections to the given destinationNode.
|
||||||
|
auto before = m_output_connections.size();
|
||||||
|
m_output_connections.remove_all_matching([&](AudioNodeConnection& connection) {
|
||||||
|
if (connection.destination_node != destination_node)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connection.destination_node->m_input_connections.remove_all_matching([&](AudioNodeConnection& reverse_connection) {
|
||||||
|
return reverse_connection.destination_node.ptr() == this;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
// If there is no connection to the destinationNode, an InvalidAccessError exception MUST be thrown.
|
||||||
|
if (m_output_connections.size() == before) {
|
||||||
|
return WebIDL::InvalidAccessError::create(realm(), MUST(String::formatted("No connection to given AudioNode")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output
|
||||||
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output)
|
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output)
|
||||||
{
|
{
|
||||||
(void)destination_node;
|
|
||||||
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
||||||
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
||||||
if (output >= number_of_outputs()) {
|
if (output >= number_of_outputs()) {
|
||||||
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(destination_node, output)");
|
// The destinationNode parameter is the AudioNode to disconnect.
|
||||||
|
auto before = m_output_connections.size();
|
||||||
|
m_output_connections.remove_all_matching([&](AudioNodeConnection& connection) {
|
||||||
|
if (connection.destination_node != destination_node || connection.output != output)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connection.destination_node->m_input_connections.remove_all_matching([&](AudioNodeConnection& reverse_connection) {
|
||||||
|
return reverse_connection.destination_node.ptr() == this && reverse_connection.output == output;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there is no connection to the destinationNode from the given output, an InvalidAccessError exception MUST be thrown.
|
||||||
|
if (m_output_connections.size() == before) {
|
||||||
|
return WebIDL::InvalidAccessError::create(realm(), MUST(String::formatted("No connection from output {} to given AudioNode", output)));
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output-input
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output-input
|
||||||
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input)
|
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input)
|
||||||
{
|
{
|
||||||
(void)destination_node;
|
|
||||||
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
||||||
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
||||||
if (output >= number_of_outputs()) {
|
if (output >= number_of_outputs()) {
|
||||||
|
@ -173,28 +228,63 @@ WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioNode> destination_n
|
||||||
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Input index '{}' exceeds number of inputs", input)));
|
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Input index '{}' exceeds number of inputs", input)));
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(destination_node, output, input)");
|
// The destinationNode parameter is the AudioNode to disconnect.
|
||||||
|
auto before = m_output_connections.size();
|
||||||
|
m_output_connections.remove_all_matching([&](AudioNodeConnection& connection) {
|
||||||
|
if (connection.destination_node != destination_node || connection.output != output || connection.input != input)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connection.destination_node->m_input_connections.remove_all_matching([&](AudioNodeConnection& reverse_connection) {
|
||||||
|
return reverse_connection.destination_node.ptr() == this && reverse_connection.output == output && reverse_connection.input == input;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there is no connection to the destinationNode from the given output to the given input, an InvalidAccessError exception MUST be thrown.
|
||||||
|
if (m_output_connections.size() == before) {
|
||||||
|
return WebIDL::InvalidAccessError::create(realm(), MUST(String::formatted("No connection from output {} to input {} of given AudioNode", output, input)));
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam
|
||||||
void AudioNode::disconnect(GC::Ref<AudioParam> destination_param)
|
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioParam> destination_param)
|
||||||
{
|
{
|
||||||
(void)destination_param;
|
// The destinationParam parameter is the AudioParam to disconnect.
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(destination_param)");
|
auto before = m_param_connections.size();
|
||||||
|
m_param_connections.remove_all_matching([&](AudioParamConnection& connection) {
|
||||||
|
return connection.destination_param == destination_param;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there is no connection to the destinationParam, an InvalidAccessError exception MUST be thrown.
|
||||||
|
if (m_param_connections.size() == before) {
|
||||||
|
return WebIDL::InvalidAccessError::create(realm(), MUST(String::formatted("No connection to given AudioParam")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam-output
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam-output
|
||||||
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output)
|
WebIDL::ExceptionOr<void> AudioNode::disconnect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output)
|
||||||
{
|
{
|
||||||
(void)destination_param;
|
|
||||||
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
// The output parameter is an index describing which output of the AudioNode from which to disconnect.
|
||||||
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
// If this parameter is out-of-bounds, an IndexSizeError exception MUST be thrown.
|
||||||
if (output >= number_of_outputs()) {
|
if (output >= number_of_outputs()) {
|
||||||
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
return WebIDL::IndexSizeError::create(realm(), MUST(String::formatted("Output index {} exceeds number of outputs", output)));
|
||||||
}
|
}
|
||||||
|
// The destinationParam parameter is the AudioParam to disconnect.
|
||||||
|
auto before = m_param_connections.size();
|
||||||
|
m_param_connections.remove_all_matching([&](AudioParamConnection& connection) {
|
||||||
|
return connection.destination_param == destination_param && connection.output == output;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there is no connection to the destinationParam, an InvalidAccessError exception MUST be thrown.
|
||||||
|
if (m_param_connections.size() == before) {
|
||||||
|
return WebIDL::InvalidAccessError::create(realm(), MUST(String::formatted("No connection from output {} to given AudioParam", output)));
|
||||||
|
}
|
||||||
|
|
||||||
dbgln("FIXME: Implement AudioNode::disconnect(destination_param, output)");
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,10 +57,10 @@ public:
|
||||||
|
|
||||||
void disconnect();
|
void disconnect();
|
||||||
WebIDL::ExceptionOr<void> disconnect(WebIDL::UnsignedLong output);
|
WebIDL::ExceptionOr<void> disconnect(WebIDL::UnsignedLong output);
|
||||||
void disconnect(GC::Ref<AudioNode> destination_node);
|
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioNode> destination_node);
|
||||||
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output);
|
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output);
|
||||||
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input);
|
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input);
|
||||||
void disconnect(GC::Ref<AudioParam> destination_param);
|
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioParam> destination_param);
|
||||||
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output);
|
WebIDL::ExceptionOr<void> disconnect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output);
|
||||||
|
|
||||||
// https://webaudio.github.io/web-audio-api/#dom-audionode-context
|
// https://webaudio.github.io/web-audio-api/#dom-audionode-context
|
||||||
|
|
|
@ -318,3 +318,8 @@ Text/input/wpt-import/IndexedDB/idbfactory_open.any.html
|
||||||
Text/input/XHR/XMLHttpRequest-override-mimetype-blob.html
|
Text/input/XHR/XMLHttpRequest-override-mimetype-blob.html
|
||||||
|
|
||||||
Text/input/wpt-import/webaudio/the-audio-api/the-periodicwave-interface/periodicWave.html
|
Text/input/wpt-import/webaudio/the-audio-api/the-periodicwave-interface/periodicWave.html
|
||||||
|
|
||||||
|
; Will fail until OfflineRenderContext::start_rendering is implemented
|
||||||
|
; https://github.com/LadybirdBrowser/ladybird/issues/5333
|
||||||
|
Text/input/wpt-import/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect.html
|
||||||
|
Text/input/wpt-import/webaudio/the-audio-api/the-audionode-interface/audionode-disconnect-audioparam.html
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
audionode-disconnect-audioparam.html
|
||||||
|
</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>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script id="layout-test-code">
|
||||||
|
let renderQuantum = 128;
|
||||||
|
|
||||||
|
let sampleRate = 44100;
|
||||||
|
let renderDuration = 0.5;
|
||||||
|
let disconnectTime = 0.5 * renderDuration;
|
||||||
|
|
||||||
|
let audit = Audit.createTaskRunner();
|
||||||
|
|
||||||
|
// Calculate the index for disconnection.
|
||||||
|
function getDisconnectIndex(disconnectTime) {
|
||||||
|
let disconnectIndex = disconnectTime * sampleRate;
|
||||||
|
disconnectIndex = renderQuantum *
|
||||||
|
Math.floor((disconnectIndex + renderQuantum - 1) / renderQuantum);
|
||||||
|
return disconnectIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the index of value change.
|
||||||
|
function getValueChangeIndex(array, targetValue) {
|
||||||
|
return array.findIndex(function(element, index) {
|
||||||
|
if (element === targetValue)
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task 1: test disconnect(AudioParam) method.
|
||||||
|
audit.define('disconnect(AudioParam)', (task, should) => {
|
||||||
|
// Creates a buffer source with value [1] and then connect it to two
|
||||||
|
// gain nodes in series. The output of the buffer source is lowered by
|
||||||
|
// half
|
||||||
|
// (* 0.5) and then connected to two |.gain| AudioParams in each gain
|
||||||
|
// node.
|
||||||
|
//
|
||||||
|
// (1) bufferSource => gain1 => gain2
|
||||||
|
// (2) bufferSource => half => gain1.gain
|
||||||
|
// (3) half => gain2.gain
|
||||||
|
//
|
||||||
|
// This graph should produce the output of 2.25 (= 1 * 1.5 * 1.5). After
|
||||||
|
// disconnecting (3), it should produce 1.5.
|
||||||
|
let context =
|
||||||
|
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer1ch = createConstantBuffer(context, 1, 1);
|
||||||
|
let half = context.createGain();
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer1ch;
|
||||||
|
source.loop = true;
|
||||||
|
half.gain.value = 0.5;
|
||||||
|
|
||||||
|
source.connect(gain1);
|
||||||
|
gain1.connect(gain2);
|
||||||
|
gain2.connect(context.destination);
|
||||||
|
source.connect(half);
|
||||||
|
|
||||||
|
// Connecting |half| to both |gain1.gain| and |gain2.gain| amplifies the
|
||||||
|
// signal by 2.25 (= 1.5 * 1.5) because each gain node amplifies the
|
||||||
|
// signal by 1.5 (= 1.0 + 0.5).
|
||||||
|
half.connect(gain1.gain);
|
||||||
|
half.connect(gain2.gain);
|
||||||
|
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
// Schedule the disconnection at the half of render duration.
|
||||||
|
context.suspend(disconnectTime).then(function() {
|
||||||
|
half.disconnect(gain2.gain);
|
||||||
|
context.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
let channelData = buffer.getChannelData(0);
|
||||||
|
let disconnectIndex = getDisconnectIndex(disconnectTime);
|
||||||
|
let valueChangeIndex = getValueChangeIndex(channelData, 1.5);
|
||||||
|
|
||||||
|
// Expected values are: 1 * 1.5 * 1.5 -> 1 * 1.5 = [2.25, 1.5]
|
||||||
|
should(channelData, 'Channel #0').containValues([2.25, 1.5]);
|
||||||
|
should(valueChangeIndex, 'The index of value change')
|
||||||
|
.beEqualTo(disconnectIndex);
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 2: test disconnect(AudioParam, output) method.
|
||||||
|
audit.define('disconnect(AudioParam, output)', (task, should) => {
|
||||||
|
// Create a 2-channel buffer source with [1, 2] in each channel and
|
||||||
|
// make a serial connection through gain1 and gain 2. The make the
|
||||||
|
// buffer source half with a gain node and connect it to a 2-output
|
||||||
|
// splitter. Connect each output to 2 gain AudioParams respectively.
|
||||||
|
//
|
||||||
|
// (1) bufferSource => gain1 => gain2
|
||||||
|
// (2) bufferSource => half => splitter(2)
|
||||||
|
// (3) splitter#0 => gain1.gain
|
||||||
|
// (4) splitter#1 => gain2.gain
|
||||||
|
//
|
||||||
|
// This graph should produce 3 (= 1 * 1.5 * 2) and 6 (= 2 * 1.5 * 2) for
|
||||||
|
// each channel. After disconnecting (4), it should output 1.5 and 3.
|
||||||
|
let context =
|
||||||
|
new OfflineAudioContext(2, renderDuration * sampleRate, sampleRate);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer2ch = createConstantBuffer(context, 1, [1, 2]);
|
||||||
|
let splitter = context.createChannelSplitter(2);
|
||||||
|
let half = context.createGain();
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer2ch;
|
||||||
|
source.loop = true;
|
||||||
|
half.gain.value = 0.5;
|
||||||
|
|
||||||
|
source.connect(gain1);
|
||||||
|
gain1.connect(gain2);
|
||||||
|
gain2.connect(context.destination);
|
||||||
|
|
||||||
|
// |source| originally is [1, 2] but it becomes [0.5, 1] after 0.5 gain.
|
||||||
|
// Each splitter's output will be applied to |gain1.gain| and
|
||||||
|
// |gain2.gain| respectively in an additive fashion.
|
||||||
|
source.connect(half);
|
||||||
|
half.connect(splitter);
|
||||||
|
|
||||||
|
// This amplifies the signal by 1.5. (= 1.0 + 0.5)
|
||||||
|
splitter.connect(gain1.gain, 0);
|
||||||
|
|
||||||
|
// This amplifies the signal by 2. (= 1.0 + 1.0)
|
||||||
|
splitter.connect(gain2.gain, 1);
|
||||||
|
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
// Schedule the disconnection at the half of render duration.
|
||||||
|
context.suspend(disconnectTime).then(function() {
|
||||||
|
splitter.disconnect(gain2.gain, 1);
|
||||||
|
context.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
let channelData0 = buffer.getChannelData(0);
|
||||||
|
let channelData1 = buffer.getChannelData(1);
|
||||||
|
|
||||||
|
let disconnectIndex = getDisconnectIndex(disconnectTime);
|
||||||
|
let valueChangeIndexCh0 = getValueChangeIndex(channelData0, 1.5);
|
||||||
|
let valueChangeIndexCh1 = getValueChangeIndex(channelData1, 3);
|
||||||
|
|
||||||
|
// Expected values are: 1 * 1.5 * 2 -> 1 * 1.5 = [3, 1.5]
|
||||||
|
should(channelData0, 'Channel #0').containValues([3, 1.5]);
|
||||||
|
should(
|
||||||
|
valueChangeIndexCh0,
|
||||||
|
'The index of value change in channel #0')
|
||||||
|
.beEqualTo(disconnectIndex);
|
||||||
|
|
||||||
|
// Expected values are: 2 * 1.5 * 2 -> 2 * 1.5 = [6, 3]
|
||||||
|
should(channelData1, 'Channel #1').containValues([6, 3]);
|
||||||
|
should(
|
||||||
|
valueChangeIndexCh1,
|
||||||
|
'The index of value change in channel #1')
|
||||||
|
.beEqualTo(disconnectIndex);
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 3: exception checks.
|
||||||
|
audit.define('exceptions', (task, should) => {
|
||||||
|
let context = new AudioContext();
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let splitter = context.createChannelSplitter(2);
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
let gain3 = context.createGain();
|
||||||
|
|
||||||
|
// Connect a splitter to gain nodes and merger so we can test the
|
||||||
|
// possible ways of disconnecting the nodes to verify that appropriate
|
||||||
|
// exceptions are thrown.
|
||||||
|
gain1.connect(splitter);
|
||||||
|
splitter.connect(gain2.gain, 0);
|
||||||
|
splitter.connect(gain3.gain, 1);
|
||||||
|
gain2.connect(gain3);
|
||||||
|
gain3.connect(context.destination);
|
||||||
|
|
||||||
|
// gain1 is not connected to gain3.gain. Exception should be thrown.
|
||||||
|
should(
|
||||||
|
function() {
|
||||||
|
gain1.disconnect(gain3.gain);
|
||||||
|
},
|
||||||
|
'gain1.disconnect(gain3.gain)')
|
||||||
|
.throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// When the output index is good but the destination is invalid.
|
||||||
|
should(
|
||||||
|
function() {
|
||||||
|
splitter.disconnect(gain1.gain, 1);
|
||||||
|
},
|
||||||
|
'splitter.disconnect(gain1.gain, 1)')
|
||||||
|
.throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// When both arguments are wrong, throw IndexSizeError first.
|
||||||
|
should(
|
||||||
|
function() {
|
||||||
|
splitter.disconnect(gain1.gain, 2);
|
||||||
|
},
|
||||||
|
'splitter.disconnect(gain1.gain, 2)')
|
||||||
|
.throw(DOMException, 'IndexSizeError');
|
||||||
|
|
||||||
|
task.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
audit.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,298 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
audionode-disconnect.html
|
||||||
|
</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>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script id="layout-test-code">
|
||||||
|
let audit = Audit.createTaskRunner();
|
||||||
|
|
||||||
|
// Task 1: test disconnect() method.
|
||||||
|
audit.define('disconnect()', (task, should) => {
|
||||||
|
|
||||||
|
// Connect a source to multiple gain nodes, each connected to the
|
||||||
|
// destination. Then disconnect the source. The expected output should
|
||||||
|
// be all zeros since the source was disconnected.
|
||||||
|
let context = new OfflineAudioContext(1, 128, 44100);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer1ch = createConstantBuffer(context, 128, [1]);
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
let gain3 = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer1ch;
|
||||||
|
|
||||||
|
source.connect(gain1);
|
||||||
|
source.connect(gain2);
|
||||||
|
source.connect(gain3);
|
||||||
|
gain1.connect(context.destination);
|
||||||
|
gain2.connect(context.destination);
|
||||||
|
gain3.connect(context.destination);
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
// This disconnects everything.
|
||||||
|
source.disconnect();
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
|
||||||
|
// With everything disconnected, the result should be zero.
|
||||||
|
should(buffer.getChannelData(0), 'Channel #0')
|
||||||
|
.beConstantValueOf(0);
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 2: test disconnect(output) method.
|
||||||
|
audit.define('disconnect(output)', (task, should) => {
|
||||||
|
|
||||||
|
// Create multiple connections from each output of a ChannelSplitter
|
||||||
|
// to a gain node. Then test if disconnecting a single output of
|
||||||
|
// splitter is actually disconnected.
|
||||||
|
let context = new OfflineAudioContext(1, 128, 44100);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer3ch = createConstantBuffer(context, 128, [1, 2, 3]);
|
||||||
|
let splitter = context.createChannelSplitter(3);
|
||||||
|
let sum = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer3ch;
|
||||||
|
|
||||||
|
source.connect(splitter);
|
||||||
|
splitter.connect(sum, 0);
|
||||||
|
splitter.connect(sum, 1);
|
||||||
|
splitter.connect(sum, 2);
|
||||||
|
sum.connect(context.destination);
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
// This disconnects the second output.
|
||||||
|
splitter.disconnect(1);
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
|
||||||
|
// The rendered channel should contain 4. (= 1 + 0 + 3)
|
||||||
|
should(buffer.getChannelData(0), 'Channel #0')
|
||||||
|
.beConstantValueOf(4);
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 3: test disconnect(AudioNode) method.
|
||||||
|
audit.define('disconnect(AudioNode)', (task, should) => {
|
||||||
|
|
||||||
|
// Connect a source to multiple gain nodes. Then test if disconnecting a
|
||||||
|
// single destination selectively works correctly.
|
||||||
|
let context = new OfflineAudioContext(1, 128, 44100);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer1ch = createConstantBuffer(context, 128, [1]);
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
let gain3 = context.createGain();
|
||||||
|
let orphan = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer1ch;
|
||||||
|
|
||||||
|
source.connect(gain1);
|
||||||
|
source.connect(gain2);
|
||||||
|
source.connect(gain3);
|
||||||
|
gain1.connect(context.destination);
|
||||||
|
gain2.connect(context.destination);
|
||||||
|
gain3.connect(context.destination);
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
source.disconnect(gain2);
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
|
||||||
|
// The |sum| gain node should produce value 2. (1 + 0 + 1 = 2)
|
||||||
|
should(buffer.getChannelData(0), 'Channel #0')
|
||||||
|
.beConstantValueOf(2);
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 4: test disconnect(AudioNode, output) method.
|
||||||
|
audit.define('disconnect(AudioNode, output)', (task, should) => {
|
||||||
|
|
||||||
|
// Connect a buffer with 2 channels with each containing 1 and 2
|
||||||
|
// respectively to a ChannelSplitter, then connect the splitter to 2
|
||||||
|
// gain nodes as shown below:
|
||||||
|
// (1) splitter#0 => gain1
|
||||||
|
// (2) splitter#0 => gain2
|
||||||
|
// (3) splitter#1 => gain2
|
||||||
|
// Then disconnect (2) and verify if the selective disconnection on a
|
||||||
|
// specified output of the destination node works correctly.
|
||||||
|
let context = new OfflineAudioContext(1, 128, 44100);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer2ch = createConstantBuffer(context, 128, [1, 2]);
|
||||||
|
let splitter = context.createChannelSplitter(2);
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
|
||||||
|
source.buffer = buffer2ch;
|
||||||
|
|
||||||
|
source.connect(splitter);
|
||||||
|
splitter.connect(gain1, 0); // gain1 gets channel 0.
|
||||||
|
splitter.connect(gain2, 0); // gain2 sums channel 0 and 1.
|
||||||
|
splitter.connect(gain2, 1);
|
||||||
|
gain1.connect(context.destination);
|
||||||
|
gain2.connect(context.destination);
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
splitter.disconnect(gain2, 0); // Now gain2 gets [2]
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
|
||||||
|
// The sum of gain1 and gain2 should produce value 3. (= 1 + 2)
|
||||||
|
should(buffer.getChannelData(0), 'Channel #0')
|
||||||
|
.beConstantValueOf(3);
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 5: test disconnect(AudioNode, output, input) method.
|
||||||
|
audit.define('disconnect(AudioNode, output, input)', (task, should) => {
|
||||||
|
|
||||||
|
// Create a 3-channel buffer with [1, 2, 3] in each channel and then
|
||||||
|
// pass it through a splitter and a merger. Each input/output of the
|
||||||
|
// splitter and the merger is connected in a sequential order as shown
|
||||||
|
// below.
|
||||||
|
// (1) splitter#0 => merger#0
|
||||||
|
// (2) splitter#1 => merger#1
|
||||||
|
// (3) splitter#2 => merger#2
|
||||||
|
// Then disconnect (3) and verify if each channel contains [1] and [2]
|
||||||
|
// respectively.
|
||||||
|
let context = new OfflineAudioContext(3, 128, 44100);
|
||||||
|
let source = context.createBufferSource();
|
||||||
|
let buffer3ch = createConstantBuffer(context, 128, [1, 2, 3]);
|
||||||
|
let splitter = context.createChannelSplitter(3);
|
||||||
|
let merger = context.createChannelMerger(3);
|
||||||
|
|
||||||
|
source.buffer = buffer3ch;
|
||||||
|
|
||||||
|
source.connect(splitter);
|
||||||
|
splitter.connect(merger, 0, 0);
|
||||||
|
splitter.connect(merger, 1, 1);
|
||||||
|
splitter.connect(merger, 2, 2);
|
||||||
|
merger.connect(context.destination);
|
||||||
|
source.start();
|
||||||
|
|
||||||
|
splitter.disconnect(merger, 2, 2);
|
||||||
|
|
||||||
|
context.startRendering()
|
||||||
|
.then(function(buffer) {
|
||||||
|
|
||||||
|
// Each channel should have 1, 2, and 0 respectively.
|
||||||
|
should(buffer.getChannelData(0), 'Channel #0')
|
||||||
|
.beConstantValueOf(1);
|
||||||
|
should(buffer.getChannelData(1), 'Channel #1')
|
||||||
|
.beConstantValueOf(2);
|
||||||
|
should(buffer.getChannelData(2), 'Channel #2')
|
||||||
|
.beConstantValueOf(0);
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task 6: exception checks.
|
||||||
|
audit.define('exceptions', (task, should) => {
|
||||||
|
let context = new OfflineAudioContext(2, 128, 44100);
|
||||||
|
let gain1 = context.createGain();
|
||||||
|
let splitter = context.createChannelSplitter(2);
|
||||||
|
let merger = context.createChannelMerger(2);
|
||||||
|
let gain2 = context.createGain();
|
||||||
|
let gain3 = context.createGain();
|
||||||
|
|
||||||
|
// Connect a splitter to gain nodes and merger so we can test the
|
||||||
|
// possible ways of disconnecting the nodes to verify that appropriate
|
||||||
|
// exceptions are thrown.
|
||||||
|
gain1.connect(splitter);
|
||||||
|
splitter.connect(gain2, 0);
|
||||||
|
splitter.connect(gain3, 1);
|
||||||
|
splitter.connect(merger, 0, 0);
|
||||||
|
splitter.connect(merger, 1, 1);
|
||||||
|
gain2.connect(gain3);
|
||||||
|
gain3.connect(context.destination);
|
||||||
|
merger.connect(context.destination);
|
||||||
|
|
||||||
|
// There is no output #2. An exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(2);
|
||||||
|
}, 'splitter.disconnect(2)').throw(DOMException, 'IndexSizeError');
|
||||||
|
|
||||||
|
// Disconnecting the output already disconnected should not throw.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(1);
|
||||||
|
splitter.disconnect(1);
|
||||||
|
}, 'Disconnecting a connection twice').notThrow();
|
||||||
|
|
||||||
|
// gain1 is not connected gain2. An exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
gain1.disconnect(gain2);
|
||||||
|
}, 'gain1.disconnect(gain2)').throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// gain1 and gain3 are not connected. An exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
gain1.disconnect(gain3);
|
||||||
|
}, 'gain1.disconnect(gain3)').throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// There is no output #2 in the splitter. An exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(gain2, 2);
|
||||||
|
}, 'splitter.disconnect(gain2, 2)').throw(DOMException, 'IndexSizeError');
|
||||||
|
|
||||||
|
// The splitter and gain1 are not connected. An exception should be
|
||||||
|
// thrown.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(gain1, 0);
|
||||||
|
}, 'splitter.disconnect(gain1, 0)').throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// The splitter output #0 and the gain3 output #0 are not connected. An
|
||||||
|
// exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(gain3, 0, 0);
|
||||||
|
}, 'splitter.disconnect(gain3, 0, 0)').throw(DOMException, 'InvalidAccessError');
|
||||||
|
|
||||||
|
// The output index is out of bound. An exception should be thrown.
|
||||||
|
should(function() {
|
||||||
|
splitter.disconnect(merger, 3, 0);
|
||||||
|
}, 'splitter.disconnect(merger, 3, 0)').throw(DOMException, 'IndexSizeError');
|
||||||
|
|
||||||
|
task.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
audit.define('disabled-outputs', (task, should) => {
|
||||||
|
// See crbug.com/656652
|
||||||
|
let context = new OfflineAudioContext(2, 1024, 44100);
|
||||||
|
let g1 = context.createGain();
|
||||||
|
let g2 = context.createGain();
|
||||||
|
g1.connect(g2);
|
||||||
|
g1.disconnect(g2);
|
||||||
|
let g3 = context.createGain();
|
||||||
|
g2.connect(g3);
|
||||||
|
g1.connect(g2);
|
||||||
|
context.startRendering()
|
||||||
|
.then(function() {
|
||||||
|
// If we make it here, we passed.
|
||||||
|
should(true, 'Disabled outputs handled')
|
||||||
|
.message('correctly', 'inccorrectly');
|
||||||
|
})
|
||||||
|
.then(() => task.done());
|
||||||
|
});
|
||||||
|
|
||||||
|
audit.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue