Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8274a95
feat: added base GraphObject
poneciak57 Mar 19, 2026
b4625aa
feat: integrated new memory disposer
maciejmakowski2003 Mar 19, 2026
a010057
feat: added bridge node
poneciak57 Mar 20, 2026
d9d1791
fix: changed from shared_ptr to using unique_ptr for singly owned com…
poneciak57 Mar 23, 2026
fa4d2f0
Merge branch 'feat/graph_refactor_2' into feat/integrate-new-memory-d…
poneciak57 Mar 23, 2026
52d274d
feat: moved disposer out of graph class
poneciak57 Mar 23, 2026
92b9b3c
Merge branch 'main' into feat/graph_refactor_2
maciejmakowski2003 Mar 24, 2026
c607534
refactor: template nitpick
maciejmakowski2003 Mar 24, 2026
971bf36
refactor: update AudioNodeHostObject to use Graph and HostNode
maciejmakowski2003 Mar 25, 2026
8efcef4
test: fix tests to use std::make_shared instead of context factory me…
maciejmakowski2003 Mar 25, 2026
7d2d876
refactor: nitpicks
maciejmakowski2003 Mar 25, 2026
2a525b9
refactor: first part of integration
maciejmakowski2003 Mar 25, 2026
98e76f2
ci: yarn format
maciejmakowski2003 Mar 25, 2026
08a5ee2
refactor: added separate input and output buffers getters to satisfy …
maciejmakowski2003 Mar 26, 2026
3265b23
refactor: destination ownership and connection logic
maciejmakowski2003 Mar 26, 2026
f151efa
ci: yarn format
maciejmakowski2003 Mar 26, 2026
bc8c628
refactor: disconnect all outputs from a node
maciejmakowski2003 Mar 26, 2026
ebffe07
refactor: destination node initialization and ownership
maciejmakowski2003 Mar 26, 2026
bd4637c
ci: yarn format
maciejmakowski2003 Mar 26, 2026
c9cfa78
refactor: added param owner node
maciejmakowski2003 Mar 26, 2026
9b984c5
fix: fixed my processor node implementation in the guide and template
maciejmakowski2003 Mar 26, 2026
b6adf6c
refactor: removed isInitialized and added overrides for canBeDestructed
maciejmakowski2003 Mar 26, 2026
22c5b5f
feat: bridge audio param to graph
poneciak57 Mar 28, 2026
5994a64
Merge branch 'main' into feat/graph_refactor_2
maciejmakowski2003 Mar 30, 2026
a7a0fc4
ci: yarn format
maciejmakowski2003 Mar 30, 2026
43a7395
fix: fixed removeAllEdges
maciejmakowski2003 Mar 30, 2026
2952e5e
refactor: cleanup
maciejmakowski2003 Mar 31, 2026
1888507
fix: nitpicks
maciejmakowski2003 Mar 31, 2026
88f506c
refactor: lazy bridge node creation for AudioParamHostObject
maciejmakowski2003 Mar 31, 2026
4db7657
refactor: trigger disposing of ghost nodes on context state changes
maciejmakowski2003 Mar 31, 2026
f12fb88
refactor: bring back recorder connect with recorder adapter
maciejmakowski2003 Mar 31, 2026
a985577
ci: format
maciejmakowski2003 Mar 31, 2026
d7f53d1
refactor: implemented disable() for AudioBufferSourceNode to dispose …
maciejmakowski2003 Mar 31, 2026
cb9d312
fix: fixed allocation when adding nodes
poneciak57 Apr 2, 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
6 changes: 3 additions & 3 deletions apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2514,7 +2514,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da
hermes-engine: 471e81260adadffc041e40c5eea01333addabb53
hermes-engine: ca0c1d4fe0200e05fedd8d7c0c283b54cd461436
RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7
RCTRequired: bb77b070f75f53398ce43c0aaaa58337cebe2bf6
RCTSwiftUI: afc0a0a635860da1040a0b894bfd529da06d7810
Expand All @@ -2523,7 +2523,7 @@ SPEC CHECKSUMS:
React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b
React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b
React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f
React-Core-prebuilt: 6586031f606ff8ab466cac9e8284053a91342881
React-Core-prebuilt: e44365cf4785c3aa56ababc9ab204fe8bc6b17d0
React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42
React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe
React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc
Expand Down Expand Up @@ -2587,7 +2587,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: e96e93b493d8d86eeaee3e590ba0be53f6abe46f
ReactCodegen: f66521b131699d6af0790f10653933b3f1f79a6f
ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e
ReactNativeDependencies: a5d71d95f2654107eb45e6ece04caba36beac2bd
ReactNativeDependencies: 3467a1fea6f7a524df13b30430bebcc254d9aee2
RNAudioAPI: fa5c075d2fcdb1ad9a695754b38f07c8c3074396
RNGestureHandler: 07de6f059e0ee5744ae9a56feb07ee345338cc31
RNReanimated: d75c81956bf7531fe08ba4390149002ab8bdd127
Expand Down
15 changes: 5 additions & 10 deletions packages/audiodocs/docs/guides/create-your-own-effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,10 @@ namespace audioapi {

class MyProcessorNode : public AudioNode {
public:
explicit MyProcessorNode(const std::shared_ptr<BaseAudioContext> &context, );
explicit MyProcessorNode(const std::shared_ptr<BaseAudioContext> &context);

protected:
std::shared_ptr<DSPAudioBuffer>
processNode(const std::shared_ptr<DSPAudioBuffer> &buffer,
int framesToProcess) override;
void processNode(int framesToProcess) override;

// highlight-start
private:
Expand All @@ -76,14 +74,11 @@ private:
namespace audioapi {
MyProcessorNode::MyProcessorNode(const std::shared_ptr<BaseAudioContext> &context)
//highlight-next-line
: AudioNode(context), gain(0.5) {
isInitialized_.store(true, std::memory_order_release);
}
: AudioNode(context), gain(0.5) {}

std::shared_ptr<DSPAudioBuffer> MyProcessorNode::processNode(const std::shared_ptr<DSPAudioBuffer> &buffer,
int framesToProcess) {
void MyProcessorNode::processNode(int framesToProcess) {
// highlight-start
for (int channel = 0; channel < buffer->getNumberOfChannels(); ++channel) {
for (int channel = 0; channel < audioBuffer_->getNumberOfChannels(); ++channel) {
auto *audioArray = bus->getChannel(channel);
for (size_t i = 0; i < framesToProcess; ++i) {
// Apply gain to each sample in the audio array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
namespace audioapi {
MyProcessorNode::MyProcessorNode(
const std::shared_ptr<BaseAudioContext> &context)
: AudioNode(context) {
isInitialized_.store(true, std::memory_order_release);
}
: AudioNode(context) {}

std::shared_ptr<DSPAudioBuffer>
MyProcessorNode::processNode(const std::shared_ptr<DSPAudioBuffer> &buffer,
int framesToProcess) {
void MyProcessorNode::processNode(int framesToProcess) {
// put your processing logic here
}
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ class MyProcessorNode : public AudioNode {
explicit MyProcessorNode(const std::shared_ptr<BaseAudioContext> &context);

protected:
std::shared_ptr<DSPAudioBuffer>
processNode(const std::shared_ptr<DSPAudioBuffer> &buffer,
int framesToProcess) override;
void processNode(int framesToProcess) override;
};
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ AndroidAudioRecorder::~AndroidAudioRecorder() {

if (isConnected()) {
isConnected_.store(false, std::memory_order_release);
adapterNode_->adapterCleanup();
static_cast<RecorderAdapterNode *>(adapterNodeHandle_->audioNode.get())->adapterCleanup();
}
}

Expand Down Expand Up @@ -143,7 +143,8 @@ Result<std::string, std::string> AndroidAudioRecorder::start(const std::string &
if (isConnected()) {
deinterleavingBuffer_ = std::make_shared<AudioBuffer>(
streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
adapterNode_->init(streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
static_cast<RecorderAdapterNode *>(adapterNodeHandle_->audioNode.get())
->init(streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
}

auto result = mStream_->requestStart();
Expand Down Expand Up @@ -198,7 +199,7 @@ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorde
}

if (isConnected()) {
adapterNode_->adapterCleanup();
static_cast<RecorderAdapterNode *>(adapterNodeHandle_->audioNode.get())->adapterCleanup();
}

filePath_ = "";
Expand Down Expand Up @@ -318,14 +319,15 @@ void AndroidAudioRecorder::clearOnAudioReadyCallback() {
/// If the recorder is already active, it will initialize the adapter node immediately.
/// This method should be called from the JS thread only.
/// @param node Shared pointer to the RecorderAdapterNode to connect.
void AndroidAudioRecorder::connect(const std::shared_ptr<RecorderAdapterNode> &node) {
void AndroidAudioRecorder::connect(const std::shared_ptr<utils::graph::NodeHandle> &node) {
std::scoped_lock adapterLock(adapterNodeMutex_);
adapterNode_ = node;
adapterNodeHandle_ = node;

if (!isIdle()) {
deinterleavingBuffer_ = std::make_shared<AudioBuffer>(
streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
adapterNode_->init(streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
static_cast<RecorderAdapterNode *>(adapterNodeHandle_->audioNode.get())
->init(streamMaxBufferSizeInFrames_, streamChannelCount_, streamSampleRate_);
}

isConnected_.store(true, std::memory_order_release);
Expand All @@ -338,7 +340,7 @@ void AndroidAudioRecorder::disconnect() {
std::scoped_lock adapterLock(adapterNodeMutex_);
isConnected_.store(false, std::memory_order_release);
deinterleavingBuffer_ = nullptr;
adapterNode_ = nullptr;
adapterNodeHandle_ = nullptr;
}

/// @brief onAudioReady callback that is invoked by the Oboe stream when new audio data is available.
Expand Down Expand Up @@ -376,9 +378,10 @@ oboe::DataCallbackResult AndroidAudioRecorder::onAudioReady(
if (auto adapterLock = Locker::tryLock(adapterNodeMutex_)) {
auto const data = static_cast<float *>(audioData);
deinterleavingBuffer_->deinterleaveFrom(data, numFrames);
auto *adapterNode = static_cast<RecorderAdapterNode *>(adapterNodeHandle_->audioNode.get());

for (size_t ch = 0; ch < streamChannelCount_; ++ch) {
adapterNode_->buff_[ch]->write(*deinterleavingBuffer_->getChannel(ch), numFrames);
adapterNode->buff_[ch]->write(*deinterleavingBuffer_->getChannel(ch), numFrames);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AndroidAudioRecorder : public oboe::AudioStreamCallback, public AudioRecor
uint64_t callbackId) override;
void clearOnAudioReadyCallback() override;

void connect(const std::shared_ptr<RecorderAdapterNode> &node) override;
void connect(const std::shared_ptr<utils::graph::NodeHandle> &node) override;
void disconnect() override;

oboe::DataCallbackResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
namespace audioapi {

AudioPlayer::AudioPlayer(
const std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> &renderAudio,
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount)
: renderAudio_(renderAudio),
sampleRate_(sampleRate),
channelCount_(channelCount),
isRunning_(false) {
isInitialized_ = openAudioStream();
: renderAudio_(renderAudio), sampleRate_(sampleRate), channelCount_(channelCount) {
isInitialized_.store(openAudioStream(), std::memory_order_release);
}

bool AudioPlayer::openAudioStream() {
Expand Down Expand Up @@ -85,7 +82,7 @@ void AudioPlayer::suspend() {
}

void AudioPlayer::cleanup() {
isInitialized_ = false;
isInitialized_.store(false, std::memory_order_release);

if (mStream_ != nullptr) {
mStream_->close();
Expand All @@ -100,7 +97,7 @@ bool AudioPlayer::isRunning() const {

DataCallbackResult
AudioPlayer::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
if (!isInitialized_) {
if (!isInitialized_.load(std::memory_order_acquire)) {
return DataCallbackResult::Continue;
}

Expand All @@ -111,7 +108,7 @@ AudioPlayer::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numF
auto framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);

if (isRunning_.load(std::memory_order_acquire)) {
renderAudio_(buffer_, framesToProcess);
renderAudio_(buffer_.get(), framesToProcess);
} else {
buffer_->zero();
}
Expand All @@ -129,7 +126,7 @@ void AudioPlayer::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result erro
if (error == oboe::Result::ErrorDisconnected) {
cleanup();
if (openAudioStream()) {
isInitialized_ = true;
isInitialized_.store(true, std::memory_order_release);
resume();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AudioContext;
class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback {
public:
AudioPlayer(
const std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> &renderAudio,
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount);

Expand All @@ -40,13 +40,13 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback {
void onErrorAfterClose(AudioStream * /* audioStream */, Result /* error */) override;

private:
std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> renderAudio_;
std::function<void(DSPAudioBuffer *, int)> renderAudio_;
std::shared_ptr<AudioStream> mStream_;
std::shared_ptr<DSPAudioBuffer> buffer_;
bool isInitialized_ = false;
float sampleRate_;
int channelCount_;
std::atomic<bool> isRunning_;
std::atomic<bool> isInitialized_{false};
std::atomic<bool> isRunning_{false};

bool openAudioStream();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ AudioContextHostObject::AudioContextHostObject(
}

JSI_HOST_FUNCTION_IMPL(AudioContextHostObject, close) {
context_->getGraph()->collectDisposedNodes();
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
auto promise = promiseVendor_->createAsyncPromise([audioContext = std::move(audioContext)]() {
return [audioContext](jsi::Runtime &runtime) {
Expand All @@ -35,6 +36,7 @@ JSI_HOST_FUNCTION_IMPL(AudioContextHostObject, close) {
}

JSI_HOST_FUNCTION_IMPL(AudioContextHostObject, resume) {
context_->getGraph()->collectDisposedNodes();
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
auto promise = promiseVendor_->createAsyncPromise([audioContext = std::move(audioContext)]() {
auto result = audioContext->resume();
Expand All @@ -46,6 +48,7 @@ JSI_HOST_FUNCTION_IMPL(AudioContextHostObject, resume) {
}

JSI_HOST_FUNCTION_IMPL(AudioContextHostObject, suspend) {
context_->getGraph()->collectDisposedNodes();
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
auto promise = promiseVendor_->createAsyncPromise([audioContext = std::move(audioContext)]() {
auto result = audioContext->suspend();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
#include <audioapi/HostObjects/AudioNodeHostObject.h>
#include <audioapi/HostObjects/AudioParamHostObject.h>
#include <audioapi/HostObjects/destinations/AudioDestinationNodeHostObject.h>
#include <audioapi/HostObjects/utils/JsEnumParser.h>
#include <audioapi/core/AudioNode.h>

#include <memory>
#include <utility>

namespace audioapi {

AudioNodeHostObject::AudioNodeHostObject(
const std::shared_ptr<AudioNode> &node,
const std::shared_ptr<utils::graph::Graph> &graph,
std::unique_ptr<AudioNode> node,
const AudioNodeOptions &options)
: node_(node),
: utils::graph::HostNode(graph, std::move(node)),
numberOfInputs_(options.numberOfInputs),
numberOfOutputs_(options.numberOfOutputs),
channelCount_(options.channelCount),
Expand Down Expand Up @@ -60,30 +63,31 @@ JSI_HOST_FUNCTION_IMPL(AudioNodeHostObject, connect) {
auto obj = args[0].getObject(runtime);
if (obj.isHostObject<AudioNodeHostObject>(runtime)) {
auto node = obj.getHostObject<AudioNodeHostObject>(runtime);
node_->connect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
}
if (obj.isHostObject<AudioParamHostObject>(runtime)) {
connect(*node);
} else if (obj.isHostObject<AudioParamHostObject>(runtime)) {
auto param = obj.getHostObject<AudioParamHostObject>(runtime);
node_->connect(std::shared_ptr<AudioParamHostObject>(param)->param_);
param->connectToGraph();
graph_->addEdge(node_, param->bridgeNode());
}
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION_IMPL(AudioNodeHostObject, disconnect) {
if (args[0].isUndefined()) {
node_->disconnect();
disconnect();
return jsi::Value::undefined();
}

auto obj = args[0].getObject(runtime);
if (obj.isHostObject<AudioNodeHostObject>(runtime)) {
auto node = obj.getHostObject<AudioNodeHostObject>(runtime);
node_->disconnect(std::shared_ptr<AudioNodeHostObject>(node)->node_);
}

if (obj.isHostObject<AudioParamHostObject>(runtime)) {
disconnect(*node);
} else if (obj.isHostObject<AudioParamHostObject>(runtime)) {
auto param = obj.getHostObject<AudioParamHostObject>(runtime);
node_->disconnect(std::shared_ptr<AudioParamHostObject>(param)->param_);
// Disconnect source → bridge
graph_->removeEdge(node_, param->bridgeNode());
}

return jsi::Value::undefined();
}
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <audioapi/core/types/ChannelCountMode.h>
#include <audioapi/core/types/ChannelInterpretation.h>
#include <audioapi/core/utils/graph/Graph.hpp>
#include <audioapi/core/utils/graph/HostNode.hpp>
#include <audioapi/jsi/JsiHostObject.h>
#include <audioapi/types/NodeOptions.h>

Expand All @@ -13,10 +15,11 @@ using namespace facebook;

class AudioNode;

class AudioNodeHostObject : public JsiHostObject {
class AudioNodeHostObject : public JsiHostObject, public utils::graph::HostNode {
public:
explicit AudioNodeHostObject(
const std::shared_ptr<AudioNode> &node,
const std::shared_ptr<utils::graph::Graph> &graph,
std::unique_ptr<AudioNode> node,
const AudioNodeOptions &options = AudioNodeOptions());
~AudioNodeHostObject() override;

Expand All @@ -26,12 +29,13 @@ class AudioNodeHostObject : public JsiHostObject {
JSI_PROPERTY_GETTER_DECL(channelCountMode);
JSI_PROPERTY_GETTER_DECL(channelInterpretation);

using utils::graph::HostNode::connect;
using utils::graph::HostNode::disconnect;

JSI_HOST_FUNCTION_DECL(connect);
JSI_HOST_FUNCTION_DECL(disconnect);

protected:
std::shared_ptr<AudioNode> node_;

const int numberOfInputs_;
const int numberOfOutputs_;
size_t channelCount_;
Expand Down
Loading
Loading