LibWeb/WebAudio: Add stable IDs to AudioNodes

Modeled after UniqueNodeID and uses incremental counter on
BaseAudioContext to assign IDs.
This commit is contained in:
Ben Eidson 2026-01-18 13:42:23 -05:00 committed by Jelle Raaijmakers
parent 0796418c18
commit d942b98549
7 changed files with 44 additions and 20 deletions

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* Copyright (c) 2025-2026, Ben Eidson <b.e.eidson@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -17,6 +17,7 @@ AudioNode::AudioNode(JS::Realm& realm, GC::Ref<BaseAudioContext> context, WebIDL
: DOM::EventTarget(realm)
, m_context(context)
, m_channel_count(channel_count)
, m_node_id(context->next_node_id({}))
{
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* Copyright (c) 2025-2026, Ben Eidson <b.e.eidson@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -12,6 +12,7 @@
#include <LibWeb/Bindings/AudioNodePrototype.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/WebAudio/Types.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::WebAudio {
@ -86,6 +87,8 @@ public:
WebIDL::ExceptionOr<void> 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<BaseAudioContext>, WebIDL::UnsignedLong channel_count = 2);
@ -103,6 +106,7 @@ private:
Vector<AudioNodeConnection> m_output_connections;
// Connections from this node's outputs into AudioParams.
Vector<AudioParamConnection> m_param_connections;
NodeID const m_node_id;
};
}

View file

@ -51,9 +51,7 @@ WebIDL::ExceptionOr<void> 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<void> 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 {};
}

View file

@ -22,6 +22,7 @@
#include <LibWeb/WebAudio/PeriodicWave.h>
#include <LibWeb/WebAudio/ScriptProcessorNode.h>
#include <LibWeb/WebAudio/StereoPannerNode.h>
#include <LibWeb/WebAudio/Types.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::WebAudio {
@ -89,6 +90,8 @@ public:
void queue_control_message(ControlMessage);
NodeID next_node_id(Badge<AudioNode>) { 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<JS::PromiseCapability>, GC::Root<WebIDL::BufferSource>, GC::Ptr<WebIDL::CallbackType>, GC::Ptr<WebIDL::CallbackType>);
u64 m_next_node_id { 0 };
float m_sample_rate { 0 };
double m_current_time { 0 };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* Copyright (c) 2025-2026, Ben Eidson <b.e.eidson@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,22 +7,21 @@
#pragma once
#include <AK/Variant.h>
#include <LibWeb/WebAudio/Types.h>
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<StartSource, StopSource>;

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2026, Ben Eidson <b.e.eidson@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/DistinctNumeric.h>
namespace Web::WebAudio {
// Stable identifier for AudioNode instances within a BaseAudioContext.
AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, NodeID, CastToUnderlying);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Ben Eidson <b.e.eidson@gmail.com>
* Copyright (c) 2025-2026, Ben Eidson <b.e.eidson@gmail.com>
*
* 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<Web::WebAudio::StartSource>());
EXPECT_EQ(batch[0].get<Web::WebAudio::StartSource>().when, 1.0);
EXPECT_EQ(batch[0].get<Web::WebAudio::StartSource>().node_id, 0u);
EXPECT(batch[1].has<Web::WebAudio::StopSource>());
EXPECT_EQ(batch[1].get<Web::WebAudio::StopSource>().when, 2.0);
EXPECT_EQ(batch[1].get<Web::WebAudio::StopSource>().node_id, Web::WebAudio::NodeID { 1 });
EXPECT(batch[2].has<Web::WebAudio::StartSource>());
EXPECT_EQ(batch[2].get<Web::WebAudio::StartSource>().when, 3.0);
EXPECT_EQ(batch[2].get<Web::WebAudio::StartSource>().node_id, Web::WebAudio::NodeID { 2 });
}