From d942b98549383a6b4892e6d6088da6e97032c90f Mon Sep 17 00:00:00 2001 From: Ben Eidson Date: Sun, 18 Jan 2026 13:42:23 -0500 Subject: [PATCH] LibWeb/WebAudio: Add stable IDs to AudioNodes Modeled after UniqueNodeID and uses incremental counter on BaseAudioContext to assign IDs. --- Libraries/LibWeb/WebAudio/AudioNode.cpp | 3 ++- Libraries/LibWeb/WebAudio/AudioNode.h | 6 +++++- .../LibWeb/WebAudio/AudioScheduledSourceNode.cpp | 8 ++------ Libraries/LibWeb/WebAudio/BaseAudioContext.h | 5 +++++ Libraries/LibWeb/WebAudio/ControlMessage.h | 11 +++++------ Libraries/LibWeb/WebAudio/Types.h | 16 ++++++++++++++++ Tests/LibWeb/TestControlMessageQueue.cpp | 15 +++++++++------ 7 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 Libraries/LibWeb/WebAudio/Types.h diff --git a/Libraries/LibWeb/WebAudio/AudioNode.cpp b/Libraries/LibWeb/WebAudio/AudioNode.cpp index 4128266c6f..b736954b73 100644 --- a/Libraries/LibWeb/WebAudio/AudioNode.cpp +++ b/Libraries/LibWeb/WebAudio/AudioNode.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2024, Shannon Booth - * Copyright (c) 2025, Ben Eidson + * Copyright (c) 2025-2026, Ben Eidson * * SPDX-License-Identifier: BSD-2-Clause */ @@ -17,6 +17,7 @@ AudioNode::AudioNode(JS::Realm& realm, GC::Ref context, WebIDL : DOM::EventTarget(realm) , m_context(context) , m_channel_count(channel_count) + , m_node_id(context->next_node_id({})) { } diff --git a/Libraries/LibWeb/WebAudio/AudioNode.h b/Libraries/LibWeb/WebAudio/AudioNode.h index 63af908de5..d0094e8775 100644 --- a/Libraries/LibWeb/WebAudio/AudioNode.h +++ b/Libraries/LibWeb/WebAudio/AudioNode.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2024, Shannon Booth - * Copyright (c) 2025, Ben Eidson + * Copyright (c) 2025-2026, Ben Eidson * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::WebAudio { @@ -86,6 +87,8 @@ public: WebIDL::ExceptionOr initialize_audio_node_options(AudioNodeOptions const& given_options, AudioNodeDefaultOptions const& default_options); + NodeID node_id() const { return m_node_id; } + protected: AudioNode(JS::Realm&, GC::Ref, WebIDL::UnsignedLong channel_count = 2); @@ -103,6 +106,7 @@ private: Vector m_output_connections; // Connections from this node's outputs into AudioParams. Vector m_param_connections; + NodeID const m_node_id; }; } diff --git a/Libraries/LibWeb/WebAudio/AudioScheduledSourceNode.cpp b/Libraries/LibWeb/WebAudio/AudioScheduledSourceNode.cpp index c08724fe13..e4be6617b8 100644 --- a/Libraries/LibWeb/WebAudio/AudioScheduledSourceNode.cpp +++ b/Libraries/LibWeb/WebAudio/AudioScheduledSourceNode.cpp @@ -51,9 +51,7 @@ WebIDL::ExceptionOr AudioScheduledSourceNode::start(double when) set_source_started(true); // 4. Queue a control message to start the AudioScheduledSourceNode, including the parameter values in the message. - // FIXME: Include a stable source id so the rendering thread can route this message to the correct AudioScheduledSourceNode - // once render-thread scheduling is implemented. - context()->queue_control_message(StartSource { .when = when }); + context()->queue_control_message(StartSource { .node_id = node_id(), .when = when }); // FIXME: 5. Send a control message to the associated AudioContext to start running its rendering thread only when all the following conditions are met: @@ -73,9 +71,7 @@ WebIDL::ExceptionOr AudioScheduledSourceNode::stop(double when) return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "when must not be negative"sv }; // 3. Queue a control message to stop the AudioScheduledSourceNode, including the parameter values in the message. - // FIXME: Include a stable source id so the rendering thread can route this message to the correct AudioScheduledSourceNode - // once render-thread scheduling is implemented. - context()->queue_control_message(StopSource { .when = when }); + context()->queue_control_message(StopSource { .node_id = node_id(), .when = when }); return {}; } diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.h b/Libraries/LibWeb/WebAudio/BaseAudioContext.h index 18ad8353f1..6a770dbcb2 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.h +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.h @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace Web::WebAudio { @@ -89,6 +90,8 @@ public: void queue_control_message(ControlMessage); + NodeID next_node_id(Badge) { return ++m_next_node_id; } + protected: explicit BaseAudioContext(JS::Realm&, float m_sample_rate = 0); @@ -106,6 +109,8 @@ private: void queue_a_decoding_operation(GC::Ref, GC::Root, GC::Ptr, GC::Ptr); + u64 m_next_node_id { 0 }; + float m_sample_rate { 0 }; double m_current_time { 0 }; diff --git a/Libraries/LibWeb/WebAudio/ControlMessage.h b/Libraries/LibWeb/WebAudio/ControlMessage.h index ddab464d27..4a162a4634 100644 --- a/Libraries/LibWeb/WebAudio/ControlMessage.h +++ b/Libraries/LibWeb/WebAudio/ControlMessage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Ben Eidson + * Copyright (c) 2025-2026, Ben Eidson * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,22 +7,21 @@ #pragma once #include +#include namespace Web::WebAudio { -using SourceId = u64; - struct StartSource { - SourceId id { 0 }; // FIXME: stable per-source id for render-thread routing + NodeID node_id { 0 }; double when { 0.0 }; }; struct StopSource { - SourceId id { 0 }; // FIXME: stable per-source id for render-thread routing + NodeID node_id { 0 }; double when { 0.0 }; }; -// FIXME: add more event types +// FIXME: add more message types // https://webaudio.github.io/web-audio-api/#control-message using ControlMessage = Variant; diff --git a/Libraries/LibWeb/WebAudio/Types.h b/Libraries/LibWeb/WebAudio/Types.h new file mode 100644 index 0000000000..7082e271ac --- /dev/null +++ b/Libraries/LibWeb/WebAudio/Types.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026, Ben Eidson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::WebAudio { + +// Stable identifier for AudioNode instances within a BaseAudioContext. +AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, NodeID, CastToUnderlying); + +} diff --git a/Tests/LibWeb/TestControlMessageQueue.cpp b/Tests/LibWeb/TestControlMessageQueue.cpp index 4ef15d5e81..b50b73aee0 100644 --- a/Tests/LibWeb/TestControlMessageQueue.cpp +++ b/Tests/LibWeb/TestControlMessageQueue.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Ben Eidson + * Copyright (c) 2025-2026, Ben Eidson * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,8 +12,8 @@ TEST_CASE(drain_returns_all_and_clears) { Web::WebAudio::ControlMessageQueue queue; - queue.enqueue(Web::WebAudio::StartSource { .when = 1.0 }); - queue.enqueue(Web::WebAudio::StopSource { .when = 2.0 }); + queue.enqueue(Web::WebAudio::StartSource { .node_id = Web::WebAudio::NodeID { 0 }, .when = 1.0 }); + queue.enqueue(Web::WebAudio::StopSource { .node_id = Web::WebAudio::NodeID { 1 }, .when = 2.0 }); auto batch = queue.drain(); EXPECT_EQ(batch.size(), 2u); @@ -26,19 +26,22 @@ TEST_CASE(drain_preserves_first_in_first_out) { Web::WebAudio::ControlMessageQueue queue; - queue.enqueue(Web::WebAudio::StartSource { .when = 1.0 }); - queue.enqueue(Web::WebAudio::StopSource { .when = 2.0 }); - queue.enqueue(Web::WebAudio::StartSource { .when = 3.0 }); + queue.enqueue(Web::WebAudio::StartSource { .node_id = Web::WebAudio::NodeID { 0 }, .when = 1.0 }); + queue.enqueue(Web::WebAudio::StopSource { .node_id = Web::WebAudio::NodeID { 1 }, .when = 2.0 }); + queue.enqueue(Web::WebAudio::StartSource { .node_id = Web::WebAudio::NodeID { 2 }, .when = 3.0 }); auto batch = queue.drain(); EXPECT_EQ(batch.size(), 3u); EXPECT(batch[0].has()); EXPECT_EQ(batch[0].get().when, 1.0); + EXPECT_EQ(batch[0].get().node_id, 0u); EXPECT(batch[1].has()); EXPECT_EQ(batch[1].get().when, 2.0); + EXPECT_EQ(batch[1].get().node_id, Web::WebAudio::NodeID { 1 }); EXPECT(batch[2].has()); EXPECT_EQ(batch[2].get().when, 3.0); + EXPECT_EQ(batch[2].get().node_id, Web::WebAudio::NodeID { 2 }); }