Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .claude/skills/turbo-modules/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ Each `get*Function()` private method creates a `jsi::Function` via `jsi::Functio

**Adding a new top-level global**: add a `static jsi::Function getCreateXxxFunction(...)` private method and a `setProperty("createXxx", ...)` call in `injectJSIBindings`. This is only needed for objects that JS creates directly (not objects created as properties of another HostObject).

**Construction-time options** (e.g. `new AudioRecorder({ androidInputPreset, iosVoiceProcessing })`) travel as positional args of these factory functions — there is no options object and no TurboModule method involved. Three rules:
- Parse defensively — `if (count > N && args[N].isBool())` — and keep the no-arg behavior as the default, so an older JS bundle against a newer binary still works.
- Update `src/AudioAPIModule/globals.d.ts` in the same change; it is the only type contract for these globals.
- Platform-specific options are passed to *both* platforms and consumed by the `#ifdef ANDROID` branch in the HostObject constructor; the other platform ignores its counterpart. Name them with the platform prefix (`androidInputPreset`, `iosVoiceProcessing`) so the asymmetry is visible from JS.

---

## iOS Native Module (`AudioAPIModule.mm`)
Expand Down
5 changes: 4 additions & 1 deletion apps/common-app/src/singletons/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AudioContext, AudioRecorder } from 'react-native-audio-api';

export const audioContext = new AudioContext();
export const audioRecorder = new AudioRecorder();
export const audioRecorder = new AudioRecorder({
androidInputPreset: 'voiceCommunication',
iosVoiceProcessing: true,
});
39 changes: 20 additions & 19 deletions apps/fabric-example/ios/FabricExampleTests/AudioEngineTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,8 @@ - (void)testDetachSourceNodeRemovesTrackedNodeAndClearsGraphWhenEmpty {

- (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {
NSString *sourceNodeId = [self attachSourceNodeToAudioEngine];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachSourceNodeWithId:sourceNodeId];
Expand All @@ -434,8 +434,8 @@ - (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {

- (void)testAttachInputNodeStoresAndConnectsInput {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];

AVAudioSinkNode *inputNode = self.audioEngine.inputNode;
XCTAssertNotNil(inputNode);
Expand All @@ -454,8 +454,8 @@ - (void)testAttachInputNodeDefersConnectionUntilLiveInputFormatIsAvailable {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
fakeEngine.fakeInputNode.outputFormat = nil;

[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];

XCTAssertNil(self.audioEngine.inputNode);
XCTAssertEqual(fakeEngine.attachNodeCallCount, 0);
Expand All @@ -482,8 +482,8 @@ - (void)testDetachInputNodeWithoutInputDoesNothing {
}

- (void)testDetachInputNodeClearsGraphOnlyWhenNoSourcesRemain {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachInputNode];
Expand All @@ -492,8 +492,8 @@ - (void)testDetachInputNodeClearsGraphOnlyWhenNoSourcesRemain {
XCTAssertFalse(self.audioEngine.graphNeedsRebuild);

[self attachSourceNodeToAudioEngine];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachInputNode];
Expand All @@ -506,8 +506,8 @@ - (void)testDetachInputNodePreservesSessionDeactivationInvalidation {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
fakeEngine.fakeRunning = YES;
self.audioEngine.state = AudioEngineStateRunning;
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];

[self.audioEngine onSessionDeactivated];
[self.audioEngine detachInputNode];
Expand Down Expand Up @@ -729,8 +729,8 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {

- (void)
testStartIfNecessaryRebuildsAfterSessionDeactivationEvenWhenTeardownClearsGraph {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];

FakeAudioEngine *oldEngine = self.audioEngine.currentFakeAudioEngine;
oldEngine.fakeRunning = YES;
Expand All @@ -747,8 +747,8 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {
AVAudioFormat *recoveredInputFormat =
[self testInputFormatWithSampleRate:48000 channelCount:1];
self.audioEngine.nextCreatedEngineInputFormat = recoveredInputFormat;
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
AVAudioSinkNode *recoveredInputNode = self.audioEngine.inputNode;

XCTAssertTrue([self.audioEngine startIfNecessary]);
Expand All @@ -769,8 +769,8 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {
}

- (void)testStartIfNecessaryRebuildsInputNodeWithFreshInstance {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
FakeAudioEngine *oldEngine = self.audioEngine.currentFakeAudioEngine;
AVAudioSinkNode *oldInputNode = self.audioEngine.inputNode;
AVAudioFormat *replacementInputFormat =
Expand Down Expand Up @@ -991,7 +991,8 @@ - (void)testConcurrentRecordAndPlayPathsDoNotCrash {
for (NSInteger index = 0; index < 10; index += 1) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO];
[self.audioEngine startIfNecessary];
dispatch_group_leave(group);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@

[[nodiscard]] bool isRunning() const override;

[[nodiscard]] double getBaseLatency() const override;
[[nodiscard]] double getOutputLatency() const override;

protected:
std::shared_ptr<DSPAudioBuffer> audioBuffer_;
NativeAudioPlayer *audioPlayer_;
float sampleRate_;
std::function<void(DSPAudioBuffer *, int)> renderAudio_;
std::atomic<uint32_t> &currentRenders_;
int channelCount_;
Expand Down
Loading