LibWeb/WebAudio: Implement AudioNode::connect()

Sets up the basic infrastructure for the audio node graph and
implements the AudioNode `connect()` method with its related overides.
AudioNodes store vectors of AudioNodeConnections and
AudioParamConnections to represent links between nodes.
This commit is contained in:
Ben Eidson 2025-05-20 21:15:15 -04:00 committed by Andrew Kaster
commit 15aa7dce29
Notes: github-actions[bot] 2025-06-17 22:57:18 +00:00
2 changed files with 54 additions and 2 deletions

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -51,8 +52,15 @@ WebIDL::ExceptionOr<void> AudioNode::initialize_audio_node_options(AudioNodeOpti
// https://webaudio.github.io/web-audio-api/#dom-audionode-connect // https://webaudio.github.io/web-audio-api/#dom-audionode-connect
WebIDL::ExceptionOr<GC::Ref<AudioNode>> AudioNode::connect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input) WebIDL::ExceptionOr<GC::Ref<AudioNode>> AudioNode::connect(GC::Ref<AudioNode> destination_node, WebIDL::UnsignedLong output, WebIDL::UnsignedLong input)
{ {
AudioNodeConnection output_connection { destination_node, output, input };
AudioNodeConnection input_connection { *this, output, input };
// There can only be one connection between a given output of one specific node and a given input of another specific node. // There can only be one connection between a given output of one specific node and a given input of another specific node.
// Multiple connections with the same termini are ignored. // Multiple connections with the same termini are ignored.
for (auto const& existing_connection : m_output_connections) {
if (existing_connection == output_connection)
return destination_node;
}
// If the destination parameter is an AudioNode that has been created using another AudioContext, an InvalidAccessError MUST be thrown. // If the destination parameter is an AudioNode that has been created using another AudioContext, an InvalidAccessError MUST be thrown.
if (m_context != destination_node->m_context) { if (m_context != destination_node->m_context) {
@ -70,14 +78,26 @@ WebIDL::ExceptionOr<GC::Ref<AudioNode>> AudioNode::connect(GC::Ref<AudioNode> de
if (input >= destination_node->number_of_inputs()) { if (input >= destination_node->number_of_inputs()) {
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)));
} }
// Connect node's output to destination_node input.
m_output_connections.append(output_connection);
// Connect destination_node input to node's output.
destination_node->m_input_connections.append(input_connection);
dbgln("FIXME: Implement Audio::connect(AudioNode)");
return destination_node; return destination_node;
} }
// https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output // https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output
WebIDL::ExceptionOr<void> AudioNode::connect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output) WebIDL::ExceptionOr<void> AudioNode::connect(GC::Ref<AudioParam> destination_param, WebIDL::UnsignedLong output)
{ {
AudioParamConnection param_connection { destination_param, output };
// There can only be one connection between a given output of one specific node and a specific AudioParam. Multiple connections
// with the same termini are ignored.
for (auto const& existing_connection : m_param_connections) {
if (existing_connection == param_connection)
return {};
}
// If destinationParam belongs to an AudioNode that belongs to a BaseAudioContext that is different from the BaseAudioContext // If destinationParam belongs to an AudioNode that belongs to a BaseAudioContext that is different from the BaseAudioContext
// that has created the AudioNode on which this method was called, an InvalidAccessError MUST be thrown. // that has created the AudioNode on which this method was called, an InvalidAccessError MUST be thrown.
if (m_context != destination_param->context()) { if (m_context != destination_param->context()) {
@ -90,7 +110,9 @@ WebIDL::ExceptionOr<void> AudioNode::connect(GC::Ref<AudioParam> destination_par
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::connect(AudioParam)"); // Connect node's output to destination_param.
m_param_connections.append(param_connection);
return {}; return {};
} }
@ -224,6 +246,14 @@ void AudioNode::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_context); visitor.visit(m_context);
for (auto& conn : m_param_connections)
visitor.visit(conn.destination_param);
for (auto& conn : m_input_connections)
visitor.visit(conn.destination_node);
for (auto& conn : m_output_connections)
visitor.visit(conn.destination_node);
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -28,6 +29,21 @@ struct AudioNodeDefaultOptions {
Bindings::ChannelInterpretation channel_interpretation; Bindings::ChannelInterpretation channel_interpretation;
}; };
struct AudioNodeConnection {
GC::Ref<AudioNode> destination_node;
WebIDL::UnsignedLong output;
WebIDL::UnsignedLong input;
bool operator==(AudioNodeConnection const& other) const = default;
};
struct AudioParamConnection {
GC::Ref<AudioParam> destination_param;
WebIDL::UnsignedLong output;
bool operator==(AudioParamConnection const& other) const = default;
};
// https://webaudio.github.io/web-audio-api/#AudioNode // https://webaudio.github.io/web-audio-api/#AudioNode
class AudioNode : public DOM::EventTarget { class AudioNode : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(AudioNode, DOM::EventTarget); WEB_PLATFORM_OBJECT(AudioNode, DOM::EventTarget);
@ -81,6 +97,12 @@ private:
WebIDL::UnsignedLong m_channel_count { 2 }; WebIDL::UnsignedLong m_channel_count { 2 };
Bindings::ChannelCountMode m_channel_count_mode { Bindings::ChannelCountMode::Max }; Bindings::ChannelCountMode m_channel_count_mode { Bindings::ChannelCountMode::Max };
Bindings::ChannelInterpretation m_channel_interpretation { Bindings::ChannelInterpretation::Speakers }; Bindings::ChannelInterpretation m_channel_interpretation { Bindings::ChannelInterpretation::Speakers };
// Connections from other AudioNode outputs into this node's inputs.
Vector<AudioNodeConnection> m_input_connections;
// Connections from this node's outputs into other AudioNode inputs.
Vector<AudioNodeConnection> m_output_connections;
// Connections from this node's outputs into AudioParams.
Vector<AudioParamConnection> m_param_connections;
}; };
} }