Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
50885c5
feat: audio event scheduler
Dec 29, 2025
eed4ed5
refactor: audio buffer source node
Dec 29, 2025
718097b
fix: missing header
Dec 29, 2025
caca77b
fix: docstring
Dec 30, 2025
b27ea3c
refactor: template for scheduleEvent
Dec 30, 2025
fc907d8
fix: aligned AudioPlayer on Android with iOS
Dec 30, 2025
c88d9ff
refactor: audio param
Dec 30, 2025
11309e2
feat: implemented fat function for fully stacked allocated callable o…
poneciak57 Dec 31, 2025
8903d17
refactor: onLoopEndedCallback and comments
Jan 2, 2026
dcc84c0
fix: fixed use-after-free caused by naive memcpy in fat function move…
poneciak57 Jan 3, 2026
d96a108
chore: formatting
poneciak57 Jan 3, 2026
079e895
feat: improved fat function to only create deleter and mover when it …
poneciak57 Jan 3, 2026
a08cfb7
fix: nitpicks
Jan 13, 2026
1b2b09f
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 13, 2026
5aef690
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 16, 2026
d4f22f1
fix: clean-up
Jan 16, 2026
d7a0545
refactor: concepts and nitpicks
Jan 16, 2026
4e57aee
fix: removed unused conditional typename
Jan 19, 2026
3d69751
refactor: audio buffer base source node
Jan 19, 2026
b23c5c6
refactor: audio buffer queue source node
Jan 20, 2026
9c580bc
refactor: assn
Jan 20, 2026
e48501f
refactor: gain, delay and constant source nodes
Jan 20, 2026
6e01395
chore: format
Jan 20, 2026
8111b72
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 20, 2026
2c51e91
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#include <audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h>

#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
#include <audioapi/core/sources/AudioBufferSourceNode.h>
#include <audioapi/core/BaseAudioContext.h>

#include <memory>
#include <utility>

namespace audioapi {

AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject(
const std::shared_ptr<AudioBufferSourceNode> &node)
: AudioBufferBaseSourceNodeHostObject(node) {
: AudioBufferBaseSourceNodeHostObject(node), loop_(false), loopSkip_(false), loopStart_(0), loopEnd_(0), buffer_(nullptr) {
// TODO: init with AudioBufferSourceOptions when PR with new ctors will be merged
addGetters(
JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loop),
JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopSkip),
Expand Down Expand Up @@ -37,104 +40,136 @@ AudioBufferSourceNodeHostObject::~AudioBufferSourceNodeHostObject() {
// When JSI object is garbage collected (together with the eventual callback),
// underlying source node might still be active and try to call the
// non-existing callback.
audioBufferSourceNode->setOnLoopEndedCallbackId(0);
auto event = [audioBufferSourceNode](BaseAudioContext &context) {
audioBufferSourceNode->setOnLoopEndedCallbackId(0);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loop) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
auto loop = audioBufferSourceNode->getLoop();
return {loop};
return {loop_};
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
auto loopSkip = audioBufferSourceNode->getLoopSkip();
return {loopSkip};
return {loopSkip_};
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, buffer) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
auto buffer = audioBufferSourceNode->getBuffer();

if (!buffer) {
if (!buffer_) {
return jsi::Value::null();
}

auto bufferHostObject = std::make_shared<AudioBufferHostObject>(buffer);
auto jsiObject = jsi::Object::createFromHostObject(runtime, bufferHostObject);
jsiObject.setExternalMemoryPressure(runtime, bufferHostObject->getSizeInBytes() + 16);
auto jsiObject = jsi::Object::createFromHostObject(runtime, buffer_);
jsiObject.setExternalMemoryPressure(runtime, buffer_->getSizeInBytes() + 16);
return jsiObject;
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
auto loopStart = audioBufferSourceNode->getLoopStart();
return {loopStart};
return {loopStart_};
}

JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
auto loopEnd = audioBufferSourceNode->getLoopEnd();
return {loopEnd};
return {loopEnd_};
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loop) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
audioBufferSourceNode->setLoop(value.getBool());
auto loop = value.getBool();

auto event = [audioBufferSourceNode, loop](BaseAudioContext &context) {
audioBufferSourceNode->setLoop(loop);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
loop_ = loop;
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
audioBufferSourceNode->setLoopSkip(value.getBool());
auto loopSkip = value.getBool();

auto event = [audioBufferSourceNode, loopSkip](BaseAudioContext &context) {
audioBufferSourceNode->setLoopSkip(loopSkip);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
loopSkip_ = loopSkip;
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
audioBufferSourceNode->setLoopStart(value.getNumber());
auto loopStart = value.getNumber();

auto event = [audioBufferSourceNode, loopStart](BaseAudioContext &context) {
audioBufferSourceNode->setLoopStart(loopStart);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
loopStart_ = loopStart;
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);
audioBufferSourceNode->setLoopEnd(value.getNumber());
auto loopEnd = value.getNumber();

auto event = [audioBufferSourceNode, loopEnd](BaseAudioContext &context) {
audioBufferSourceNode->setLoopEnd(loopEnd);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
loopEnd_ = loopEnd;
}

JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, onLoopEnded) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);

audioBufferSourceNode->setOnLoopEndedCallbackId(
std::stoull(value.getString(runtime).utf8(runtime)));
auto event = [audioBufferSourceNode, callbackId = std::stoull(value.getString(runtime).utf8(runtime))](BaseAudioContext &context) {
audioBufferSourceNode->setOnLoopEndedCallbackId(callbackId);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
}

JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, start) {
auto when = args[0].getNumber();
auto offset = args[1].getNumber();

auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);

