Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
6792be9
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 27, 2026
fd7ac8f
feat: added copy and move ctors and assignment operators for AudioBuffer
Jan 27, 2026
b1caa8a
refactor: copy buffer in host object
Jan 27, 2026
17b0201
ci: lint
Jan 27, 2026
840331a
Merge branch 'main' into refactor/communication-between-audio-and-js-…
Jan 28, 2026
343b522
refactor: audio node
Jan 28, 2026
7033881
refactor: audio param
Jan 28, 2026
631dbe1
refactor: contexts
Jan 28, 2026
623776b
fix: nitpicks
Jan 28, 2026
d5ac6ee
refactor: template AudioDestructor
Jan 28, 2026
e6d8674
ci: format
Jan 28, 2026
b599f0c
fix: nitpick
Jan 28, 2026
1d78811
refactor: renamed audio node manager to audio graph manager
Jan 28, 2026
4bac3c4
refactor: buffer source wip
Jan 29, 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
Expand Up @@ -55,7 +55,7 @@ AndroidAudioRecorder::~AndroidAudioRecorder() {
}
}

if (mStream_) {
if (mStream_ != nullptr) {
mStream_->requestStop();
mStream_->close();
mStream_.reset();
Expand All @@ -68,7 +68,7 @@ AndroidAudioRecorder::~AndroidAudioRecorder() {
/// Callable from the JS thread only.
/// @returns Success status or Error status with message.
Result<NoneType, std::string> AndroidAudioRecorder::openAudioStream() {
if (mStream_) {
if (mStream_ != nullptr) {
return Result<NoneType, std::string>::Ok(None);
}

Expand Down Expand Up @@ -404,7 +404,7 @@ bool AndroidAudioRecorder::isIdle() const {
void AndroidAudioRecorder::cleanup() {
state_.store(RecorderState::Idle, std::memory_order_release);

if (mStream_) {
if (mStream_ != nullptr) {
mStream_->close();
mStream_.reset();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ AudioPlayer::AudioPlayer(
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio,
float sampleRate,
int channelCount)
: renderAudio_(renderAudio), sampleRate_(sampleRate), channelCount_(channelCount) {
: renderAudio_(renderAudio),
sampleRate_(sampleRate),
channelCount_(channelCount),
isRunning_(false) {
isInitialized_ = openAudioStream();
}

Expand All @@ -39,51 +42,60 @@ bool AudioPlayer::openAudioStream() {
return false;
}

mBus_ = std::make_shared<AudioBus>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate_);
audioBus_ = std::make_shared<AudioBus>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate_);
return true;
}

bool AudioPlayer::start() {
if (mStream_) {
auto result = mStream_->requestStart();
return result == oboe::Result::OK;
if (mStream_ != nullptr) {
auto result = mStream_->requestStart() == oboe::Result::OK;
isRunning_.store(result, std::memory_order_release);
return result;
}

return false;
}

void AudioPlayer::stop() {
if (mStream_) {
if (mStream_ != nullptr) {
isRunning_.store(false, std::memory_order_release);
mStream_->requestStop();
}
}

bool AudioPlayer::resume() {
if (mStream_) {
auto result = mStream_->requestStart();
return result == oboe::Result::OK;
if (isRunning()) {
return true;
}

if (mStream_ != nullptr) {
auto result = mStream_->requestStart() == oboe::Result::OK;
isRunning_.store(result, std::memory_order_release);
return result;
}

return false;
}

void AudioPlayer::suspend() {
if (mStream_) {
if (mStream_ != nullptr) {
isRunning_.store(false, std::memory_order_release);
mStream_->requestPause();
}
}

void AudioPlayer::cleanup() {
isInitialized_ = false;

if (mStream_) {
if (mStream_ != nullptr) {
mStream_->close();
mStream_.reset();
}
}

bool AudioPlayer::isRunning() const {
return mStream_ && mStream_->getState() == oboe::StreamState::Started;
return mStream_ && mStream_->getState() == oboe::StreamState::Started &&
isRunning_.load(std::memory_order_acquire);
}

DataCallbackResult
Expand All @@ -95,17 +107,19 @@ AudioPlayer::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numF
auto buffer = static_cast<float *>(audioData);
int processedFrames = 0;

assert(buffer != nullptr);

while (processedFrames < numFrames) {
int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);
renderAudio_(mBus_, framesToProcess);

// TODO: optimize this with SIMD?
if (isRunning_.load(std::memory_order_acquire)) {
renderAudio_(audioBus_, framesToProcess);
} else {
audioBus_->zero();
}

for (int i = 0; i < framesToProcess; i++) {
for (int channel = 0; channel < channelCount_; channel += 1) {
for (int channel = 0; channel < channelCount_; channel++) {
buffer[(processedFrames + i) * channelCount_ + channel] =
mBus_->getChannel(channel)->getData()[i];
audioBus_->getChannel(channel)->getData()[i];
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#include <oboe/Oboe.h>
#include <cassert>
#include <functional>
#include <memory>

Expand Down Expand Up @@ -45,10 +44,11 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback {
private:
std::function<void(std::shared_ptr<AudioBus>, int)> renderAudio_;
std::shared_ptr<AudioStream> mStream_;
std::shared_ptr<AudioBus> mBus_;
std::shared_ptr<AudioBus> audioBus_;
bool isInitialized_ = false;
float sampleRate_;
int channelCount_;
std::atomic<bool> isRunning_;

bool openAudioStream();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#include <audioapi/HostObjects/AudioNodeHostObject.h>

#include <audioapi/HostObjects/AudioParamHostObject.h>
#include <audioapi/core/AudioNode.h>
#include <audioapi/HostObjects/utils/JsEnumParser.h>

#include <memory>

namespace audioapi {

AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr<AudioNode> &node) : node_(node) {
AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr<AudioNode> &node) :
node_(node),
numberOfInputs_(node->getNumberOfInputs()),
numberOfOutputs_(node->getNumberOfOutputs()),
channelCount_(node->getChannelCount()),
channelCountMode_(node->getChannelCountMode()),
channelInterpretation_(node->getChannelInterpretation()) {
addGetters(
JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfInputs),
JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfOutputs),
Expand All @@ -26,23 +33,23 @@ AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr<AudioNode> &node)
AudioNodeHostObject::~AudioNodeHostObject() = default;

JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, numberOfInputs) {
return {node_->getNumberOfInputs()};
return numberOfInputs_;
}

JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, numberOfOutputs) {
return {node_->getNumberOfOutputs()};
return numberOfOutputs_;
}

JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCount) {
return {node_->getChannelCount()};
return channelCount_;
}

JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCountMode) {
return jsi::String::createFromUtf8(runtime, node_->getChannelCountMode());
return jsi::String::createFromUtf8(runtime, js_enum_parser::channelCountModeToString(channelCountMode_));
}

JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelInterpretation) {
return jsi::String::createFromUtf8(runtime, node_->getChannelInterpretation());
return jsi::String::createFromUtf8(runtime, js_enum_parser::channelInterpretationToString(channelInterpretation_));
}

JSI_HOST_FUNCTION_IMPL(AudioNodeHostObject, connect) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <audioapi/core/types/ChannelCountMode.h>
#include <audioapi/core/types/ChannelInterpretation.h>
#include <audioapi/jsi/JsiHostObject.h>

#include <jsi/jsi.h>
Expand Down Expand Up @@ -27,5 +29,11 @@ class AudioNodeHostObject : public JsiHostObject {

protected:
std::shared_ptr<AudioNode> node_;

int numberOfInputs_;
int numberOfOutputs_;
int channelCount_;
ChannelCountMode channelCountMode_;
ChannelInterpretation channelInterpretation_;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace audioapi {

AudioParamHostObject::AudioParamHostObject(const std::shared_ptr<AudioParam> &param)
: param_(param) {
: param_(param), defaultValue_(param->getDefaultValue()), minValue_(param->getMinValue()), maxValue_(param->getMaxValue()) {
addGetters(
JSI_EXPORT_PROPERTY_GETTER(AudioParamHostObject, value),
JSI_EXPORT_PROPERTY_GETTER(AudioParamHostObject, defaultValue),
Expand All @@ -31,47 +31,58 @@ JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, value) {
}

JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, defaultValue) {
return {param_->getDefaultValue()};
return {defaultValue_};
}

JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, minValue) {
return {param_->getMinValue()};
return {minValue_};
}

JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, maxValue) {
return {param_->getMaxValue()};
return {maxValue_};
}

JSI_PROPERTY_SETTER_IMPL(AudioParamHostObject, value) {
param_->setValue(static_cast<float>(value.getNumber()));
auto event = [param = param_, value = static_cast<float>(value.getNumber())](BaseAudioContext &) {
param->setValue(value);
};

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

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueAtTime) {
auto value = static_cast<float>(args[0].getNumber());
double startTime = args[1].getNumber();
param_->setValueAtTime(value, startTime);
auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), startTime = args[1].getNumber()](BaseAudioContext &) {
param->setValueAtTime(value, startTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, linearRampToValueAtTime) {
auto value = static_cast<float>(args[0].getNumber());
double endTime = args[1].getNumber();
param_->linearRampToValueAtTime(value, endTime);
auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), endTime = args[1].getNumber()](BaseAudioContext &) {
param->linearRampToValueAtTime(value, endTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, exponentialRampToValueAtTime) {
auto value = static_cast<float>(args[0].getNumber());
double endTime = args[1].getNumber();
param_->exponentialRampToValueAtTime(value, endTime);
auto event = [param = param_, value = static_cast<float>(args[0].getNumber()), endTime = args[1].getNumber()](BaseAudioContext &) {
param->exponentialRampToValueAtTime(value, endTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setTargetAtTime) {
auto target = static_cast<float>(args[0].getNumber());
double startTime = args[1].getNumber();
double timeConstant = args[2].getNumber();
param_->setTargetAtTime(target, startTime, timeConstant);
auto event = [param = param_, target = static_cast<float>(args[0].getNumber()), startTime = args[1].getNumber(), timeConstant = args[2].getNumber()](BaseAudioContext &) {
param->setTargetAtTime(target, startTime, timeConstant);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

Expand All @@ -80,24 +91,32 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueCurveAtTime) {
args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime);
auto rawValues = reinterpret_cast<float *>(arrayBuffer.data(runtime));
auto length = static_cast<int>(arrayBuffer.size(runtime));
auto values = std::make_unique<std::vector<float>>(rawValues, rawValues + length);
auto values = std::make_shared<std::vector<float>>(rawValues, rawValues + length);

double startTime = args[1].getNumber();
double duration = args[2].getNumber();
param_->setValueCurveAtTime(std::move(values), length, startTime, duration);
auto event = [param = param_, values, length, startTime = args[1].getNumber(), duration = args[2].getNumber()](BaseAudioContext &) {
param->setValueCurveAtTime(values, length, startTime, duration);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelScheduledValues) {
double cancelTime = args[0].getNumber();
param_->cancelScheduledValues(cancelTime);
auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) {
param->cancelScheduledValues(cancelTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelAndHoldAtTime) {
double cancelTime = args[0].getNumber();
param_->cancelAndHoldAtTime(cancelTime);
return jsi::Value::undefined();
auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) {
param->cancelAndHoldAtTime(cancelTime);
};

param_->scheduleAudioEvent(std::move(event));
return jsi::Value::undefined();
}

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ class AudioParamHostObject : public JsiHostObject {
friend class AudioNodeHostObject;

std::shared_ptr<AudioParam> param_;
float defaultValue_;
float minValue_;
float maxValue_;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <audioapi/HostObjects/BaseAudioContextHostObject.h>
#include <audioapi/HostObjects/WorkletNodeHostObject.h>
#include <audioapi/HostObjects/WorkletProcessingNodeHostObject.h>
#include <audioapi/HostObjects/effects/WorkletNodeHostObject.h>
#include <audioapi/HostObjects/effects/WorkletProcessingNodeHostObject.h>
#include <audioapi/HostObjects/analysis/AnalyserNodeHostObject.h>
#include <audioapi/HostObjects/destinations/AudioDestinationNodeHostObject.h>
#include <audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ size_t DelayNodeHostObject::getSizeInBytes() const {
}

JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) {
auto delayNode = std::static_pointer_cast<DelayNode>(node_);
auto delayTimeParam = std::make_shared<AudioParamHostObject>(delayNode->getDelayTimeParam());
return jsi::Object::createFromHostObject(runtime, delayTimeParam);
return jsi::Object::createFromHostObject(runtime, delayTimeParam_);
}

} // namespace audioapi
Loading