Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 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
3024c99
fix: convolver initialization
mdydek Apr 2, 2026
465ebe3
refactor: removed .hpp files
mdydek Apr 3, 2026
bbb909f
feat: delay node
mdydek Apr 7, 2026
ccd40a7
feat: enabling delay both nodes
mdydek Apr 17, 2026
6a71c9c
feat: channel count negotiations
mdydek Apr 17, 2026
e9168ab
Merge branch 'main' into feat/graph_refactor_2
mdydek Apr 20, 2026
0e3967e
feat: removed race conditions
mdydek Apr 21, 2026
0578d53
Merge branch 'main' into feat/graph_refactor_2
mdydek Apr 21, 2026
f9c7872
feat: tail time handling
mdydek Apr 21, 2026
ccda0ad
feat: fully working tail time
mdydek Apr 23, 2026
f0037aa
test: fix
mdydek Apr 23, 2026
58029c1
feat: remove recorder adapter from api
mdydek Apr 24, 2026
351657e
refactor: memory pressure
mdydek Apr 24, 2026
093a860
test: fix sync between gc and js queues
mdydek Apr 27, 2026
cd39a39
refactor: unnecesary code cleanup
mdydek Apr 27, 2026
07c148b
feat: allocation guard
mdydek Apr 28, 2026
0e77def
feat: convolver no allocations
mdydek Apr 29, 2026
44e1f87
Merge branch 'main' into feat/graph_refactor_2
mdydek Apr 29, 2026
4e23f14
feat: no allocations streamer
mdydek Apr 29, 2026
35a2305
feat: no allocations wave shaper
mdydek Apr 29, 2026
ebab432
Merge branch 'main' into feat/graph_refactor_2
mdydek May 4, 2026
d1f1e09
feat: removed inplace function
mdydek May 4, 2026
cd94921
feat: added necessary header
mdydek May 4, 2026
0c7abaa
feat: less strict alloc guard
mdydek May 5, 2026
122ccd0
Merge branch 'main' into feat/graph_refactor_2
mdydek May 14, 2026
ae8de4f
feat: more informative thread pool
mdydek May 20, 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
15 changes: 8 additions & 7 deletions apps/common-app/src/demos/PedalBoard/PedalBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
AudioBufferSourceNode,
AudioBuffer,
GainNode,
RecorderAdapterNode,
AudioRecorder,
AudioManager
} from 'react-native-audio-api';
Expand Down Expand Up @@ -31,7 +30,7 @@ export default function PedalBoard() {
const [isLoading, setIsLoading] = useState(false);
const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);