if (args[2].isUndefined()) {
audioBufferSourceNode->start(when, offset);
auto event = [
audioBufferSourceNode,
when = args[0].getNumber(),
offset = args[1].getNumber(),
duration = args[2].isUndefined() ? -1 : args[2].getNumber()
](BaseAudioContext &context) {
audioBufferSourceNode->start(when, offset, duration);
};

return jsi::Value::undefined();
}

auto duration = args[2].getNumber();
audioBufferSourceNode->start(when, offset, duration);
audioBufferSourceNode->scheduleAudioEvent(std::move(event));

return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, setBuffer) {
auto audioBufferSourceNode = std::static_pointer_cast<AudioBufferSourceNode>(node_);

if (args[0].isNull()) {
audioBufferSourceNode->setBuffer(std::shared_ptr<AudioBuffer>(nullptr));
return jsi::Value::undefined();
}
auto bufferHostObject = args[0].isNull() ? std::shared_ptr<AudioBufferHostObject>(nullptr) :
args[0].getObject(runtime).asHostObject<AudioBufferHostObject>(runtime);

if (bufferHostObject != nullptr) {
thisValue.asObject(runtime).setExternalMemoryPressure(
runtime, bufferHostObject->getSizeInBytes() + 16);
}

auto event = [
audioBufferSourceNode,
buffer = bufferHostObject ? bufferHostObject->audioBuffer_ : nullptr
](BaseAudioContext &context) {
audioBufferSourceNode->setBuffer(buffer);
};

audioBufferSourceNode->scheduleAudioEvent(std::move(event));
buffer_ = bufferHostObject;
loopEnd_ = bufferHostObject ? bufferHostObject->audioBuffer_->getDuration() : 0.0;

auto bufferHostObject = args[0].getObject(runtime).asHostObject<AudioBufferHostObject>(runtime);
thisValue.asObject(runtime).setExternalMemoryPressure(
runtime, bufferHostObject->getSizeInBytes() + 16);
audioBufferSourceNode->setBuffer(bufferHostObject->audioBuffer_);
return jsi::Value::undefined();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace audioapi {
using namespace facebook;

class AudioBufferSourceNode;
class AudioBufferHostObject;

class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObject {
public:
Expand All @@ -30,6 +31,13 @@ class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObje

JSI_HOST_FUNCTION_DECL(start);
JSI_HOST_FUNCTION_DECL(setBuffer);

private:
bool loop_;
bool loopSkip_;
double loopStart_;
double loopEnd_;
std::shared_ptr<AudioBufferHostObject> buffer_;
};

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ AudioContext::AudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
const RuntimeRegistry &runtimeRegistry)
: BaseAudioContext(audioEventHandlerRegistry, runtimeRegistry), isInitialized_(false) {
sampleRate_ = sampleRate;
: BaseAudioContext(sampleRate, audioEventHandlerRegistry, runtimeRegistry), isInitialized_(false) {
state_ = ContextState::SUSPENDED;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ void AudioNode::disable() {
}
}

bool AudioNode::scheduleAudioEvent(std::function<void(BaseAudioContext &)> &&event) noexcept {
if (std::shared_ptr<BaseAudioContext> context = context_.lock()) {
return context->scheduleAudioEvent(std::move(event));
}

return false;
}

std::string AudioNode::toString(ChannelCountMode mode) {
switch (mode) {
case ChannelCountMode::MAX:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <string>
#include <unordered_set>
#include <vector>
#include <functional>

namespace audioapi {

Expand Down Expand Up @@ -42,6 +43,8 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
void enable();
virtual void disable();

bool scheduleAudioEvent(std::function<void(BaseAudioContext &)> &&event) noexcept;

protected:
friend class AudioNodeManager;
friend class AudioDestinationNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@
namespace audioapi {

BaseAudioContext::BaseAudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
const RuntimeRegistry &runtimeRegistry)
: nodeManager_(std::make_shared<AudioNodeManager>()),
: sampleRate_ {sampleRate},
nodeManager_(std::make_shared<AudioNodeManager>()),
audioEventHandlerRegistry_(audioEventHandlerRegistry),
runtimeRegistry_(runtimeRegistry) {}

void BaseAudioContext::initialize() {
audioEventScheduler_ = std::make_unique<AudioEventScheduler>(shared_from_this());
destination_ = std::make_shared<AudioDestinationNode>(shared_from_this());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <audioapi/core/types/ContextState.h>
#include <audioapi/core/types/OscillatorType.h>
#include <audioapi/core/utils/AudioEventScheduler.h>
#include <audioapi/core/utils/worklets/SafeIncludes.h>
#include <cassert>
#include <complex>
Expand Down Expand Up @@ -42,6 +43,7 @@ class WaveShaperNode;
class BaseAudioContext : public std::enable_shared_from_this<BaseAudioContext> {
public:
explicit BaseAudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
const RuntimeRegistry &runtimeRegistry);
virtual ~BaseAudioContext() = default;
Expand Down Expand Up @@ -101,12 +103,19 @@ class BaseAudioContext : public std::enable_shared_from_this<BaseAudioContext> {
[[nodiscard]] bool isSuspended() const;
[[nodiscard]] bool isClosed() const;

void inline processAudioEvents() {
audioEventScheduler_->processAllEvents();
}

bool inline scheduleAudioEvent(std::function<void(BaseAudioContext &)> &&event) noexcept {
return audioEventScheduler_->scheduleEvent(std::move(event));
}

protected:
static std::string toString(ContextState state);

std::shared_ptr<AudioDestinationNode> destination_;
// init in AudioContext or OfflineContext constructor
float sampleRate_{};
float sampleRate_;
ContextState state_ = ContextState::RUNNING;
std::shared_ptr<AudioNodeManager> nodeManager_;

Expand All @@ -116,6 +125,8 @@ class BaseAudioContext : public std::enable_shared_from_this<BaseAudioContext> {
std::shared_ptr<PeriodicWave> cachedSawtoothWave_ = nullptr;
std::shared_ptr<PeriodicWave> cachedTriangleWave_ = nullptr;

std::unique_ptr<AudioEventScheduler> audioEventScheduler_;

[[nodiscard]] virtual bool isDriverRunning() const = 0;

public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ OfflineAudioContext::OfflineAudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
const RuntimeRegistry &runtimeRegistry)
: BaseAudioContext(audioEventHandlerRegistry, runtimeRegistry),
: BaseAudioContext(sampleRate, audioEventHandlerRegistry, runtimeRegistry),
length_(length),
numberOfChannels_(numberOfChannels),
currentSampleFrame_(0) {
sampleRate_ = sampleRate;
resultBus_ =
std::make_shared<AudioBus>(static_cast<int>(length_), numberOfChannels_, sampleRate_);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void AudioDestinationNode::renderAudio(
}

if (std::shared_ptr<BaseAudioContext> context = context_.lock()) {
context->processAudioEvents();
context->getNodeManager()->preProcessGraph();
}

Expand Down
Loading