const sourceNodeRef = useRef<AudioBufferSourceNode | RecorderAdapterNode>(null);
const sourceNodeRef = useRef<AudioBufferSourceNode | AudioRecorder>(null);
const recorderRef = useRef<AudioRecorder | null>(null);
const pedalInputNodesRef = useRef<GainNode[]>([]);
const pedalOutputNodesRef = useRef<GainNode[]>([]);
Expand Down Expand Up @@ -84,7 +83,11 @@ export default function PedalBoard() {
return;
}
const sourceNode = sourceNodeRef.current;
sourceNode.connect(pedalInputNodesRef.current[0]);
if (sourceNode instanceof AudioRecorder) {
sourceNode.connect(audioContext, pedalInputNodesRef.current[0]);
} else {
sourceNode.connect(pedalInputNodesRef.current[0]);
}
for (let i = 0; i < PEDALS.length; i++) {
pedalInputNodesRef.current[i].connect(pedalOutputNodesRef.current[i]);
}
Expand Down Expand Up @@ -123,11 +126,9 @@ export default function PedalBoard() {
}
if (permissionsGranted) {
const recorder = new AudioRecorder();
const adapter = audioContext.createRecorderAdapter();
recorder.connect(adapter);
sourceNodeRef.current = recorder;
recorderRef.current = recorder;
sourceNodeRef.current = adapter;
sourceNodeRef.current.connect(pedalInputNodesRef.current[0]);
recorder.connect(audioContext, pedalInputNodesRef.current[0]);
if (audioContext.state === 'suspended') {
audioContext.resume();
}
Expand Down
2 changes: 1 addition & 1 deletion apps/common-app/src/demos/PedalBoard/ReverbPedal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function ReverbPedal({
} else if (level < 0.66) {
desiredDuration = 0.7;
} else {
desiredDuration = 1;
desiredDuration = 5;
}
if (convolverNodeRef.current?.buffer) {
if (convolverNodeRef.current.buffer.duration === desiredDuration) {
Expand Down
4 changes: 4 additions & 0 deletions apps/common-app/src/examples/AudioFile/AudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class AudioPlayer {
});
this.sourceNode.buffer = this.audioBuffer;
this.sourceNode.playbackRate.value = this.playbackRate;
const volume1 = this.audioContext.createGain();
const volume2 = this.audioContext.createGain();
volume1.gain.value = 0.5;
volume2.gain.value = 0.5;
this.volumeNode = this.audioContext.createGain();
this.volumeNode.gain.value = this.volume;

Expand Down
4 changes: 1 addition & 3 deletions apps/common-app/src/examples/Record/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ const Record: FC = () => {
return;
}

const adapter = audioContext.createRecorderAdapter();
adapter.connect(audioContext.destination);
audioRecorder.connect(adapter);
audioRecorder.connect(audioContext, audioContext.destination);

const result = audioRecorder.start();

Expand Down
6 changes: 5 additions & 1 deletion packages/audiodocs/docs/core/base-audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ Creates [`PeriodicWave`](/docs/effects/periodic-wave). This waveform specifies a

### `createRecorderAdapter`

Creates [`RecorderAdapterNode`](/docs/sources/recorder-adapter-node).
Creates a `RecorderAdapterNode` — an [`AudioNode`](/docs/core/audio-node) that
adapts an [`AudioRecorder`](/docs/inputs/audio-recorder) into the audio graph.
Feed it using [`AudioRecorder.connect`](/docs/inputs/audio-recorder#connect) and
connect it downstream like any other node. Produces silence until a recorder is
connected to it.

#### Returns `RecorderAdapterNode`

Expand Down
13 changes: 4 additions & 9 deletions packages/audiodocs/docs/guides/create-your-own-effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ public:
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 @@ -79,14 +77,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
14 changes: 10 additions & 4 deletions packages/audiodocs/docs/inputs/audio-recorder.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,7 @@ const MyRecorder: React.FC = () => {
return;
}

const adapter = audioContext.createRecorderAdapter();
adapter.connect(audioContext.destination);
audioRecorder.connect(adapter);
audioRecorder.connect(audioContext, audioContext.destination);

if (audioContext.state === 'suspended') {
await audioContext.resume();
Expand Down Expand Up @@ -638,11 +636,19 @@ export default MyRecorder;
</div></td>
<td>
<div class="text-content">
Connects AudioRecorder with [RecorderAdapterNode](/docs/sources/recorder-adapter-node) instance that can be used for further audio processing.
Routes captured audio into an audio graph. Two forms are supported:

1. `connect(adapter)` — connects the recorder to an adapter node you created yourself with `audioContext.createRecorderAdapter()`. Each adapter can only be connected once.
2. `connect(context, destination)` — convenience form that creates the adapter node internally, wires the recorder to it, connects it to `destination`, and returns the adapter so you can `disconnect()` it later if needed.
</div>
```tsx
// Form 1: explicit adapter
const adapter = audioContext.createRecorderAdapter();
audioRecorder.connect(adapter);
adapter.connect(audioContext.destination);

// Form 2: convenience — adapter created under the hood
const adapter = audioRecorder.connect(audioContext, audioContext.destination);
```
</td>
</tr>
Expand Down
9 changes: 0 additions & 9 deletions packages/audiodocs/docs/other/testing.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Expand Down Expand Up @@ -99,13 +97,6 @@ describe('Audio Recording Tests', () => {

expect(result.status).toBe('success');

// Set up recording chain
const oscillator = context.createOscillator();
const recorderAdapter = context.createRecorderAdapter();

oscillator.connect(recorderAdapter);
recorder.connect(recorderAdapter);

// Test recording workflow
const startResult = recorder.start();
expect(startResult.status).toBe('success');
Expand Down
49 changes: 0 additions & 49 deletions packages/audiodocs/docs/sources/recorder-adapter-node.mdx

This file was deleted.

7 changes: 3 additions & 4 deletions packages/audiodocs/docs/worklets/worklet-node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,14 @@ async function App() {
// !VERY IMPORTANT: please read the Known Issue section below
};
const workletNode = audioContext.createWorkletNode(worklet, 1024, 2, 'UIRuntime');
const adapterNode = audioContext.createRecorderAdapter();

const canSetAudioSessionActivity = await AudioManager.setAudioSessionActivity(true);
if (!canSetAudioSessionActivity) {
throw new Error("Could not activate the audio session");
}
adapterNode.connect(workletNode);
workletNode.connect(audioContext.destination);
recorder.connect(adapterNode);
recorder
.connect(audioContext, workletNode)
.connect(audioContext.destination);
recorder.start();
audioContext.resume();
}
Expand Down
8 changes: 3 additions & 5 deletions packages/audiodocs/docs/worklets/worklet-processing-node.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,12 @@ function App() {
gainWorklet,
'AudioRuntime'
);
const adapterNode = audioContext.createRecorderAdapter();

adapterNode.connect(workletProcessingNode);
workletProcessingNode.connect(audioContext.destination);
recorder.connect(adapterNode);
recorder
.connect(audioContext, workletProcessingNode)
.connect(audioContext.destination);
recorder.start();
}
}
```

## Worklet Parameters Explanation
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
3 changes: 2 additions & 1 deletion packages/react-native-audio-api/RNAudioAPI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/software-mansion/react-native-audio-api.git", :tag => "#{s.version}" }

s.subspec "audioapi" do |ss|
ss.source_files = "common/cpp/audioapi/**/*.{cpp,c,h,hpp}"
ss.source_files = "common/cpp/audioapi/**/*.{cpp,c,h,hpp}", "common/cpp/test/src/graph/AudioThreadGuard.cpp"
ss.exclude_files = $RN_AUDIO_API_FFMPEG_DISABLED ? ["common/cpp/audioapi/libs/ffmpeg/**"] : []
ss.header_dir = "audioapi"
ss.header_mappings_dir = "common/cpp/audioapi"
Expand Down Expand Up @@ -103,6 +103,7 @@ Pod::Spec.new do |s|
.join(' '),
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) HAVE_ACCELERATE=1',
"GCC_PREPROCESSOR_DEFINITIONS[config=Debug]" => '$(inherited) HAVE_ACCELERATE=1 DEBUG=1',
'OTHER_CFLAGS' => "$(inherited) #{fabric_flags} #{version_flag} #{worklets_preprocessor_flag} #{ffmpeg_flag} #{static_external_libs_flag}",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ if (RN_AUDIO_API_FFMPEG_DISABLED)
)
endif()

target_compile_definitions(
react-native-audio-api
PRIVATE $<$<CONFIG:Debug>:DEBUG>
)

if (RN_AUDIO_API_STATIC_EXTERNAL_LIBS_DISABLED)
target_compile_definitions(
react-native-audio-api
Expand Down
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checking in
is it possible to call start and stop concurently (eg at the same time)?

Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ 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;
isConnected_.store(true, std::memory_order_release);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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 @@ -86,7 +83,7 @@ void AudioPlayer::suspend() {
}

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

if (mStream_ != nullptr) {
mStream_->close();
Expand All @@ -101,7 +98,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 @@ -112,7 +109,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 @@ -130,7 +127,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
Loading