From d74dee660ca08a6f8487769d4fed7238a642fb90 Mon Sep 17 00:00:00 2001 From: michal Date: Thu, 13 Nov 2025 18:12:41 +0100 Subject: [PATCH 01/16] feat: gain impl --- .../BaseAudioContextHostObject.cpp | 7 ++- .../audioapi/HostObjects/utils/NodeOptions.h | 18 ++++++ .../HostObjects/utils/NodeOptionsParser.h | 63 +++++++++++++++++++ .../common/cpp/audioapi/core/AudioNode.cpp | 11 +++- .../common/cpp/audioapi/core/AudioNode.h | 3 +- .../cpp/audioapi/core/BaseAudioContext.cpp | 6 +- .../cpp/audioapi/core/BaseAudioContext.h | 3 +- .../cpp/audioapi/core/effects/GainNode.cpp | 11 +++- .../cpp/audioapi/core/effects/GainNode.h | 3 +- .../src/core/BaseAudioContext.ts | 2 +- .../src/core/GainNode.ts | 13 +++- .../react-native-audio-api/src/defaults.ts | 12 ++++ .../react-native-audio-api/src/interfaces.ts | 3 +- packages/react-native-audio-api/src/types.ts | 10 +++ 14 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h create mode 100644 packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h create mode 100644 packages/react-native-audio-api/src/defaults.ts diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 0f7565926..7c6c64182 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -19,6 +19,8 @@ #include #include +#include + namespace audioapi { BaseAudioContextHostObject::BaseAudioContextHostObject( @@ -184,7 +186,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createConstantSource) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createGain) { - auto gain = context_->createGain(); + auto object = args[0].asObject(runtime); + GainOptions options = + audioapi::option_parser::parseGainOptions(runtime, object); + auto gain = context_->createGain(std::make_shared(options)); auto gainHostObject = std::make_shared(gain); return jsi::Object::createFromHostObject(runtime, gainHostObject); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h new file mode 100644 index 000000000..64fb7ea32 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace audioapi { +struct AudioNodeOptions { + int channelCount; + ChannelCountMode channelCountMode; + ChannelInterpretation channelInterpretation; +}; + +struct GainOptions : AudioNodeOptions { + float gain; + explicit GainOptions(AudioNodeOptions nodeOptions) + : AudioNodeOptions(nodeOptions) {} +}; +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h new file mode 100644 index 000000000..a5a50fdb3 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace audioapi::option_parser { + std::shared_ptr parseAudioNodeOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject + ) { + AudioNodeOptions options; + + options.channelCount = + static_cast(optionsObject + .getProperty(runtime, "channelCount") + .getNumber()); + + auto channelCountModeStr = optionsObject + .getProperty(runtime, "channelCountMode") + .asString(runtime) + .utf8(runtime); + + if (channelCountModeStr == "max") { + options.channelCountMode = ChannelCountMode::MAX; + } else if (channelCountModeStr == "clamped-max") { + options.channelCountMode = ChannelCountMode::CLAMPED_MAX; + } else if (channelCountModeStr == "explicit") { + options.channelCountMode = ChannelCountMode::EXPLICIT; + } + + auto channelInterpretationStr = optionsObject + .getProperty(runtime, "channelInterpretation") + .asString(runtime) + .utf8(runtime); + + if (channelInterpretationStr == "speakers") { + options.channelInterpretation = ChannelInterpretation::SPEAKERS; + } else if (channelInterpretationStr == "discrete") { + options.channelInterpretation = ChannelInterpretation::DISCRETE; + } + + return std::make_shared(options); + } + + GainOptions parseGainOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); + GainOptions options(*nodeOptions.get()); + options.gain = static_cast(optionsObject + .getProperty(runtime, "gain") + .getNumber()); + return options; + } +} // namespace audioapi::option_parser + + diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp index dcc9d34a3..40f15b609 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,7 +8,15 @@ namespace audioapi { -AudioNode::AudioNode(BaseAudioContext *context) : context_(context) { +AudioNode::AudioNode( + BaseAudioContext *context, + std::shared_ptr options) + : context_(context) { + if (options != nullptr) { + channelCount_ = options->channelCount; + channelCountMode_ = options->channelCountMode; + channelInterpretation_ = options->channelInterpretation; + } audioBus_ = std::make_shared( RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h index 4b726ff1b..5f276e667 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h @@ -16,10 +16,11 @@ namespace audioapi { class AudioBus; class BaseAudioContext; class AudioParam; +class AudioNodeOptions; class AudioNode : public std::enable_shared_from_this { public: - explicit AudioNode(BaseAudioContext *context); + explicit AudioNode(BaseAudioContext *context, std::shared_ptr options = nullptr); virtual ~AudioNode(); int getNumberOfInputs() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index fa05c13e2..1ddca0845 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -128,8 +129,9 @@ std::shared_ptr BaseAudioContext::createStreamer() { } #endif -std::shared_ptr BaseAudioContext::createGain() { - auto gain = std::make_shared(this); +std::shared_ptr BaseAudioContext::createGain( + std::shared_ptr options) { + auto gain = std::make_shared(this, options); nodeManager_->addProcessingNode(gain); return gain; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index eba8b3460..2aa92f9dd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -35,6 +35,7 @@ class WorkletSourceNode; class WorkletNode; class WorkletProcessingNode; class StreamerNode; +class GainOptions; class BaseAudioContext { public: @@ -65,7 +66,7 @@ class BaseAudioContext { std::shared_ptr createOscillator(); std::shared_ptr createConstantSource(); std::shared_ptr createStreamer(); - std::shared_ptr createGain(); + std::shared_ptr createGain(std::shared_ptr options); std::shared_ptr createStereoPanner(); std::shared_ptr createBiquadFilter(); std::shared_ptr createBufferSource(bool pitchCorrection); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp index 166def7ec..1608ec3e5 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,9 +7,15 @@ namespace audioapi { -GainNode::GainNode(BaseAudioContext *context) : AudioNode(context) { +GainNode::GainNode( + BaseAudioContext *context, + std::shared_ptr options) + : AudioNode(context, std::static_pointer_cast(options)) { gainParam_ = std::make_shared( - 1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); + options->gain, + MOST_NEGATIVE_SINGLE_FLOAT, + MOST_POSITIVE_SINGLE_FLOAT, + context); isInitialized_ = true; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h index 7dcdf0c40..0ed3ec810 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h @@ -8,10 +8,11 @@ namespace audioapi { class AudioBus; +class GainOptions; class GainNode : public AudioNode { public: - explicit GainNode(BaseAudioContext *context); + explicit GainNode(BaseAudioContext *context, std::shared_ptr options); [[nodiscard]] std::shared_ptr getGainParam() const; diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 86938d40c..a5a0bc273 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -193,7 +193,7 @@ export default class BaseAudioContext { } createGain(): GainNode { - return new GainNode(this, this.context.createGain()); + return new GainNode(this); } createStereoPanner(): StereoPannerNode { diff --git a/packages/react-native-audio-api/src/core/GainNode.ts b/packages/react-native-audio-api/src/core/GainNode.ts index a216f1365..65a9ead68 100644 --- a/packages/react-native-audio-api/src/core/GainNode.ts +++ b/packages/react-native-audio-api/src/core/GainNode.ts @@ -1,4 +1,6 @@ import { IGainNode } from '../interfaces'; +import { GainOptions } from '../defaults'; +import { TGainOptions } from '../types'; import AudioNode from './AudioNode'; import AudioParam from './AudioParam'; import BaseAudioContext from './BaseAudioContext'; @@ -6,8 +8,13 @@ import BaseAudioContext from './BaseAudioContext'; export default class GainNode extends AudioNode { readonly gain: AudioParam; - constructor(context: BaseAudioContext, gain: IGainNode) { - super(context, gain); - this.gain = new AudioParam(gain.gain, context); + constructor(context: BaseAudioContext, gainOptions?: TGainOptions) { + const finalOptions: TGainOptions = { + ...GainOptions, + ...gainOptions, + }; + const gainNode: IGainNode = context.context.createGain(finalOptions); + super(context, gainNode); + this.gain = new AudioParam(gainNode.gain, context); } } diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts new file mode 100644 index 000000000..d3eb746df --- /dev/null +++ b/packages/react-native-audio-api/src/defaults.ts @@ -0,0 +1,12 @@ +import { TAudioNodeOptions, TGainOptions } from './types'; + +export const AudioNodeOptions: TAudioNodeOptions = { + channelCount: 2, + channelCountMode: 'max', + channelInterpretation: 'speakers', +}; + +export const GainOptions: TGainOptions = { + ...AudioNodeOptions, + gain: 1, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 8133ddcfd..b49891e7c 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -6,6 +6,7 @@ import { ContextState, OscillatorType, WindowType, + TGainOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -59,7 +60,7 @@ export interface IBaseAudioContext { ): IWorkletProcessingNode; createOscillator(): IOscillatorNode; createConstantSource(): IConstantSourceNode; - createGain(): IGainNode; + createGain(gainOptions: TGainOptions): IGainNode; createStereoPanner(): IStereoPannerNode; createBiquadFilter: () => IBiquadFilterNode; createBufferSource: (pitchCorrection: boolean) => IAudioBufferSourceNode; diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index da7d0675b..57731c7e8 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -56,3 +56,13 @@ export interface ConvolverNodeOptions { buffer?: AudioBuffer | null; disableNormalization?: boolean; } + +export type TAudioNodeOptions = { + channelCount?: number; + channelCountMode?: ChannelCountMode; + channelInterpretation?: ChannelInterpretation; +}; + +export type TGainOptions = TAudioNodeOptions & { + gain?: number; +}; From 62ae75f7d6715cb5fc535a8572ed7243be836031 Mon Sep 17 00:00:00 2001 From: michal Date: Thu, 13 Nov 2025 18:23:15 +0100 Subject: [PATCH 02/16] feat: better consistency --- .../cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp | 4 ++-- .../common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 7c6c64182..3303d2fb1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -187,9 +187,9 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createConstantSource) { JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createGain) { auto object = args[0].asObject(runtime); - GainOptions options = + std::shared_ptr options = audioapi::option_parser::parseGainOptions(runtime, object); - auto gain = context_->createGain(std::make_shared(options)); + auto gain = context_->createGain(options); auto gainHostObject = std::make_shared(gain); return jsi::Object::createFromHostObject(runtime, gainHostObject); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index a5a50fdb3..6f6efc517 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -48,7 +48,7 @@ namespace audioapi::option_parser { return std::make_shared(options); } - GainOptions parseGainOptions( + std::shared_ptr parseGainOptions( jsi::Runtime &runtime, const jsi::Object &optionsObject) { std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); @@ -56,7 +56,7 @@ namespace audioapi::option_parser { options.gain = static_cast(optionsObject .getProperty(runtime, "gain") .getNumber()); - return options; + return std::make_shared(options); } } // namespace audioapi::option_parser From 5790ce58a9d26029d629930defd2086bbc076a6c Mon Sep 17 00:00:00 2001 From: michal Date: Fri, 14 Nov 2025 12:49:38 +0100 Subject: [PATCH 03/16] docs: docs proposition --- packages/audiodocs/docs/core/audio-node.mdx | 12 ++++++++++++ packages/audiodocs/docs/effects/gain-node.mdx | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/audiodocs/docs/core/audio-node.mdx b/packages/audiodocs/docs/core/audio-node.mdx index 26aa21223..b6db096bc 100644 --- a/packages/audiodocs/docs/core/audio-node.mdx +++ b/packages/audiodocs/docs/core/audio-node.mdx @@ -111,6 +111,18 @@ If no arguments provided node disconnects from all outgoing connections. #### Returns `undefined`. +### `AudioNodeOptions` + +It is used to constructing majority of all `AudioNodes`. + +| Parameter | Type | Default | Description | +| :---: | :---: | :----: | :---- | +| `channelCount` | `number` | 2 | Indicates number of channels used in mixing of node. | +| `channelCountMode` | [`ChannelCountMode`](/docs/types/channel-count-mode) | `max` | Determines how the number of input channels affects the number of output channels in an audio node. | +| `channelInterpretation` | [`ChannelInterpretation`](/docs/types/channel-interpretation) | `speakers` | Specifies how input channels are mapped out to output channels when the number of them are different. | + +If any of these values are not provided, default values are used. + ## Remarks #### `numberOfInputs` diff --git a/packages/audiodocs/docs/effects/gain-node.mdx b/packages/audiodocs/docs/effects/gain-node.mdx index 42fb43d57..4606edc31 100644 --- a/packages/audiodocs/docs/effects/gain-node.mdx +++ b/packages/audiodocs/docs/effects/gain-node.mdx @@ -3,7 +3,7 @@ sidebar_position: 2 --- import AudioNodePropsTable from "@site/src/components/AudioNodePropsTable" -import { ReadOnly } from '@site/src/components/Badges'; +import { Optional, ReadOnly } from '@site/src/components/Badges'; import { useGainAdsrPlayground } from '@site/src/components/InteractivePlayground/GainAdsrExample/useGainAdsrPlayground'; import InteractivePlayground from '@site/src/components/InteractivePlayground'; @@ -43,6 +43,19 @@ You can read more about envelopes and ADSR on [Wikipedia]( | `number` | 1.0 | Number of gain value | + +Or by using `BaseAudioContext` factory method: [`BaseAudioContext.createGain()`](/docs/core/base-audio-context#creategain) ## Properties From 4fd54875ce85544697ad0b3e85d6bf161f5963c8 Mon Sep 17 00:00:00 2001 From: michal Date: Sun, 16 Nov 2025 14:44:37 +0100 Subject: [PATCH 04/16] feat: stereo panner options --- packages/audiodocs/docs/effects/gain-node.mdx | 2 +- .../audiodocs/docs/effects/stereo-panner-node.mdx | 15 ++++++++++++++- .../HostObjects/BaseAudioContextHostObject.cpp | 13 ++++++++----- .../cpp/audioapi/HostObjects/utils/NodeOptions.h | 6 ++++++ .../HostObjects/utils/NodeOptionsParser.h | 11 +++++++++++ .../common/cpp/audioapi/core/BaseAudioContext.cpp | 5 +++-- .../common/cpp/audioapi/core/BaseAudioContext.h | 5 +++-- .../audioapi/core/effects/StereoPannerNode.cpp | 10 ++++++---- .../cpp/audioapi/core/effects/StereoPannerNode.h | 3 ++- .../src/core/BaseAudioContext.ts | 2 +- .../src/core/StereoPannerNode.ts | 13 ++++++++++++- packages/react-native-audio-api/src/defaults.ts | 8 +++++++- packages/react-native-audio-api/src/interfaces.ts | 5 ++++- packages/react-native-audio-api/src/types.ts | 4 ++++ 14 files changed, 82 insertions(+), 20 deletions(-) diff --git a/packages/audiodocs/docs/effects/gain-node.mdx b/packages/audiodocs/docs/effects/gain-node.mdx index 4606edc31..adfa4eb19 100644 --- a/packages/audiodocs/docs/effects/gain-node.mdx +++ b/packages/audiodocs/docs/effects/gain-node.mdx @@ -53,7 +53,7 @@ Inherits all properties from [`AudioNodeOptions`](/docs/core/audio-node#audionod | Parameter | Type | Default | Description | | :---: | :---: | :----: | :---- | -| `gain` | `number` | 1.0 | Number of gain value | +| `gain` | `number` | 1.0 | Number representing gain value | Or by using `BaseAudioContext` factory method: [`BaseAudioContext.createGain()`](/docs/core/base-audio-context#creategain) diff --git a/packages/audiodocs/docs/effects/stereo-panner-node.mdx b/packages/audiodocs/docs/effects/stereo-panner-node.mdx index 438feff59..64438ff33 100644 --- a/packages/audiodocs/docs/effects/stereo-panner-node.mdx +++ b/packages/audiodocs/docs/effects/stereo-panner-node.mdx @@ -3,7 +3,7 @@ sidebar_position: 4 --- import AudioNodePropsTable from "@site/src/components/AudioNodePropsTable" -import { ReadOnly } from '@site/src/components/Badges'; +import { Optional, ReadOnly } from '@site/src/components/Badges'; # StereoPannerNode @@ -15,6 +15,19 @@ The `StereoPannerNode` interface represents the change in ratio between two outp ## Constructor +```tsx +constructor(context: BaseAudioContext, stereoPannerOptions?: StereoPannerOptions) +``` + +### `StereoPannerOptions` + +Inherits all properties from [`AudioNodeOptions`](/docs/core/audio-node#audionodeoptions) + +| Parameter | Type | Default | Description | +| :---: | :---: | :----: | :---- | +| `pan` | `number` | 0.0 | Number representing pan value | + +Or by using `BaseAudioContext` factory method: [`BaseAudioContext.createStereoPanner()`](/docs/core/base-audio-context#createstereopanner) ## Properties diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 3303d2fb1..bfab71e91 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -186,16 +186,19 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createConstantSource) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createGain) { - auto object = args[0].asObject(runtime); - std::shared_ptr options = - audioapi::option_parser::parseGainOptions(runtime, object); - auto gain = context_->createGain(options); + auto options = args[0].asObject(runtime); + std::shared_ptr gainOptions = + audioapi::option_parser::parseGainOptions(runtime, options); + auto gain = context_->createGain(gainOptions); auto gainHostObject = std::make_shared(gain); return jsi::Object::createFromHostObject(runtime, gainHostObject); } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createStereoPanner) { - auto stereoPanner = context_->createStereoPanner(); + auto options = args[0].asObject(runtime); + std::shared_ptr stereoPannerOptions = + audioapi::option_parser::parseStereoPannerOptions(runtime, options); + auto stereoPanner = context_->createStereoPanner(stereoPannerOptions); auto stereoPannerHostObject = std::make_shared(stereoPanner); return jsi::Object::createFromHostObject(runtime, stereoPannerHostObject); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 64fb7ea32..d8f90cef7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -15,4 +15,10 @@ struct GainOptions : AudioNodeOptions { explicit GainOptions(AudioNodeOptions nodeOptions) : AudioNodeOptions(nodeOptions) {} }; + +struct StereoPannerOptions : AudioNodeOptions { + float pan; + explicit StereoPannerOptions(AudioNodeOptions nodeOptions) + : AudioNodeOptions(nodeOptions) {} +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 6f6efc517..dee971a0b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -58,6 +58,17 @@ namespace audioapi::option_parser { .getNumber()); return std::make_shared(options); } + + std::shared_ptr parseStereoPannerOptions ( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); + StereoPannerOptions options(*nodeOptions.get()); + options.pan = static_cast(optionsObject + .getProperty(runtime, "pan") + .getNumber()); + return std::make_shared(options); + } } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index 1ddca0845..b9f576806 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -136,8 +136,9 @@ std::shared_ptr BaseAudioContext::createGain( return gain; } -std::shared_ptr BaseAudioContext::createStereoPanner() { - auto stereoPanner = std::make_shared(this); +std::shared_ptr BaseAudioContext::createStereoPanner( + const std::shared_ptr options) { + auto stereoPanner = std::make_shared(this, options); nodeManager_->addProcessingNode(stereoPanner); return stereoPanner; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index 2aa92f9dd..ffe9ace48 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -36,6 +36,7 @@ class WorkletNode; class WorkletProcessingNode; class StreamerNode; class GainOptions; +class StereoPannerOptions; class BaseAudioContext { public: @@ -66,8 +67,8 @@ class BaseAudioContext { std::shared_ptr createOscillator(); std::shared_ptr createConstantSource(); std::shared_ptr createStreamer(); - std::shared_ptr createGain(std::shared_ptr options); - std::shared_ptr createStereoPanner(); + std::shared_ptr createGain(const std::shared_ptr options); + std::shared_ptr createStereoPanner(const std::shared_ptr options); std::shared_ptr createBiquadFilter(); std::shared_ptr createBufferSource(bool pitchCorrection); std::shared_ptr createBufferQueueSource(bool pitchCorrection); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp index 74532ef2e..0c6f7e3fd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,10 +9,11 @@ namespace audioapi { -StereoPannerNode::StereoPannerNode(BaseAudioContext *context) - : AudioNode(context) { - channelCountMode_ = ChannelCountMode::CLAMPED_MAX; - panParam_ = std::make_shared(0.0, -1.0f, 1.0f, context); +StereoPannerNode::StereoPannerNode( + BaseAudioContext *context, + const std::shared_ptr options) + : AudioNode(context, options) { + panParam_ = std::make_shared(options->pan, -1.0f, 1.0f, context); isInitialized_ = true; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.h index ebde86c4c..12badbafd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.h @@ -10,10 +10,11 @@ namespace audioapi { class AudioBus; +class StereoPannerOptions; class StereoPannerNode : public AudioNode { public: - explicit StereoPannerNode(BaseAudioContext *context); + explicit StereoPannerNode(BaseAudioContext *context, const std::shared_ptr options); [[nodiscard]] std::shared_ptr getPanParam() const; diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index a5a0bc273..10d2d866b 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -197,7 +197,7 @@ export default class BaseAudioContext { } createStereoPanner(): StereoPannerNode { - return new StereoPannerNode(this, this.context.createStereoPanner()); + return new StereoPannerNode(this); } createBiquadFilter(): BiquadFilterNode { diff --git a/packages/react-native-audio-api/src/core/StereoPannerNode.ts b/packages/react-native-audio-api/src/core/StereoPannerNode.ts index a8fa77006..307b6a1fa 100644 --- a/packages/react-native-audio-api/src/core/StereoPannerNode.ts +++ b/packages/react-native-audio-api/src/core/StereoPannerNode.ts @@ -1,4 +1,6 @@ import { IStereoPannerNode } from '../interfaces'; +import { SteroPannerOptions } from '../defaults'; +import { TSteroPannerOptions } from '../types'; import AudioNode from './AudioNode'; import AudioParam from './AudioParam'; import BaseAudioContext from './BaseAudioContext'; @@ -6,7 +8,16 @@ import BaseAudioContext from './BaseAudioContext'; export default class StereoPannerNode extends AudioNode { readonly pan: AudioParam; - constructor(context: BaseAudioContext, pan: IStereoPannerNode) { + constructor( + context: BaseAudioContext, + stereoPannerOptions?: TSteroPannerOptions + ) { + const finalOptions: TSteroPannerOptions = { + ...SteroPannerOptions, + ...stereoPannerOptions, + }; + const pan: IStereoPannerNode = + context.context.createStereoPanner(finalOptions); super(context, pan); this.pan = new AudioParam(pan.pan, context); } diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index d3eb746df..01c01a9e7 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -1,4 +1,4 @@ -import { TAudioNodeOptions, TGainOptions } from './types'; +import { TAudioNodeOptions, TGainOptions, TSteroPannerOptions } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { channelCount: 2, @@ -10,3 +10,9 @@ export const GainOptions: TGainOptions = { ...AudioNodeOptions, gain: 1, }; + +export const SteroPannerOptions: TSteroPannerOptions = { + ...AudioNodeOptions, + channelCountMode: 'clamped-max', + pan: 0, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index b49891e7c..2ac7af8c1 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -7,6 +7,7 @@ import { OscillatorType, WindowType, TGainOptions, + TSteroPannerOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -61,7 +62,9 @@ export interface IBaseAudioContext { createOscillator(): IOscillatorNode; createConstantSource(): IConstantSourceNode; createGain(gainOptions: TGainOptions): IGainNode; - createStereoPanner(): IStereoPannerNode; + createStereoPanner( + stereoPannerOptions: TSteroPannerOptions + ): IStereoPannerNode; createBiquadFilter: () => IBiquadFilterNode; createBufferSource: (pitchCorrection: boolean) => IAudioBufferSourceNode; createBufferQueueSource: ( diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 57731c7e8..fe7083f8d 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -66,3 +66,7 @@ export type TAudioNodeOptions = { export type TGainOptions = TAudioNodeOptions & { gain?: number; }; + +export type TSteroPannerOptions = TAudioNodeOptions & { + pan?: number; +}; From 5d7cc6958ca377a5db731c829e581e9e85f1278b Mon Sep 17 00:00:00 2001 From: michal Date: Sun, 16 Nov 2025 15:15:52 +0100 Subject: [PATCH 05/16] feat: added web --- .../src/web-core/AudioContext.tsx | 4 ++-- .../react-native-audio-api/src/web-core/GainNode.tsx | 4 +++- .../src/web-core/OfflineAudioContext.tsx | 4 ++-- .../src/web-core/StereoPannerNode.tsx | 10 +++++++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index d7ab486a4..324daee32 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -61,11 +61,11 @@ export default class AudioContext implements BaseAudioContext { } createGain(): GainNode { - return new GainNode(this, this.context.createGain()); + return new GainNode(this); } createStereoPanner(): StereoPannerNode { - return new StereoPannerNode(this, this.context.createStereoPanner()); + return new StereoPannerNode(this); } createBiquadFilter(): BiquadFilterNode { diff --git a/packages/react-native-audio-api/src/web-core/GainNode.tsx b/packages/react-native-audio-api/src/web-core/GainNode.tsx index 601de5920..4f8c98a79 100644 --- a/packages/react-native-audio-api/src/web-core/GainNode.tsx +++ b/packages/react-native-audio-api/src/web-core/GainNode.tsx @@ -1,11 +1,13 @@ import BaseAudioContext from './BaseAudioContext'; import AudioNode from './AudioNode'; import AudioParam from './AudioParam'; +import { TGainOptions } from '../types'; export default class GainNode extends AudioNode { readonly gain: AudioParam; - constructor(context: BaseAudioContext, gain: globalThis.GainNode) { + constructor(context: BaseAudioContext, gainOptions?: TGainOptions) { + const gain = new globalThis.GainNode(context.context, gainOptions); super(context, gain); this.gain = new AudioParam(gain.gain, context); } diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index cf9eec733..b5941a0d1 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -67,11 +67,11 @@ export default class OfflineAudioContext implements BaseAudioContext { } createGain(): GainNode { - return new GainNode(this, this.context.createGain()); + return new GainNode(this); } createStereoPanner(): StereoPannerNode { - return new StereoPannerNode(this, this.context.createStereoPanner()); + return new StereoPannerNode(this); } createBiquadFilter(): BiquadFilterNode { diff --git a/packages/react-native-audio-api/src/web-core/StereoPannerNode.tsx b/packages/react-native-audio-api/src/web-core/StereoPannerNode.tsx index 2d468a205..5c968c8ff 100644 --- a/packages/react-native-audio-api/src/web-core/StereoPannerNode.tsx +++ b/packages/react-native-audio-api/src/web-core/StereoPannerNode.tsx @@ -1,11 +1,19 @@ import BaseAudioContext from './BaseAudioContext'; import AudioNode from './AudioNode'; import AudioParam from './AudioParam'; +import { TSteroPannerOptions } from '../types'; export default class StereoPannerNode extends AudioNode { readonly pan: AudioParam; - constructor(context: BaseAudioContext, pan: globalThis.StereoPannerNode) { + constructor( + context: BaseAudioContext, + stereoPannerOptions?: TSteroPannerOptions + ) { + const pan = new globalThis.StereoPannerNode( + context.context, + stereoPannerOptions + ); super(context, pan); this.pan = new AudioParam(pan.pan, context); } From add1958ad3d625ed20710e6e635c709fdf0d043d Mon Sep 17 00:00:00 2001 From: michal Date: Sun, 16 Nov 2025 17:01:08 +0100 Subject: [PATCH 06/16] feat: convolver options --- .../BaseAudioContextHostObject.cpp | 14 +-- .../audioapi/HostObjects/utils/NodeOptions.h | 10 ++ .../HostObjects/utils/NodeOptionsParser.h | 110 ++++++++++-------- .../cpp/audioapi/core/BaseAudioContext.cpp | 6 +- .../cpp/audioapi/core/BaseAudioContext.h | 3 +- .../audioapi/core/effects/ConvolverNode.cpp | 10 +- .../cpp/audioapi/core/effects/ConvolverNode.h | 3 +- .../cpp/audioapi/core/effects/GainNode.cpp | 2 +- .../cpp/audioapi/core/effects/GainNode.h | 2 +- .../src/core/BaseAudioContext.ts | 22 +--- .../src/core/ConvolverNode.ts | 27 ++++- .../react-native-audio-api/src/defaults.ts | 12 +- .../react-native-audio-api/src/interfaces.ts | 6 +- packages/react-native-audio-api/src/types.ts | 5 + 14 files changed, 131 insertions(+), 101 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index bfab71e91..dafed4cfe 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -281,16 +281,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createAnalyser) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createConvolver) { - auto disableNormalization = args[1].getBool(); - std::shared_ptr convolver; - if (args[0].isUndefined()) { - convolver = context_->createConvolver(nullptr, disableNormalization); - } else { - auto bufferHostObject = - args[0].getObject(runtime).asHostObject(runtime); - convolver = context_->createConvolver( - bufferHostObject->audioBuffer_, disableNormalization); - } + auto options = args[0].asObject(runtime); + std::shared_ptr convolverOptions = + audioapi::option_parser::parseConvolverOptions(runtime, options); + auto convolver = context_->createConvolver(convolverOptions); auto convolverHostObject = std::make_shared(convolver); return jsi::Object::createFromHostObject(runtime, convolverHostObject); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index d8f90cef7..82bb86fb4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -1,7 +1,10 @@ #pragma once +#include + #include #include +#include namespace audioapi { struct AudioNodeOptions { @@ -21,4 +24,11 @@ struct StereoPannerOptions : AudioNodeOptions { explicit StereoPannerOptions(AudioNodeOptions nodeOptions) : AudioNodeOptions(nodeOptions) {} }; + +struct ConvolverOptions : AudioNodeOptions { + std::shared_ptr bus; + bool disableNormalization; + explicit ConvolverOptions(AudioNodeOptions nodeOptions) + : AudioNodeOptions(nodeOptions) {} +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index dee971a0b..34459fff1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -2,73 +2,83 @@ #include #include +#include #include #include #include -#include #include +#include namespace audioapi::option_parser { - std::shared_ptr parseAudioNodeOptions( +std::shared_ptr parseAudioNodeOptions( jsi::Runtime &runtime, - const jsi::Object &optionsObject - ) { - AudioNodeOptions options; - - options.channelCount = - static_cast(optionsObject - .getProperty(runtime, "channelCount") - .getNumber()); + const jsi::Object &optionsObject) { + AudioNodeOptions options; - auto channelCountModeStr = optionsObject - .getProperty(runtime, "channelCountMode") - .asString(runtime) - .utf8(runtime); + options.channelCount = static_cast( + optionsObject.getProperty(runtime, "channelCount").getNumber()); - if (channelCountModeStr == "max") { - options.channelCountMode = ChannelCountMode::MAX; - } else if (channelCountModeStr == "clamped-max") { - options.channelCountMode = ChannelCountMode::CLAMPED_MAX; - } else if (channelCountModeStr == "explicit") { - options.channelCountMode = ChannelCountMode::EXPLICIT; - } + auto channelCountModeStr = + optionsObject.getProperty(runtime, "channelCountMode") + .asString(runtime) + .utf8(runtime); - auto channelInterpretationStr = optionsObject - .getProperty(runtime, "channelInterpretation") - .asString(runtime) - .utf8(runtime); + if (channelCountModeStr == "max") { + options.channelCountMode = ChannelCountMode::MAX; + } else if (channelCountModeStr == "clamped-max") { + options.channelCountMode = ChannelCountMode::CLAMPED_MAX; + } else if (channelCountModeStr == "explicit") { + options.channelCountMode = ChannelCountMode::EXPLICIT; + } - if (channelInterpretationStr == "speakers") { - options.channelInterpretation = ChannelInterpretation::SPEAKERS; - } else if (channelInterpretationStr == "discrete") { - options.channelInterpretation = ChannelInterpretation::DISCRETE; - } + auto channelInterpretationStr = + optionsObject.getProperty(runtime, "channelInterpretation") + .asString(runtime) + .utf8(runtime); - return std::make_shared(options); + if (channelInterpretationStr == "speakers") { + options.channelInterpretation = ChannelInterpretation::SPEAKERS; + } else if (channelInterpretationStr == "discrete") { + options.channelInterpretation = ChannelInterpretation::DISCRETE; } - std::shared_ptr parseGainOptions( - jsi::Runtime &runtime, - const jsi::Object &optionsObject) { - std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); - GainOptions options(*nodeOptions.get()); - options.gain = static_cast(optionsObject - .getProperty(runtime, "gain") - .getNumber()); - return std::make_shared(options); - } + return std::make_shared(options); +} - std::shared_ptr parseStereoPannerOptions ( +std::shared_ptr parseGainOptions( jsi::Runtime &runtime, const jsi::Object &optionsObject) { - std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); - StereoPannerOptions options(*nodeOptions.get()); - options.pan = static_cast(optionsObject - .getProperty(runtime, "pan") - .getNumber()); - return std::make_shared(options); - } -} // namespace audioapi::option_parser + std::shared_ptr nodeOptions = + parseAudioNodeOptions(runtime, optionsObject); + GainOptions options(*nodeOptions.get()); + options.gain = static_cast( + optionsObject.getProperty(runtime, "gain").getNumber()); + return std::make_shared(options); +} +std::shared_ptr parseStereoPannerOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = + parseAudioNodeOptions(runtime, optionsObject); + StereoPannerOptions options(*nodeOptions.get()); + options.pan = + static_cast(optionsObject.getProperty(runtime, "pan").getNumber()); + return std::make_shared(options); +} +std::shared_ptr parseConvolverOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = + parseAudioNodeOptions(runtime, optionsObject); + ConvolverOptions options(*nodeOptions.get()); + options.disableNormalization = static_cast(optionsObject.getProperty(runtime, "disableNormalization").getNumber()); + if (optionsObject.hasProperty(runtime, "buffer")) { + auto bufferHostObject = optionsObject.getProperty(runtime, "buffer").getObject(runtime).asHostObject(runtime); + options.bus = bufferHostObject->audioBuffer_; + } + return std::make_shared(options); +} +} // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index b9f576806..0ab9351bc 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -187,10 +187,8 @@ std::shared_ptr BaseAudioContext::createAnalyser() { } std::shared_ptr BaseAudioContext::createConvolver( - std::shared_ptr buffer, - bool disableNormalization) { - auto convolver = - std::make_shared(this, buffer, disableNormalization); + const std::shared_ptr options) { + auto convolver = std::make_shared(this, options); nodeManager_->addProcessingNode(convolver); return convolver; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index ffe9ace48..9fb28f715 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -37,6 +37,7 @@ class WorkletProcessingNode; class StreamerNode; class GainOptions; class StereoPannerOptions; +class ConvolverOptions; class BaseAudioContext { public: @@ -79,7 +80,7 @@ class BaseAudioContext { bool disableNormalization, int length); std::shared_ptr createAnalyser(); - std::shared_ptr createConvolver(std::shared_ptr buffer, bool disableNormalization); + std::shared_ptr createConvolver(const std::shared_ptr options); std::shared_ptr getBasicWaveForm(OscillatorType type); [[nodiscard]] float getNyquistFrequency() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp index 28f457f8c..40a0359d3 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -11,9 +12,8 @@ namespace audioapi { ConvolverNode::ConvolverNode( BaseAudioContext *context, - const std::shared_ptr &buffer, - bool disableNormalization) - : AudioNode(context), + const std::shared_ptr options) + : AudioNode(context, options), remainingSegments_(0), internalBufferIndex_(0), signalledToStop_(false), @@ -23,9 +23,9 @@ ConvolverNode::ConvolverNode( internalBuffer_(nullptr) { channelCount_ = 2; channelCountMode_ = ChannelCountMode::CLAMPED_MAX; - normalize_ = !disableNormalization; + normalize_ = !options->disableNormalization; gainCalibrationSampleRate_ = context->getSampleRate(); - setBuffer(buffer); + setBuffer(options->bus); audioBus_ = std::make_shared( RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); isInitialized_ = true; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h index d1208bea0..be78df27c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h @@ -16,10 +16,11 @@ namespace audioapi { class AudioBus; class AudioBuffer; +class ConvolverOptions; class ConvolverNode : public AudioNode { public: - explicit ConvolverNode(BaseAudioContext *context, const std::shared_ptr& buffer, bool disableNormalization); + explicit ConvolverNode(BaseAudioContext *context, const std::shared_ptr options); [[nodiscard]] bool getNormalize_() const; [[nodiscard]] const std::shared_ptr &getBuffer() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp index 1608ec3e5..97854ff79 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp @@ -10,7 +10,7 @@ namespace audioapi { GainNode::GainNode( BaseAudioContext *context, std::shared_ptr options) - : AudioNode(context, std::static_pointer_cast(options)) { + : AudioNode(context, options) { gainParam_ = std::make_shared( options->gain, MOST_NEGATIVE_SINGLE_FLOAT, diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h index 0ed3ec810..fc15c5f78 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.h @@ -12,7 +12,7 @@ class GainOptions; class GainNode : public AudioNode { public: - explicit GainNode(BaseAudioContext *context, std::shared_ptr options); + explicit GainNode(BaseAudioContext *context, const std::shared_ptr options); [[nodiscard]] std::shared_ptr getGainParam() const; diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 10d2d866b..aa8af2561 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -5,7 +5,6 @@ import { ContextState, PeriodicWaveConstraints, AudioWorkletRuntime, - ConvolverNodeOptions, } from '../types'; import { assertWorkletsEnabled, workletsModule } from '../utils'; import WorkletSourceNode from './WorkletSourceNode'; @@ -276,24 +275,7 @@ export default class BaseAudioContext { return new AnalyserNode(this, this.context.createAnalyser()); } - createConvolver(options?: ConvolverNodeOptions): ConvolverNode { - if (options?.buffer) { - const numberOfChannels = options.buffer.numberOfChannels; - if ( - numberOfChannels !== 1 && - numberOfChannels !== 2 && - numberOfChannels !== 4 - ) { - throw new NotSupportedError( - `The number of channels provided (${numberOfChannels}) in impulse response for ConvolverNode buffer must be 1 or 2 or 4.` - ); - } - } - const buffer = options?.buffer ?? null; - const disableNormalization = options?.disableNormalization ?? false; - return new ConvolverNode( - this, - this.context.createConvolver(buffer?.buffer, disableNormalization) - ); + createConvolver(): ConvolverNode { + return new ConvolverNode(this); } } diff --git a/packages/react-native-audio-api/src/core/ConvolverNode.ts b/packages/react-native-audio-api/src/core/ConvolverNode.ts index a2b71592f..2cac2629c 100644 --- a/packages/react-native-audio-api/src/core/ConvolverNode.ts +++ b/packages/react-native-audio-api/src/core/ConvolverNode.ts @@ -1,12 +1,33 @@ import { IConvolverNode } from '../interfaces'; +import { ConvolverOptions } from '../defaults'; +import { TConvolverOptions } from '../types'; +import { NotSupportedError } from '../errors'; import BaseAudioContext from './BaseAudioContext'; import AudioNode from './AudioNode'; import AudioBuffer from './AudioBuffer'; export default class ConvolverNode extends AudioNode { - constructor(context: BaseAudioContext, node: IConvolverNode) { - super(context, node); - this.normalize = node.normalize; + constructor(context: BaseAudioContext, convolverOptions?: TConvolverOptions) { + const finalOptions: TConvolverOptions = { + ...ConvolverOptions, + ...convolverOptions, + }; + if (finalOptions.buffer) { + const numberOfChannels = finalOptions.buffer.numberOfChannels; + if ( + numberOfChannels !== 1 && + numberOfChannels !== 2 && + numberOfChannels !== 4 + ) { + throw new NotSupportedError( + `The number of channels provided (${numberOfChannels}) in impulse response for ConvolverNode buffer must be 1 or 2 or 4.` + ); + } + } + const convolverNode: IConvolverNode = + context.context.createConvolver(finalOptions); + super(context, convolverNode); + this.normalize = convolverNode.normalize; } public get buffer(): AudioBuffer | null { diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index 01c01a9e7..12fd42f1b 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -1,4 +1,9 @@ -import { TAudioNodeOptions, TGainOptions, TSteroPannerOptions } from './types'; +import { + TAudioNodeOptions, + TGainOptions, + TSteroPannerOptions, + TConvolverOptions, +} from './types'; export const AudioNodeOptions: TAudioNodeOptions = { channelCount: 2, @@ -16,3 +21,8 @@ export const SteroPannerOptions: TSteroPannerOptions = { channelCountMode: 'clamped-max', pan: 0, }; + +export const ConvolverOptions: TConvolverOptions = { + ...AudioNodeOptions, + disableNormalization: false, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 2ac7af8c1..dcac2379b 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -8,6 +8,7 @@ import { WindowType, TGainOptions, TSteroPannerOptions, + TConvolverOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -81,10 +82,7 @@ export interface IBaseAudioContext { disableNormalization: boolean ) => IPeriodicWave; createAnalyser: () => IAnalyserNode; - createConvolver: ( - buffer: IAudioBuffer | undefined, - disableNormalization: boolean - ) => IConvolverNode; + createConvolver: (convolverOptions: TConvolverOptions) => IConvolverNode; createStreamer: () => IStreamerNode; } diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index fe7083f8d..bcf48f5d3 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -70,3 +70,8 @@ export type TGainOptions = TAudioNodeOptions & { export type TSteroPannerOptions = TAudioNodeOptions & { pan?: number; }; + +export type TConvolverOptions = TAudioNodeOptions & { + buffer?: AudioBuffer; + disableNormalization?: boolean; +}; From 022683be60c0d156f032c640f86de09ca851c310 Mon Sep 17 00:00:00 2001 From: michal Date: Mon, 17 Nov 2025 13:02:09 +0100 Subject: [PATCH 07/16] test: tests --- .../audioapi/HostObjects/utils/NodeOptions.h | 20 +++++++----------- .../common/cpp/test/src/GainTest.cpp | 6 ++++-- .../common/cpp/test/src/StereoPannerTest.cpp | 6 ++++-- packages/react-native-audio-api/src/types.ts | 21 +++++++------------ 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 82bb86fb4..0ddd28b65 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -8,27 +8,21 @@ namespace audioapi { struct AudioNodeOptions { - int channelCount; - ChannelCountMode channelCountMode; - ChannelInterpretation channelInterpretation; + int channelCount = 2; + ChannelCountMode channelCountMode = ChannelCountMode::MAX; + ChannelInterpretation channelInterpretation = ChannelInterpretation::SPEAKERS; }; struct GainOptions : AudioNodeOptions { - float gain; - explicit GainOptions(AudioNodeOptions nodeOptions) - : AudioNodeOptions(nodeOptions) {} + float gain = 1.0f; }; struct StereoPannerOptions : AudioNodeOptions { - float pan; - explicit StereoPannerOptions(AudioNodeOptions nodeOptions) - : AudioNodeOptions(nodeOptions) {} + float pan = 0.0f; }; struct ConvolverOptions : AudioNodeOptions { - std::shared_ptr bus; - bool disableNormalization; - explicit ConvolverOptions(AudioNodeOptions nodeOptions) - : AudioNodeOptions(nodeOptions) {} + std::shared_ptr bus = nullptr; + bool disableNormalization = false; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/test/src/GainTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/GainTest.cpp index 30f00c385..4d5dc51a8 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/GainTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/GainTest.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -23,7 +24,8 @@ class GainTest : public ::testing::Test { class TestableGainNode : public GainNode { public: - explicit TestableGainNode(BaseAudioContext *context) : GainNode(context) {} + explicit TestableGainNode(BaseAudioContext *context) + : GainNode(context, std::make_shared()) {} void setGainParam(float value) { getGainParam()->setValue(value); @@ -37,7 +39,7 @@ class TestableGainNode : public GainNode { }; TEST_F(GainTest, GainCanBeCreated) { - auto gain = context->createGain(); + auto gain = context->createGain(std::make_shared()); ASSERT_NE(gain, nullptr); } diff --git a/packages/react-native-audio-api/common/cpp/test/src/StereoPannerTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/StereoPannerTest.cpp index 62a1ea268..68bda8990 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/StereoPannerTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/StereoPannerTest.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,7 +25,7 @@ class StereoPannerTest : public ::testing::Test { class TestableStereoPannerNode : public StereoPannerNode { public: explicit TestableStereoPannerNode(BaseAudioContext *context) - : StereoPannerNode(context) {} + : StereoPannerNode(context, std::make_shared()) {} void setPanParam(float value) { getPanParam()->setValue(value); @@ -38,7 +39,8 @@ class TestableStereoPannerNode : public StereoPannerNode { }; TEST_F(StereoPannerTest, StereoPannerCanBeCreated) { - auto panner = context->createStereoPanner(); + auto panner = + context->createStereoPanner(std::make_shared()); ASSERT_NE(panner, nullptr); } diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index bcf48f5d3..483f0058b 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -52,26 +52,21 @@ export interface AudioBufferBaseSourceNodeOptions { export type ProcessorMode = 'processInPlace' | 'processThrough'; -export interface ConvolverNodeOptions { - buffer?: AudioBuffer | null; - disableNormalization?: boolean; -} - -export type TAudioNodeOptions = { +export interface TAudioNodeOptions { channelCount?: number; channelCountMode?: ChannelCountMode; channelInterpretation?: ChannelInterpretation; -}; +} -export type TGainOptions = TAudioNodeOptions & { +export interface TGainOptions extends TAudioNodeOptions { gain?: number; -}; +} -export type TSteroPannerOptions = TAudioNodeOptions & { +export interface TSteroPannerOptions extends TAudioNodeOptions { pan?: number; -}; +} -export type TConvolverOptions = TAudioNodeOptions & { +export interface TConvolverOptions extends TAudioNodeOptions { buffer?: AudioBuffer; disableNormalization?: boolean; -}; +} From 0a539d161f88b26c565175e6b95c0ffc7f1dc1c0 Mon Sep 17 00:00:00 2001 From: michal Date: Mon, 17 Nov 2025 13:49:21 +0100 Subject: [PATCH 08/16] feat: constant source options --- .../HostObjects/BaseAudioContextHostObject.cpp | 5 ++++- .../cpp/audioapi/HostObjects/utils/NodeOptions.h | 4 ++++ .../audioapi/HostObjects/utils/NodeOptionsParser.h | 12 ++++++++++-- .../common/cpp/audioapi/core/BaseAudioContext.cpp | 5 +++-- .../common/cpp/audioapi/core/BaseAudioContext.h | 3 ++- .../cpp/audioapi/core/sources/ConstantSourceNode.cpp | 10 ++++++++-- .../cpp/audioapi/core/sources/ConstantSourceNode.h | 3 ++- .../common/cpp/test/src/ConstantSourceTest.cpp | 7 +++++-- .../src/core/BaseAudioContext.ts | 2 +- .../src/core/ConstantSourceNode.ts | 10 +++++++++- .../react-native-audio-api/src/core/ConvolverNode.ts | 4 ++-- packages/react-native-audio-api/src/core/GainNode.ts | 4 ++-- .../src/core/StereoPannerNode.ts | 7 ++----- packages/react-native-audio-api/src/defaults.ts | 5 +++++ packages/react-native-audio-api/src/interfaces.ts | 5 ++++- packages/react-native-audio-api/src/types.ts | 4 ++++ 16 files changed, 67 insertions(+), 23 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index dafed4cfe..e67e3708a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -179,7 +179,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createStreamer) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createConstantSource) { - auto constantSource = context_->createConstantSource(); + auto options = args[0].asObject(runtime); + std::shared_ptr constantSourceOptions = + audioapi::option_parser::parseConstantSourceOptions(runtime, options); + auto constantSource = context_->createConstantSource(constantSourceOptions); auto constantSourceHostObject = std::make_shared(constantSource); return jsi::Object::createFromHostObject(runtime, constantSourceHostObject); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 0ddd28b65..8348adc2e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -25,4 +25,8 @@ struct ConvolverOptions : AudioNodeOptions { std::shared_ptr bus = nullptr; bool disableNormalization = false; }; + +struct ConstantSourceOptions { + float offset = 1.0f; +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 34459fff1..ea4c4d157 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -8,7 +8,7 @@ #include #include -#include +#include namespace audioapi::option_parser { std::shared_ptr parseAudioNodeOptions( @@ -81,4 +81,12 @@ std::shared_ptr parseConvolverOptions( } return std::make_shared(options); } -} // namespace audioapi::option_parser + +std::shared_ptr parseConstantSourceOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + ConstantSourceOptions options; + options.offset = static_cast(optionsObject.getProperty(runtime, "offset").getNumber()); + return std::make_shared(options); +} +}// namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index 0ab9351bc..e11d53583 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -115,8 +115,9 @@ std::shared_ptr BaseAudioContext::createOscillator() { return oscillator; } -std::shared_ptr BaseAudioContext::createConstantSource() { - auto constantSource = std::make_shared(this); +std::shared_ptr BaseAudioContext::createConstantSource( + const std::shared_ptr options) { + auto constantSource = std::make_shared(this, options); nodeManager_->addSourceNode(constantSource); return constantSource; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index 9fb28f715..502134cc4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -38,6 +38,7 @@ class StreamerNode; class GainOptions; class StereoPannerOptions; class ConvolverOptions; +class ConstantSourceOptions; class BaseAudioContext { public: @@ -66,7 +67,7 @@ class BaseAudioContext { std::weak_ptr runtime, bool shouldLockRuntime = true); std::shared_ptr createOscillator(); - std::shared_ptr createConstantSource(); + std::shared_ptr createConstantSource(const std::shared_ptr options); std::shared_ptr createStreamer(); std::shared_ptr createGain(const std::shared_ptr options); std::shared_ptr createStereoPanner(const std::shared_ptr options); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp index ed0f12d3b..c64b5dec3 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,10 +6,15 @@ #include namespace audioapi { -ConstantSourceNode::ConstantSourceNode(BaseAudioContext *context) +ConstantSourceNode::ConstantSourceNode( + BaseAudioContext *context, + const std::shared_ptr options) : AudioScheduledSourceNode(context) { offsetParam_ = std::make_shared( - 1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); + options->offset, + MOST_NEGATIVE_SINGLE_FLOAT, + MOST_POSITIVE_SINGLE_FLOAT, + context); isInitialized_ = true; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.h index f0b520a16..dd5a719e9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.h @@ -10,10 +10,11 @@ namespace audioapi { class AudioBus; +class ConstantSourceOptions; class ConstantSourceNode : public AudioScheduledSourceNode { public: - explicit ConstantSourceNode(BaseAudioContext *context); + explicit ConstantSourceNode(BaseAudioContext *context, const std::shared_ptr options); [[nodiscard]] std::shared_ptr getOffsetParam() const; diff --git a/packages/react-native-audio-api/common/cpp/test/src/ConstantSourceTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/ConstantSourceTest.cpp index 7f16ab392..2cf867e3a 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/ConstantSourceTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/ConstantSourceTest.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,7 +25,8 @@ class ConstantSourceTest : public ::testing::Test { class TestableConstantSourceNode : public ConstantSourceNode { public: explicit TestableConstantSourceNode(BaseAudioContext *context) - : ConstantSourceNode(context) {} + : ConstantSourceNode(context, std::make_shared()) { + } void setOffsetParam(float value) { getOffsetParam()->setValue(value); @@ -38,7 +40,8 @@ class TestableConstantSourceNode : public ConstantSourceNode { }; TEST_F(ConstantSourceTest, ConstantSourceCanBeCreated) { - auto constantSource = context->createConstantSource(); + auto constantSource = + context->createConstantSource(std::make_shared()); ASSERT_NE(constantSource, nullptr); } diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index aa8af2561..40ec3b953 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -188,7 +188,7 @@ export default class BaseAudioContext { } createConstantSource(): ConstantSourceNode { - return new ConstantSourceNode(this, this.context.createConstantSource()); + return new ConstantSourceNode(this); } createGain(): GainNode { diff --git a/packages/react-native-audio-api/src/core/ConstantSourceNode.ts b/packages/react-native-audio-api/src/core/ConstantSourceNode.ts index 1a36c4b9f..65b26f517 100644 --- a/packages/react-native-audio-api/src/core/ConstantSourceNode.ts +++ b/packages/react-native-audio-api/src/core/ConstantSourceNode.ts @@ -1,4 +1,6 @@ import { IConstantSourceNode } from '../interfaces'; +import { ConstantSourceOptions } from '../defaults'; +import { TConstantSourceOptions } from '../types'; import AudioParam from './AudioParam'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import BaseAudioContext from './BaseAudioContext'; @@ -6,7 +8,13 @@ import BaseAudioContext from './BaseAudioContext'; export default class ConstantSourceNode extends AudioScheduledSourceNode { readonly offset: AudioParam; - constructor(context: BaseAudioContext, node: IConstantSourceNode) { + constructor(context: BaseAudioContext, options?: TConstantSourceOptions) { + const finalOptions: TConstantSourceOptions = { + ...ConstantSourceOptions, + ...options, + }; + const node: IConstantSourceNode = + context.context.createConstantSource(finalOptions); super(context, node); this.offset = new AudioParam(node.offset, context); } diff --git a/packages/react-native-audio-api/src/core/ConvolverNode.ts b/packages/react-native-audio-api/src/core/ConvolverNode.ts index 2cac2629c..d942cc77b 100644 --- a/packages/react-native-audio-api/src/core/ConvolverNode.ts +++ b/packages/react-native-audio-api/src/core/ConvolverNode.ts @@ -7,10 +7,10 @@ import AudioNode from './AudioNode'; import AudioBuffer from './AudioBuffer'; export default class ConvolverNode extends AudioNode { - constructor(context: BaseAudioContext, convolverOptions?: TConvolverOptions) { + constructor(context: BaseAudioContext, options?: TConvolverOptions) { const finalOptions: TConvolverOptions = { ...ConvolverOptions, - ...convolverOptions, + ...options, }; if (finalOptions.buffer) { const numberOfChannels = finalOptions.buffer.numberOfChannels; diff --git a/packages/react-native-audio-api/src/core/GainNode.ts b/packages/react-native-audio-api/src/core/GainNode.ts index 65a9ead68..1c2b8f93b 100644 --- a/packages/react-native-audio-api/src/core/GainNode.ts +++ b/packages/react-native-audio-api/src/core/GainNode.ts @@ -8,10 +8,10 @@ import BaseAudioContext from './BaseAudioContext'; export default class GainNode extends AudioNode { readonly gain: AudioParam; - constructor(context: BaseAudioContext, gainOptions?: TGainOptions) { + constructor(context: BaseAudioContext, options?: TGainOptions) { const finalOptions: TGainOptions = { ...GainOptions, - ...gainOptions, + ...options, }; const gainNode: IGainNode = context.context.createGain(finalOptions); super(context, gainNode); diff --git a/packages/react-native-audio-api/src/core/StereoPannerNode.ts b/packages/react-native-audio-api/src/core/StereoPannerNode.ts index 307b6a1fa..040e9f840 100644 --- a/packages/react-native-audio-api/src/core/StereoPannerNode.ts +++ b/packages/react-native-audio-api/src/core/StereoPannerNode.ts @@ -8,13 +8,10 @@ import BaseAudioContext from './BaseAudioContext'; export default class StereoPannerNode extends AudioNode { readonly pan: AudioParam; - constructor( - context: BaseAudioContext, - stereoPannerOptions?: TSteroPannerOptions - ) { + constructor(context: BaseAudioContext, options?: TSteroPannerOptions) { const finalOptions: TSteroPannerOptions = { ...SteroPannerOptions, - ...stereoPannerOptions, + ...options, }; const pan: IStereoPannerNode = context.context.createStereoPanner(finalOptions); diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index 12fd42f1b..7ed81d960 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -3,6 +3,7 @@ import { TGainOptions, TSteroPannerOptions, TConvolverOptions, + TConstantSourceOptions, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -26,3 +27,7 @@ export const ConvolverOptions: TConvolverOptions = { ...AudioNodeOptions, disableNormalization: false, }; + +export const ConstantSourceOptions: TConstantSourceOptions = { + offset: 1, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index dcac2379b..fdfb208ad 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -9,6 +9,7 @@ import { TGainOptions, TSteroPannerOptions, TConvolverOptions, + TConstantSourceOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -61,7 +62,9 @@ export interface IBaseAudioContext { shouldUseUiRuntime: boolean ): IWorkletProcessingNode; createOscillator(): IOscillatorNode; - createConstantSource(): IConstantSourceNode; + createConstantSource( + constantSourceOptions: TConstantSourceOptions + ): IConstantSourceNode; createGain(gainOptions: TGainOptions): IGainNode; createStereoPanner( stereoPannerOptions: TSteroPannerOptions diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 483f0058b..de142df50 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -70,3 +70,7 @@ export interface TConvolverOptions extends TAudioNodeOptions { buffer?: AudioBuffer; disableNormalization?: boolean; } + +export interface TConstantSourceOptions { + offset?: number; +} From a052c456b2cc5181bc417950f503f385fb5edabd Mon Sep 17 00:00:00 2001 From: michal Date: Tue, 18 Nov 2025 16:54:32 +0100 Subject: [PATCH 09/16] feat: web for convolver --- .../BaseAudioContextHostObject.cpp | 2 +- packages/react-native-audio-api/src/types.ts | 7 +++++- .../src/web-core/AudioContext.tsx | 24 ++----------------- .../src/web-core/ConvolverNode.tsx | 16 ++++++------- .../src/web-core/OfflineAudioContext.tsx | 24 ++----------------- 5 files changed, 19 insertions(+), 54 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 7ce90741d..c6b74471f 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -20,8 +20,8 @@ #include #include -#include #include +#include namespace audioapi { diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index de142df50..0f14284a7 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -67,10 +67,15 @@ export interface TSteroPannerOptions extends TAudioNodeOptions { } export interface TConvolverOptions extends TAudioNodeOptions { - buffer?: AudioBuffer; + buffer?: AudioBuffer | null; disableNormalization?: boolean; } +export interface TWebConvolverOptions { + buffer?: globalThis.AudioBuffer | null; + normalize?: boolean; +} + export interface TConstantSourceOptions { offset?: number; } diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index 324daee32..9165a8154 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -16,7 +16,6 @@ import OscillatorNode from './OscillatorNode'; import PeriodicWave from './PeriodicWave'; import StereoPannerNode from './StereoPannerNode'; import ConvolverNode from './ConvolverNode'; -import { ConvolverNodeOptions } from './ConvolverNodeOptions'; import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm'; import ConstantSourceNode from './ConstantSourceNode'; @@ -72,27 +71,8 @@ export default class AudioContext implements BaseAudioContext { return new BiquadFilterNode(this, this.context.createBiquadFilter()); } - createConvolver(options?: ConvolverNodeOptions): ConvolverNode { - if (options?.buffer) { - const numberOfChannels = options.buffer.numberOfChannels; - if ( - numberOfChannels !== 1 && - numberOfChannels !== 2 && - numberOfChannels !== 4 - ) { - throw new NotSupportedError( - `The number of channels provided (${numberOfChannels}) in impulse response for ConvolverNode buffer must be 1 or 2 or 4.` - ); - } - } - const buffer = options?.buffer ?? null; - const disableNormalization = options?.disableNormalization ?? false; - return new ConvolverNode( - this, - this.context.createConvolver(), - buffer, - disableNormalization - ); + createConvolver(): ConvolverNode { + return new ConvolverNode(this); } async createBufferSource( diff --git a/packages/react-native-audio-api/src/web-core/ConvolverNode.tsx b/packages/react-native-audio-api/src/web-core/ConvolverNode.tsx index 6bc910514..8a9488ed1 100644 --- a/packages/react-native-audio-api/src/web-core/ConvolverNode.tsx +++ b/packages/react-native-audio-api/src/web-core/ConvolverNode.tsx @@ -1,20 +1,20 @@ import BaseAudioContext from './BaseAudioContext'; import AudioNode from './AudioNode'; import AudioBuffer from './AudioBuffer'; +import { TWebConvolverOptions } from '../types'; export default class ConvolverNode extends AudioNode { constructor( context: BaseAudioContext, - node: globalThis.ConvolverNode, - buffer: AudioBuffer | null = null, - disableNormalization: boolean = false + convolverOptions?: TWebConvolverOptions ) { - super(context, node); + const convolver = new globalThis.ConvolverNode( + context.context, + convolverOptions + ); - (this.node as globalThis.ConvolverNode).normalize = !disableNormalization; - if (buffer) { - (this.node as globalThis.ConvolverNode).buffer = buffer.buffer; - } + const node = convolver; + super(context, node); } public get buffer(): AudioBuffer | null { diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index b5941a0d1..8d71b85cb 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -19,7 +19,6 @@ import ConstantSourceNode from './ConstantSourceNode'; import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm'; import ConvolverNode from './ConvolverNode'; -import { ConvolverNodeOptions } from './ConvolverNodeOptions'; export default class OfflineAudioContext implements BaseAudioContext { readonly context: globalThis.OfflineAudioContext; @@ -78,27 +77,8 @@ export default class OfflineAudioContext implements BaseAudioContext { return new BiquadFilterNode(this, this.context.createBiquadFilter()); } - createConvolver(options?: ConvolverNodeOptions): ConvolverNode { - if (options?.buffer) { - const numberOfChannels = options.buffer.numberOfChannels; - if ( - numberOfChannels !== 1 && - numberOfChannels !== 2 && - numberOfChannels !== 4 - ) { - throw new NotSupportedError( - `The number of channels provided (${numberOfChannels}) in impulse response for ConvolverNode buffer must be 1 or 2 or 4.` - ); - } - } - const buffer = options?.buffer ?? null; - const disableNormalization = options?.disableNormalization ?? false; - return new ConvolverNode( - this, - this.context.createConvolver(), - buffer, - disableNormalization - ); + createConvolver(): ConvolverNode { + return new ConvolverNode(this); } async createBufferSource( From a47447b75f968d5e3dc2872f9f175273516d35e2 Mon Sep 17 00:00:00 2001 From: michal Date: Wed, 19 Nov 2025 10:41:40 +0100 Subject: [PATCH 10/16] feat: periodic wave --- packages/react-native-audio-api/src/api.ts | 2 +- .../react-native-audio-api/src/api.web.ts | 2 +- .../src/core/BaseAudioContext.ts | 8 +-- .../src/core/PeriodicWave.ts | 56 ++++++++++++++++++- .../react-native-audio-api/src/defaults.ts | 5 ++ packages/react-native-audio-api/src/types.ts | 13 +++-- .../src/web-core/AudioContext.tsx | 6 +- .../src/web-core/BaseAudioContext.tsx | 2 +- .../src/web-core/OfflineAudioContext.tsx | 5 +- .../src/web-core/PeriodicWave.tsx | 15 ++++- 10 files changed, 89 insertions(+), 25 deletions(-) diff --git a/packages/react-native-audio-api/src/api.ts b/packages/react-native-audio-api/src/api.ts index 9a575300d..6f7500e01 100644 --- a/packages/react-native-audio-api/src/api.ts +++ b/packages/react-native-audio-api/src/api.ts @@ -78,6 +78,7 @@ export { default as ConvolverNode } from './core/ConvolverNode'; export { default as useSystemVolume } from './hooks/useSystemVolume'; export { decodeAudioData, decodePCMInBase64 } from './core/AudioDecoder'; export { default as changePlaybackSpeed } from './core/AudioStretcher'; +export { default as PeriodicWave } from './core/PeriodicWave'; export { OscillatorType, @@ -86,7 +87,6 @@ export { ChannelInterpretation, ContextState, WindowType, - PeriodicWaveConstraints, AudioWorkletRuntime, } from './types'; diff --git a/packages/react-native-audio-api/src/api.web.ts b/packages/react-native-audio-api/src/api.web.ts index 292f86993..bdf1474c0 100644 --- a/packages/react-native-audio-api/src/api.web.ts +++ b/packages/react-native-audio-api/src/api.web.ts @@ -14,6 +14,7 @@ export { default as OscillatorNode } from './web-core/OscillatorNode'; export { default as StereoPannerNode } from './web-core/StereoPannerNode'; export { default as ConstantSourceNode } from './web-core/ConstantSourceNode'; export { default as ConvolverNode } from './web-core/ConvolverNode'; +export { default as PeriodicWave } from './web-core/PeriodicWave'; export * from './web-core/custom'; @@ -24,7 +25,6 @@ export { ChannelInterpretation, ContextState, WindowType, - PeriodicWaveConstraints, } from './types'; export { diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 40ec3b953..9c0e4b724 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -3,7 +3,6 @@ import { IBaseAudioContext } from '../interfaces'; import { AudioBufferBaseSourceNodeOptions, ContextState, - PeriodicWaveConstraints, AudioWorkletRuntime, } from '../types'; import { assertWorkletsEnabled, workletsModule } from '../utils'; @@ -263,12 +262,7 @@ export default class BaseAudioContext { `The lengths of the real (${real.length}) and imaginary (${imag.length}) arrays must match.` ); } - - const disableNormalization = constraints?.disableNormalization ?? false; - - return new PeriodicWave( - this.context.createPeriodicWave(real, imag, disableNormalization) - ); + return new PeriodicWave(this, { real, imag, ...constraints }); } createAnalyser(): AnalyserNode { diff --git a/packages/react-native-audio-api/src/core/PeriodicWave.ts b/packages/react-native-audio-api/src/core/PeriodicWave.ts index b00c4505c..2452f7ba3 100644 --- a/packages/react-native-audio-api/src/core/PeriodicWave.ts +++ b/packages/react-native-audio-api/src/core/PeriodicWave.ts @@ -1,10 +1,62 @@ import { IPeriodicWave } from '../interfaces'; +import BaseAudioContext from './BaseAudioContext'; +import { TPeriodicWaveOptions } from '../types'; +import { PeriodicWaveConstraints } from '../defaults'; +import { NotSupportedError } from '../errors'; + +export function validatePeriodicWaveOptions( + sampleRate: number, + options?: TPeriodicWaveOptions +): TPeriodicWaveOptions { + let real: Float32Array | undefined; + let imag: Float32Array | undefined; + if (!options || (!options.real && !options.imag)) { + // default to a sine wave + if (sampleRate < 24000) { + real = new Float32Array(2048); + imag = new Float32Array(2048); + } else if (sampleRate < 88200) { + real = new Float32Array(4096); + imag = new Float32Array(4096); + } else { + real = new Float32Array(16384); + imag = new Float32Array(16384); + } + imag[1] = 1; + } else { + real = options?.real; + imag = options?.imag; + if (real && imag && real.length !== imag.length) { + throw new NotSupportedError( + "'real' and 'imag' arrays must have the same length" + ); + } + if (real && !imag) { + imag = new Float32Array(real.length); + } else if (!real && imag) { + real = new Float32Array(imag.length); + } + } + const norm: boolean = options?.disableNormalization + ? options.disableNormalization + : PeriodicWaveConstraints.disableNormalization!; + return { real, imag, disableNormalization: norm }; +} export default class PeriodicWave { /** @internal */ public readonly periodicWave: IPeriodicWave; - constructor(periodicWave: IPeriodicWave) { - this.periodicWave = periodicWave; + constructor(context: BaseAudioContext, options?: TPeriodicWaveOptions) { + const finalOptions = validatePeriodicWaveOptions( + context.sampleRate, + options + ); + console.log('finalOptions', finalOptions); + this.periodicWave = context.context.createPeriodicWave( + finalOptions.real, + finalOptions.imag, + finalOptions.disableNormalization! + ); } } diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index 7ed81d960..d1dbd5c3e 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -4,6 +4,7 @@ import { TSteroPannerOptions, TConvolverOptions, TConstantSourceOptions, + TPeriodicWaveConstraints, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -31,3 +32,7 @@ export const ConvolverOptions: TConvolverOptions = { export const ConstantSourceOptions: TConstantSourceOptions = { offset: 1, }; + +export const PeriodicWaveConstraints: TPeriodicWaveConstraints = { + disableNormalization: false, +}; diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 0f14284a7..7a86b0286 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -25,10 +25,6 @@ export type OscillatorType = | 'triangle' | 'custom'; -export interface PeriodicWaveConstraints { - disableNormalization: boolean; -} - export interface AudioContextOptions { sampleRate?: number; } @@ -79,3 +75,12 @@ export interface TWebConvolverOptions { export interface TConstantSourceOptions { offset?: number; } + +export interface TPeriodicWaveConstraints { + disableNormalization?: boolean; +} + +export interface TPeriodicWaveOptions extends TPeriodicWaveConstraints { + real: Float32Array; + imag: Float32Array; +} diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index 9165a8154..cad51bd90 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -1,6 +1,5 @@ import { ContextState, - PeriodicWaveConstraints, AudioContextOptions, AudioBufferBaseSourceNodeOptions, } from '../types'; @@ -19,6 +18,7 @@ import ConvolverNode from './ConvolverNode'; import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm'; import ConstantSourceNode from './ConstantSourceNode'; +import { PeriodicWaveConstraints } from '../defaults'; export default class AudioContext implements BaseAudioContext { readonly context: globalThis.AudioContext; @@ -132,9 +132,7 @@ export default class AudioContext implements BaseAudioContext { ); } - return new PeriodicWave( - this.context.createPeriodicWave(real, imag, constraints) - ); + return new PeriodicWave(this, { real, imag, ...constraints }); } createAnalyser(): AnalyserNode { diff --git a/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx b/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx index 5d2fa9a74..701bea3d1 100644 --- a/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/BaseAudioContext.tsx @@ -1,4 +1,4 @@ -import { ContextState, PeriodicWaveConstraints } from '../types'; +import { ContextState } from '../types'; import AnalyserNode from './AnalyserNode'; import AudioDestinationNode from './AudioDestinationNode'; import AudioBuffer from './AudioBuffer'; diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index 8d71b85cb..146679ec1 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -1,6 +1,5 @@ import { ContextState, - PeriodicWaveConstraints, OfflineAudioContextOptions, AudioBufferBaseSourceNodeOptions, } from '../types'; @@ -138,9 +137,7 @@ export default class OfflineAudioContext implements BaseAudioContext { ); } - return new PeriodicWave( - this.context.createPeriodicWave(real, imag, constraints) - ); + return new PeriodicWave(this, { real, imag, ...constraints }); } createAnalyser(): AnalyserNode { diff --git a/packages/react-native-audio-api/src/web-core/PeriodicWave.tsx b/packages/react-native-audio-api/src/web-core/PeriodicWave.tsx index bdf8979e1..89a7fe050 100644 --- a/packages/react-native-audio-api/src/web-core/PeriodicWave.tsx +++ b/packages/react-native-audio-api/src/web-core/PeriodicWave.tsx @@ -1,8 +1,21 @@ +import BaseAudioContext from './BaseAudioContext'; +import { TPeriodicWaveOptions } from '../types'; +import { validatePeriodicWaveOptions } from '../core/PeriodicWave'; + export default class PeriodicWave { /** @internal */ readonly periodicWave: globalThis.PeriodicWave; - constructor(periodicWave: globalThis.PeriodicWave) { + constructor(context: BaseAudioContext, options?: TPeriodicWaveOptions) { + const finalOptions = validatePeriodicWaveOptions( + context.sampleRate, + options + ); + const periodicWave = context.context.createPeriodicWave( + finalOptions.real, + finalOptions.imag, + { disableNormalization: finalOptions.disableNormalization } + ); this.periodicWave = periodicWave; } } From 1aa64432271dbe861559583a5ab212da1f01083f Mon Sep 17 00:00:00 2001 From: michal Date: Wed, 19 Nov 2025 11:23:27 +0100 Subject: [PATCH 11/16] feat: analyser node --- .../AudioVisualizer/AudioVisualizer.tsx | 5 +--- .../BaseAudioContextHostObject.cpp | 5 +++- .../audioapi/HostObjects/utils/NodeOptions.h | 7 ++++++ .../HostObjects/utils/NodeOptionsParser.h | 15 ++++++++++++ .../cpp/audioapi/core/BaseAudioContext.cpp | 13 ++++++----- .../cpp/audioapi/core/BaseAudioContext.h | 13 ++++++----- .../audioapi/core/analysis/AnalyserNode.cpp | 15 +++++++----- .../cpp/audioapi/core/analysis/AnalyserNode.h | 3 ++- .../src/core/AnalyserNode.ts | 23 ++++++++++++++++++- .../src/core/BaseAudioContext.ts | 2 +- .../react-native-audio-api/src/defaults.ts | 9 ++++++++ .../react-native-audio-api/src/interfaces.ts | 3 ++- packages/react-native-audio-api/src/types.ts | 7 ++++++ .../src/web-core/AnalyserNode.tsx | 21 ++++++++++++----- .../src/web-core/AudioContext.tsx | 3 +-- .../src/web-core/OfflineAudioContext.tsx | 2 +- 16 files changed, 110 insertions(+), 36 deletions(-) diff --git a/apps/common-app/src/examples/AudioVisualizer/AudioVisualizer.tsx b/apps/common-app/src/examples/AudioVisualizer/AudioVisualizer.tsx index 638239b00..9a11f9af2 100644 --- a/apps/common-app/src/examples/AudioVisualizer/AudioVisualizer.tsx +++ b/apps/common-app/src/examples/AudioVisualizer/AudioVisualizer.tsx @@ -105,10 +105,7 @@ const AudioVisualizer: React.FC = () => { } if (!analyserRef.current) { - analyserRef.current = audioContextRef.current.createAnalyser(); - analyserRef.current.fftSize = FFT_SIZE; - analyserRef.current.smoothingTimeConstant = 0.2; - + analyserRef.current = new AnalyserNode(audioContextRef.current, { fftSize: FFT_SIZE, smoothingTimeConstant: 0.2 }); analyserRef.current.connect(audioContextRef.current.destination); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index c6b74471f..a6810a9dc 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -253,7 +253,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createPeriodicWave) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createAnalyser) { - auto analyser = context_->createAnalyser(); + auto options = args[0].asObject(runtime); + std::shared_ptr analyserOptions = + audioapi::option_parser::parseAnalyserOptions(runtime, options); + auto analyser = context_->createAnalyser(analyserOptions); auto analyserHostObject = std::make_shared(analyser); return jsi::Object::createFromHostObject(runtime, analyserHostObject); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 64a99cfad..dc104e0fb 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -29,4 +29,11 @@ struct ConvolverOptions : AudioNodeOptions { struct ConstantSourceOptions { float offset = 1.0f; }; + +struct AnalyserOptions : AudioNodeOptions { + int fftSize = 2048; + float minDecibels = -100.0f; + float maxDecibels = -30.0f; + float smoothingTimeConstant = 0.8f; +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 0c5bbd76c..a70c1a94e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -83,4 +83,19 @@ std::shared_ptr parseConstantSourceOptions( options.offset = static_cast(optionsObject.getProperty(runtime, "offset").getNumber()); return std::make_shared(options); } + +std::shared_ptr parseAnalyserOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); + AnalyserOptions options(*nodeOptions.get()); + options.fftSize = static_cast(optionsObject.getProperty(runtime, "fftSize").getNumber()); + options.minDecibels = + static_cast(optionsObject.getProperty(runtime, "minDecibels").getNumber()); + options.maxDecibels = + static_cast(optionsObject.getProperty(runtime, "maxDecibels").getNumber()); + options.smoothingTimeConstant = + static_cast(optionsObject.getProperty(runtime, "smoothingTimeConstant").getNumber()); + return std::make_shared(options); +} } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index a75747c61..3a681843d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -66,7 +66,7 @@ double BaseAudioContext::getCurrentTime() const { return destination_->getCurrentTime(); } -std::shared_ptr BaseAudioContext::getDestination() { +std::shared_ptr BaseAudioContext::getDestination() const { return destination_; } @@ -117,7 +117,7 @@ std::shared_ptr BaseAudioContext::createOscillator() { } std::shared_ptr BaseAudioContext::createConstantSource( - const std::shared_ptr options) { + std::shared_ptr options) { auto constantSource = std::make_shared(this, options); nodeManager_->addSourceNode(constantSource); return constantSource; @@ -138,7 +138,7 @@ std::shared_ptr BaseAudioContext::createGain(std::shared_ptr BaseAudioContext::createStereoPanner( - const std::shared_ptr options) { + std::shared_ptr options) { auto stereoPanner = std::make_shared(this, options); nodeManager_->addProcessingNode(stereoPanner); return stereoPanner; @@ -175,14 +175,15 @@ std::shared_ptr BaseAudioContext::createPeriodicWave( return std::make_shared(sampleRate_, complexData, length, disableNormalization); } -std::shared_ptr BaseAudioContext::createAnalyser() { - auto analyser = std::make_shared(this); +std::shared_ptr BaseAudioContext::createAnalyser( + std::shared_ptr options) { + auto analyser = std::make_shared(this, options); nodeManager_->addProcessingNode(analyser); return analyser; } std::shared_ptr BaseAudioContext::createConvolver( - const std::shared_ptr options) { + std::shared_ptr options) { auto convolver = std::make_shared(this, options); nodeManager_->addProcessingNode(convolver); return convolver; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index 0b72f638a..dd470a497 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -39,6 +39,7 @@ class GainOptions; class StereoPannerOptions; class ConvolverOptions; class ConstantSourceOptions; +class AnalyserOptions; class BaseAudioContext { public: @@ -51,7 +52,7 @@ class BaseAudioContext { [[nodiscard]] float getSampleRate() const; [[nodiscard]] double getCurrentTime() const; [[nodiscard]] std::size_t getCurrentSampleFrame() const; - std::shared_ptr getDestination(); + std::shared_ptr getDestination() const; std::shared_ptr createRecorderAdapter(); std::shared_ptr createWorkletSourceNode( @@ -70,11 +71,11 @@ class BaseAudioContext { bool shouldLockRuntime = true); std::shared_ptr createOscillator(); std::shared_ptr createConstantSource( - const std::shared_ptr options); + std::shared_ptr options); std::shared_ptr createStreamer(); - std::shared_ptr createGain(const std::shared_ptr options); + std::shared_ptr createGain(std::shared_ptr options); std::shared_ptr createStereoPanner( - const std::shared_ptr options); + std::shared_ptr options); std::shared_ptr createBiquadFilter(); std::shared_ptr createBufferSource(bool pitchCorrection); std::shared_ptr createBufferQueueSource(bool pitchCorrection); @@ -84,8 +85,8 @@ class BaseAudioContext { const std::vector> &complexData, bool disableNormalization, int length); - std::shared_ptr createAnalyser(); - std::shared_ptr createConvolver(const std::shared_ptr options); + std::shared_ptr createAnalyser(std::shared_ptr options); + std::shared_ptr createConvolver(std::shared_ptr options); std::shared_ptr getBasicWaveForm(OscillatorType type); [[nodiscard]] float getNyquistFrequency() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp index c01d3f5bd..860ae0636 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,12 +13,14 @@ #include namespace audioapi { -AnalyserNode::AnalyserNode(audioapi::BaseAudioContext *context) - : AudioNode(context), - fftSize_(2048), - minDecibels_(-100), - maxDecibels_(-30), - smoothingTimeConstant_(0.8), +AnalyserNode::AnalyserNode( + audioapi::BaseAudioContext *context, + std::shared_ptr options) + : AudioNode(context, options), + fftSize_(options->fftSize), + minDecibels_(options->minDecibels), + maxDecibels_(options->maxDecibels), + smoothingTimeConstant_(options->smoothingTimeConstant), windowType_(WindowType::BLACKMAN) { inputBuffer_ = std::make_unique(MAX_FFT_SIZE * 2); tempBuffer_ = std::make_unique(fftSize_); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h index b62d0058a..d8dc2691b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h @@ -15,11 +15,12 @@ namespace audioapi { class AudioBus; class AudioArray; class CircularAudioArray; +class AnalyserOptions; class AnalyserNode : public AudioNode { public: enum class WindowType { BLACKMAN, HANN }; - explicit AnalyserNode(BaseAudioContext *context); + explicit AnalyserNode(BaseAudioContext *context, std::shared_ptr options); int getFftSize() const; int getFrequencyBinCount() const; diff --git a/packages/react-native-audio-api/src/core/AnalyserNode.ts b/packages/react-native-audio-api/src/core/AnalyserNode.ts index d2d8176e1..6af3e4bdb 100644 --- a/packages/react-native-audio-api/src/core/AnalyserNode.ts +++ b/packages/react-native-audio-api/src/core/AnalyserNode.ts @@ -1,6 +1,8 @@ +import BaseAudioContext from './BaseAudioContext'; +import { AnalyserOptions } from '../defaults'; import { IndexSizeError } from '../errors'; import { IAnalyserNode } from '../interfaces'; -import { WindowType } from '../types'; +import { WindowType, TAnalyserOptions } from '../types'; import AudioNode from './AudioNode'; export default class AnalyserNode extends AudioNode { @@ -8,6 +10,25 @@ export default class AnalyserNode extends AudioNode { 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, ]; + constructor(context: BaseAudioContext, options?: TAnalyserOptions) { + const finalOptions: TAnalyserOptions = { + ...AnalyserOptions, + ...options, + }; + + if (!AnalyserNode.allowedFFTSize.includes(finalOptions.fftSize!)) { + throw new IndexSizeError( + `fftSize must be one of the following values: ${AnalyserNode.allowedFFTSize.join( + ', ' + )}` + ); + } + console.log('finalOptions', finalOptions); + const analyserNode: IAnalyserNode = + context.context.createAnalyser(finalOptions); + super(context, analyserNode); + } + public get fftSize(): number { return (this.node as IAnalyserNode).fftSize; } diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 9c0e4b724..00be97846 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -266,7 +266,7 @@ export default class BaseAudioContext { } createAnalyser(): AnalyserNode { - return new AnalyserNode(this, this.context.createAnalyser()); + return new AnalyserNode(this); } createConvolver(): ConvolverNode { diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index d1dbd5c3e..627611784 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -5,6 +5,7 @@ import { TConvolverOptions, TConstantSourceOptions, TPeriodicWaveConstraints, + TAnalyserOptions, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -24,6 +25,14 @@ export const SteroPannerOptions: TSteroPannerOptions = { pan: 0, }; +export const AnalyserOptions: TAnalyserOptions = { + ...AudioNodeOptions, + fftSize: 2048, + minDecibels: -100, + maxDecibels: -30, + smoothingTimeConstant: 0.8, +}; + export const ConvolverOptions: TConvolverOptions = { ...AudioNodeOptions, disableNormalization: false, diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 3777bb496..c4a84f66b 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -10,6 +10,7 @@ import { TSteroPannerOptions, TConvolverOptions, TConstantSourceOptions, + TAnalyserOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -84,7 +85,7 @@ export interface IBaseAudioContext { imag: Float32Array, disableNormalization: boolean ) => IPeriodicWave; - createAnalyser: () => IAnalyserNode; + createAnalyser: (analyserOptions: TAnalyserOptions) => IAnalyserNode; createConvolver: (convolverOptions: TConvolverOptions) => IConvolverNode; createStreamer: () => IStreamerNode; } diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 7a86b0286..b8c3df165 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -62,6 +62,13 @@ export interface TSteroPannerOptions extends TAudioNodeOptions { pan?: number; } +export interface TAnalyserOptions extends TAudioNodeOptions { + fftSize?: number; + minDecibels?: number; + maxDecibels?: number; + smoothingTimeConstant?: number; +} + export interface TConvolverOptions extends TAudioNodeOptions { buffer?: AudioBuffer | null; disableNormalization?: boolean; diff --git a/packages/react-native-audio-api/src/web-core/AnalyserNode.tsx b/packages/react-native-audio-api/src/web-core/AnalyserNode.tsx index c74b67e05..e8a360163 100644 --- a/packages/react-native-audio-api/src/web-core/AnalyserNode.tsx +++ b/packages/react-native-audio-api/src/web-core/AnalyserNode.tsx @@ -1,5 +1,5 @@ import AudioNode from './AudioNode'; -import { WindowType } from '../types'; +import { WindowType, TAnalyserOptions } from '../types'; import BaseAudioContext from './BaseAudioContext'; export default class AnalyserNode extends AudioNode { @@ -9,7 +9,8 @@ export default class AnalyserNode extends AudioNode { maxDecibels: number; smoothingTimeConstant: number; - constructor(context: BaseAudioContext, node: globalThis.AnalyserNode) { + constructor(context: BaseAudioContext, analyserOptions?: TAnalyserOptions) { + const node = new globalThis.AnalyserNode(context.context, analyserOptions); super(context, node); this.fftSize = node.fftSize; @@ -30,18 +31,26 @@ export default class AnalyserNode extends AudioNode { } public getByteFrequencyData(array: Uint8Array): void { - (this.node as globalThis.AnalyserNode).getByteFrequencyData(array); + (this.node as globalThis.AnalyserNode).getByteFrequencyData( + array as Uint8Array + ); } public getByteTimeDomainData(array: Uint8Array): void { - (this.node as globalThis.AnalyserNode).getByteTimeDomainData(array); + (this.node as globalThis.AnalyserNode).getByteTimeDomainData( + array as Uint8Array + ); } public getFloatFrequencyData(array: Float32Array): void { - (this.node as globalThis.AnalyserNode).getFloatFrequencyData(array); + (this.node as globalThis.AnalyserNode).getFloatFrequencyData( + array as Float32Array + ); } public getFloatTimeDomainData(array: Float32Array): void { - (this.node as globalThis.AnalyserNode).getFloatTimeDomainData(array); + (this.node as globalThis.AnalyserNode).getFloatTimeDomainData( + array as Float32Array + ); } } diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index cad51bd90..089dd3e45 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -18,7 +18,6 @@ import ConvolverNode from './ConvolverNode'; import { globalWasmPromise, globalTag } from './custom/LoadCustomWasm'; import ConstantSourceNode from './ConstantSourceNode'; -import { PeriodicWaveConstraints } from '../defaults'; export default class AudioContext implements BaseAudioContext { readonly context: globalThis.AudioContext; @@ -136,7 +135,7 @@ export default class AudioContext implements BaseAudioContext { } createAnalyser(): AnalyserNode { - return new AnalyserNode(this, this.context.createAnalyser()); + return new AnalyserNode(this); } async decodeAudioDataSource(source: string): Promise { diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index 146679ec1..eb3e0cde9 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -141,7 +141,7 @@ export default class OfflineAudioContext implements BaseAudioContext { } createAnalyser(): AnalyserNode { - return new AnalyserNode(this, this.context.createAnalyser()); + return new AnalyserNode(this); } async decodeAudioDataSource(source: string): Promise { From 3b0b1b781944fb1e7f98aeb0379e8d9be2a07937 Mon Sep 17 00:00:00 2001 From: michal Date: Wed, 19 Nov 2025 12:36:18 +0100 Subject: [PATCH 12/16] feat: biquad filter --- .../BaseAudioContextHostObject.cpp | 5 ++- .../audioapi/HostObjects/utils/NodeOptions.h | 10 ++++++ .../HostObjects/utils/NodeOptionsParser.h | 35 +++++++++++++++++++ .../cpp/audioapi/core/BaseAudioContext.cpp | 5 +-- .../cpp/audioapi/core/BaseAudioContext.h | 4 ++- .../core/effects/BiquadFilterNode.cpp | 19 +++++----- .../audioapi/core/effects/BiquadFilterNode.h | 5 ++- packages/react-native-audio-api/src/api.ts | 1 - .../react-native-audio-api/src/api.web.ts | 1 - .../src/core/BaseAudioContext.ts | 2 +- .../src/core/BiquadFilterNode.ts | 11 ++++-- .../react-native-audio-api/src/defaults.ts | 10 ++++++ .../react-native-audio-api/src/interfaces.ts | 6 ++-- packages/react-native-audio-api/src/types.ts | 10 +++++- .../src/web-core/AudioContext.tsx | 4 +-- .../src/web-core/BiquadFilterNode.tsx | 8 +++-- .../src/web-core/ConstantSourceNode.tsx | 4 ++- .../src/web-core/OfflineAudioContext.tsx | 4 +-- 18 files changed, 116 insertions(+), 28 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index a6810a9dc..e70be7a16 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -195,7 +195,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createStereoPanner) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBiquadFilter) { - auto biquadFilter = context_->createBiquadFilter(); + auto options = args[0].asObject(runtime); + std::shared_ptr biquadFilterOptions = + audioapi::option_parser::parseBiquadFilterOptions(runtime, options); + auto biquadFilter = context_->createBiquadFilter(biquadFilterOptions); auto biquadFilterHostObject = std::make_shared(biquadFilter); return jsi::Object::createFromHostObject(runtime, biquadFilterHostObject); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index dc104e0fb..a98a5f7c4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -36,4 +37,13 @@ struct AnalyserOptions : AudioNodeOptions { float maxDecibels = -30.0f; float smoothingTimeConstant = 0.8f; }; + +struct BiquadFilterOptions : AudioNodeOptions { + BiquadFilterType type = + BiquadFilterType::LOWPASS; // Uncomment and define BiquadFilterType enum as needed + float frequency = 350.0f; + float detune = 0.0f; + float Q = 1.0f; + float gain = 0.0f; +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index a70c1a94e..56ff61528 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -98,4 +98,39 @@ std::shared_ptr parseAnalyserOptions( static_cast(optionsObject.getProperty(runtime, "smoothingTimeConstant").getNumber()); return std::make_shared(options); } + +std::shared_ptr parseBiquadFilterOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); + BiquadFilterOptions options(*nodeOptions.get()); + + auto typeStr = optionsObject.getProperty(runtime, "type").asString(runtime).utf8(runtime); + + if (typeStr == "lowpass") { + options.type = BiquadFilterType::LOWPASS; + } else if (typeStr == "highpass") { + options.type = BiquadFilterType::HIGHPASS; + } else if (typeStr == "bandpass") { + options.type = BiquadFilterType::BANDPASS; + } else if (typeStr == "lowshelf") { + options.type = BiquadFilterType::LOWSHELF; + } else if (typeStr == "highshelf") { + options.type = BiquadFilterType::HIGHSHELF; + } else if (typeStr == "peaking") { + options.type = BiquadFilterType::PEAKING; + } else if (typeStr == "notch") { + options.type = BiquadFilterType::NOTCH; + } else if (typeStr == "allpass") { + options.type = BiquadFilterType::ALLPASS; + } + + options.frequency = + static_cast(optionsObject.getProperty(runtime, "frequency").getNumber()); + options.detune = static_cast(optionsObject.getProperty(runtime, "detune").getNumber()); + options.Q = static_cast(optionsObject.getProperty(runtime, "Q").getNumber()); + options.gain = static_cast(optionsObject.getProperty(runtime, "gain").getNumber()); + + return std::make_shared(options); +} } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index 3a681843d..94f20bd06 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -144,8 +144,9 @@ std::shared_ptr BaseAudioContext::createStereoPanner( return stereoPanner; } -std::shared_ptr BaseAudioContext::createBiquadFilter() { - auto biquadFilter = std::make_shared(this); +std::shared_ptr BaseAudioContext::createBiquadFilter( + std::shared_ptr options) { + auto biquadFilter = std::make_shared(this, options); nodeManager_->addProcessingNode(biquadFilter); return biquadFilter; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index dd470a497..3dbc8c92a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -40,6 +40,7 @@ class StereoPannerOptions; class ConvolverOptions; class ConstantSourceOptions; class AnalyserOptions; +class BiquadFilterOptions; class BaseAudioContext { public: @@ -76,7 +77,8 @@ class BaseAudioContext { std::shared_ptr createGain(std::shared_ptr options); std::shared_ptr createStereoPanner( std::shared_ptr options); - std::shared_ptr createBiquadFilter(); + std::shared_ptr createBiquadFilter( + std::shared_ptr options); std::shared_ptr createBufferSource(bool pitchCorrection); std::shared_ptr createBufferQueueSource(bool pitchCorrection); static std::shared_ptr diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp index 33f1f3aaa..b502e2cca 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp @@ -26,6 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -38,21 +39,23 @@ namespace audioapi { -BiquadFilterNode::BiquadFilterNode(BaseAudioContext *context) : AudioNode(context) { - frequencyParam_ = - std::make_shared(350.0, 0.0f, context->getNyquistFrequency(), context); +BiquadFilterNode::BiquadFilterNode( + BaseAudioContext *context, + std::shared_ptr options) + : AudioNode(context, options) { + frequencyParam_ = std::make_shared( + options->frequency, 0.0f, context->getNyquistFrequency(), context); detuneParam_ = std::make_shared( - 0.0f, + options->detune, -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT, 1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT, context); QParam_ = std::make_shared( - 1.0f, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); + options->Q, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); gainParam_ = std::make_shared( - 0.0f, MOST_NEGATIVE_SINGLE_FLOAT, 40 * LOG10_MOST_POSITIVE_SINGLE_FLOAT, context); - type_ = BiquadFilterType::LOWPASS; + options->gain, MOST_NEGATIVE_SINGLE_FLOAT, 40 * LOG10_MOST_POSITIVE_SINGLE_FLOAT, context); + type_ = options->type; isInitialized_ = true; - channelCountMode_ = ChannelCountMode::MAX; } std::string BiquadFilterNode::getType() { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h index 6a7a606d1..3d48377ad 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h @@ -46,6 +46,7 @@ namespace audioapi { class AudioBus; +class BiquadFilterOptions; class BiquadFilterNode : public AudioNode { #ifdef AUDIO_API_TEST_SUITE @@ -54,7 +55,9 @@ class BiquadFilterNode : public AudioNode { #endif public: - explicit BiquadFilterNode(BaseAudioContext *context); + explicit BiquadFilterNode( + BaseAudioContext *context, + std::shared_ptr options); [[nodiscard]] std::string getType(); void setType(const std::string &type); diff --git a/packages/react-native-audio-api/src/api.ts b/packages/react-native-audio-api/src/api.ts index 6f7500e01..c667c0ff6 100644 --- a/packages/react-native-audio-api/src/api.ts +++ b/packages/react-native-audio-api/src/api.ts @@ -82,7 +82,6 @@ export { default as PeriodicWave } from './core/PeriodicWave'; export { OscillatorType, - BiquadFilterType, ChannelCountMode, ChannelInterpretation, ContextState, diff --git a/packages/react-native-audio-api/src/api.web.ts b/packages/react-native-audio-api/src/api.web.ts index bdf1474c0..09259d993 100644 --- a/packages/react-native-audio-api/src/api.web.ts +++ b/packages/react-native-audio-api/src/api.web.ts @@ -20,7 +20,6 @@ export * from './web-core/custom'; export { OscillatorType, - BiquadFilterType, ChannelCountMode, ChannelInterpretation, ContextState, diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 00be97846..a12b62937 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -199,7 +199,7 @@ export default class BaseAudioContext { } createBiquadFilter(): BiquadFilterNode { - return new BiquadFilterNode(this, this.context.createBiquadFilter()); + return new BiquadFilterNode(this); } createBufferSource( diff --git a/packages/react-native-audio-api/src/core/BiquadFilterNode.ts b/packages/react-native-audio-api/src/core/BiquadFilterNode.ts index 68db7c02a..b85475fc5 100644 --- a/packages/react-native-audio-api/src/core/BiquadFilterNode.ts +++ b/packages/react-native-audio-api/src/core/BiquadFilterNode.ts @@ -3,7 +3,8 @@ import { IBiquadFilterNode } from '../interfaces'; import AudioNode from './AudioNode'; import AudioParam from './AudioParam'; import BaseAudioContext from './BaseAudioContext'; -import { BiquadFilterType } from '../types'; +import { BiquadFilterOptions } from '../defaults'; +import { TBiquadFilterOptions } from '../types'; export default class BiquadFilterNode extends AudioNode { readonly frequency: AudioParam; @@ -11,7 +12,13 @@ export default class BiquadFilterNode extends AudioNode { readonly Q: AudioParam; readonly gain: AudioParam; - constructor(context: BaseAudioContext, biquadFilter: IBiquadFilterNode) { + constructor(context: BaseAudioContext, options?: TBiquadFilterOptions) { + const finalOptions: TBiquadFilterOptions = { + ...BiquadFilterOptions, + ...options, + }; + const biquadFilter: IBiquadFilterNode = + context.context.createBiquadFilter(finalOptions); super(context, biquadFilter); this.frequency = new AudioParam(biquadFilter.frequency, context); this.detune = new AudioParam(biquadFilter.detune, context); diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index 627611784..064ea0220 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -6,6 +6,7 @@ import { TConstantSourceOptions, TPeriodicWaveConstraints, TAnalyserOptions, + TBiquadFilterOptions, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -33,6 +34,15 @@ export const AnalyserOptions: TAnalyserOptions = { smoothingTimeConstant: 0.8, }; +export const BiquadFilterOptions: TBiquadFilterOptions = { + ...AudioNodeOptions, + Q: 1, + detune: 0, + frequency: 350, + gain: 0, + type: 'lowpass', +}; + export const ConvolverOptions: TConvolverOptions = { ...AudioNodeOptions, disableNormalization: false, diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index c4a84f66b..7cd746a55 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -1,6 +1,5 @@ import { AudioEventCallback, AudioEventName } from './events/types'; import { - BiquadFilterType, ChannelCountMode, ChannelInterpretation, ContextState, @@ -11,6 +10,7 @@ import { TConvolverOptions, TConstantSourceOptions, TAnalyserOptions, + TBiquadFilterOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -70,7 +70,9 @@ export interface IBaseAudioContext { createStereoPanner( stereoPannerOptions: TSteroPannerOptions ): IStereoPannerNode; - createBiquadFilter: () => IBiquadFilterNode; + createBiquadFilter: ( + biquadFilterOptions: TBiquadFilterOptions + ) => IBiquadFilterNode; createBufferSource: (pitchCorrection: boolean) => IAudioBufferSourceNode; createBufferQueueSource: ( pitchCorrection: boolean diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index b8c3df165..3be45fc91 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -4,7 +4,7 @@ export type ChannelCountMode = 'max' | 'clamped-max' | 'explicit'; export type ChannelInterpretation = 'speakers' | 'discrete'; -export type BiquadFilterType = +type BiquadFilterType = | 'lowpass' | 'highpass' | 'bandpass' @@ -69,6 +69,14 @@ export interface TAnalyserOptions extends TAudioNodeOptions { smoothingTimeConstant?: number; } +export interface TBiquadFilterOptions extends TAudioNodeOptions { + type?: BiquadFilterType; + frequency?: number; + detune?: number; + Q?: number; + gain?: number; +} + export interface TConvolverOptions extends TAudioNodeOptions { buffer?: AudioBuffer | null; disableNormalization?: boolean; diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index 089dd3e45..2cd837244 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -55,7 +55,7 @@ export default class AudioContext implements BaseAudioContext { } createConstantSource(): ConstantSourceNode { - return new ConstantSourceNode(this, this.context.createConstantSource()); + return new ConstantSourceNode(this); } createGain(): GainNode { @@ -67,7 +67,7 @@ export default class AudioContext implements BaseAudioContext { } createBiquadFilter(): BiquadFilterNode { - return new BiquadFilterNode(this, this.context.createBiquadFilter()); + return new BiquadFilterNode(this); } createConvolver(): ConvolverNode { diff --git a/packages/react-native-audio-api/src/web-core/BiquadFilterNode.tsx b/packages/react-native-audio-api/src/web-core/BiquadFilterNode.tsx index 4a8a4df21..dd540369a 100644 --- a/packages/react-native-audio-api/src/web-core/BiquadFilterNode.tsx +++ b/packages/react-native-audio-api/src/web-core/BiquadFilterNode.tsx @@ -1,8 +1,8 @@ import AudioParam from './AudioParam'; import AudioNode from './AudioNode'; import BaseAudioContext from './BaseAudioContext'; -import { BiquadFilterType } from '../types'; import { InvalidAccessError } from '../errors'; +import { TBiquadFilterOptions } from '../types'; export default class BiquadFilterNode extends AudioNode { readonly frequency: AudioParam; @@ -12,8 +12,12 @@ export default class BiquadFilterNode extends AudioNode { constructor( context: BaseAudioContext, - biquadFilter: globalThis.BiquadFilterNode + biquadFilterOptions?: TBiquadFilterOptions ) { + const biquadFilter = new globalThis.BiquadFilterNode( + context.context, + biquadFilterOptions + ); super(context, biquadFilter); this.frequency = new AudioParam(biquadFilter.frequency, context); this.detune = new AudioParam(biquadFilter.detune, context); diff --git a/packages/react-native-audio-api/src/web-core/ConstantSourceNode.tsx b/packages/react-native-audio-api/src/web-core/ConstantSourceNode.tsx index 25560b4a6..9d8e745c1 100644 --- a/packages/react-native-audio-api/src/web-core/ConstantSourceNode.tsx +++ b/packages/react-native-audio-api/src/web-core/ConstantSourceNode.tsx @@ -1,11 +1,13 @@ import AudioParam from './AudioParam'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import BaseAudioContext from './BaseAudioContext'; +import { TConstantSourceOptions } from '../types'; export default class ConstantSourceNode extends AudioScheduledSourceNode { readonly offset: AudioParam; - constructor(context: BaseAudioContext, node: globalThis.ConstantSourceNode) { + constructor(context: BaseAudioContext, options?: TConstantSourceOptions) { + const node = new globalThis.ConstantSourceNode(context.context, options); super(context, node); this.offset = new AudioParam(node.offset, context); } diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index eb3e0cde9..ee3cc4ae8 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -61,7 +61,7 @@ export default class OfflineAudioContext implements BaseAudioContext { } createConstantSource(): ConstantSourceNode { - return new ConstantSourceNode(this, this.context.createConstantSource()); + return new ConstantSourceNode(this); } createGain(): GainNode { @@ -73,7 +73,7 @@ export default class OfflineAudioContext implements BaseAudioContext { } createBiquadFilter(): BiquadFilterNode { - return new BiquadFilterNode(this, this.context.createBiquadFilter()); + return new BiquadFilterNode(this); } createConvolver(): ConvolverNode { From 1dcdaa66aafe0b62da5d95b0fc91d17ec2a85ded Mon Sep 17 00:00:00 2001 From: michal Date: Thu, 20 Nov 2025 16:10:44 +0100 Subject: [PATCH 13/16] feat: oscillator --- .../BaseAudioContextHostObject.cpp | 5 ++- .../audioapi/HostObjects/utils/NodeOptions.h | 12 +++++-- .../HostObjects/utils/NodeOptionsParser.h | 35 +++++++++++++++++++ .../cpp/audioapi/core/BaseAudioContext.cpp | 5 +-- .../cpp/audioapi/core/BaseAudioContext.h | 3 +- .../audioapi/core/sources/OscillatorNode.cpp | 21 ++++++++--- .../audioapi/core/sources/OscillatorNode.h | 3 +- .../src/core/BaseAudioContext.ts | 2 +- .../src/core/OscillatorNode.ts | 21 +++++++++-- .../src/core/PeriodicWave.ts | 1 - .../react-native-audio-api/src/defaults.ts | 8 +++++ .../react-native-audio-api/src/interfaces.ts | 3 +- packages/react-native-audio-api/src/types.ts | 8 +++++ .../src/web-core/AudioContext.tsx | 2 +- .../src/web-core/OfflineAudioContext.tsx | 2 +- .../src/web-core/OscillatorNode.tsx | 5 +-- 16 files changed, 115 insertions(+), 21 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index e70be7a16..24f4b637b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -154,7 +154,10 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createRecorderAdapter) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createOscillator) { - auto oscillator = context_->createOscillator(); + auto options = args[0].asObject(runtime); + std::shared_ptr oscillatorOptions = + audioapi::option_parser::parseOscillatorOptions(runtime, options); + auto oscillator = context_->createOscillator(oscillatorOptions); auto oscillatorHostObject = std::make_shared(oscillator); return jsi::Object::createFromHostObject(runtime, oscillatorHostObject); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index a98a5f7c4..751a0096a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -2,10 +2,12 @@ #include +#include #include #include #include #include +#include namespace audioapi { struct AudioNodeOptions { @@ -39,11 +41,17 @@ struct AnalyserOptions : AudioNodeOptions { }; struct BiquadFilterOptions : AudioNodeOptions { - BiquadFilterType type = - BiquadFilterType::LOWPASS; // Uncomment and define BiquadFilterType enum as needed + BiquadFilterType type = BiquadFilterType::LOWPASS; float frequency = 350.0f; float detune = 0.0f; float Q = 1.0f; float gain = 0.0f; }; + +struct OscillatorOptions { + std::shared_ptr periodicWave = nullptr; + float frequency = 440.0f; + float detune = 0.0f; + OscillatorType type = OscillatorType::SINE; +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 56ff61528..6abc0f415 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -133,4 +134,38 @@ std::shared_ptr parseBiquadFilterOptions( return std::make_shared(options); } + +std::shared_ptr parseOscillatorOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr nodeOptions = parseAudioNodeOptions(runtime, optionsObject); + OscillatorOptions options; + + auto typeStr = optionsObject.getProperty(runtime, "type").asString(runtime).utf8(runtime); + + if (typeStr == "sine") { + options.type = OscillatorType::SINE; + } else if (typeStr == "square") { + options.type = OscillatorType::SQUARE; + } else if (typeStr == "sawtooth") { + options.type = OscillatorType::SAWTOOTH; + } else if (typeStr == "triangle") { + options.type = OscillatorType::TRIANGLE; + } else if (typeStr == "custom") { + options.type = OscillatorType::CUSTOM; + } + + options.frequency = + static_cast(optionsObject.getProperty(runtime, "frequency").getNumber()); + options.detune = static_cast(optionsObject.getProperty(runtime, "detune").getNumber()); + + if (optionsObject.hasProperty(runtime, "periodicWave")) { + auto periodicWaveHostObject = optionsObject.getProperty(runtime, "periodicWave") + .getObject(runtime) + .asHostObject(runtime); + options.periodicWave = periodicWaveHostObject->periodicWave_; + } + + return std::make_shared(options); +} } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index 94f20bd06..d76c312f5 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -110,8 +110,9 @@ std::shared_ptr BaseAudioContext::createRecorderAdapter() { return recorderAdapter; } -std::shared_ptr BaseAudioContext::createOscillator() { - auto oscillator = std::make_shared(this); +std::shared_ptr BaseAudioContext::createOscillator( + std::shared_ptr options) { + auto oscillator = std::make_shared(this, options); nodeManager_->addSourceNode(oscillator); return oscillator; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index 3dbc8c92a..d0d0dc398 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -41,6 +41,7 @@ class ConvolverOptions; class ConstantSourceOptions; class AnalyserOptions; class BiquadFilterOptions; +class OscillatorOptions; class BaseAudioContext { public: @@ -70,7 +71,7 @@ class BaseAudioContext { std::shared_ptr &shareableWorklet, std::weak_ptr runtime, bool shouldLockRuntime = true); - std::shared_ptr createOscillator(); + std::shared_ptr createOscillator(std::shared_ptr options); std::shared_ptr createConstantSource( std::shared_ptr options); std::shared_ptr createStreamer(); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp index fbf7d29fd..810e6ade1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,16 +9,26 @@ namespace audioapi { -OscillatorNode::OscillatorNode(BaseAudioContext *context) : AudioScheduledSourceNode(context) { +OscillatorNode::OscillatorNode( + BaseAudioContext *context, + std::shared_ptr options) + : AudioScheduledSourceNode(context) { frequencyParam_ = std::make_shared( - 444.0, -context_->getNyquistFrequency(), context_->getNyquistFrequency(), context); + options->frequency, + -context_->getNyquistFrequency(), + context_->getNyquistFrequency(), + context); detuneParam_ = std::make_shared( - 0.0, + options->detune, -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT, 1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT, context); - type_ = OscillatorType::SINE; - periodicWave_ = context_->getBasicWaveForm(type_); + type_ = options->type; + if (options->periodicWave) { + periodicWave_ = options->periodicWave; + } else { + periodicWave_ = context_->getBasicWaveForm(type_); + } audioBus_ = std::make_shared(RENDER_QUANTUM_SIZE, 1, context_->getSampleRate()); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.h index 47ac310d0..3a643e456 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.h @@ -13,10 +13,11 @@ namespace audioapi { class AudioBus; +class OscillatorOptions; class OscillatorNode : public AudioScheduledSourceNode { public: - explicit OscillatorNode(BaseAudioContext *context); + explicit OscillatorNode(BaseAudioContext *context, std::shared_ptr options); [[nodiscard]] std::shared_ptr getFrequencyParam() const; [[nodiscard]] std::shared_ptr getDetuneParam() const; diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index a12b62937..9686d0c94 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -179,7 +179,7 @@ export default class BaseAudioContext { } createOscillator(): OscillatorNode { - return new OscillatorNode(this, this.context.createOscillator()); + return new OscillatorNode(this); } createStreamer(): StreamerNode { diff --git a/packages/react-native-audio-api/src/core/OscillatorNode.ts b/packages/react-native-audio-api/src/core/OscillatorNode.ts index 48296831c..df6253b03 100644 --- a/packages/react-native-audio-api/src/core/OscillatorNode.ts +++ b/packages/react-native-audio-api/src/core/OscillatorNode.ts @@ -1,17 +1,34 @@ import { IOscillatorNode } from '../interfaces'; -import { OscillatorType } from '../types'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import AudioParam from './AudioParam'; import BaseAudioContext from './BaseAudioContext'; import PeriodicWave from './PeriodicWave'; import { InvalidStateError } from '../errors'; import { EventEmptyType } from '../events/types'; +import { OscillatorOptions } from '../defaults'; +import { TOscillatorOptions } from '../types'; export default class OscillatorNode extends AudioScheduledSourceNode { readonly frequency: AudioParam; readonly detune: AudioParam; - constructor(context: BaseAudioContext, node: IOscillatorNode) { + constructor(context: BaseAudioContext, options?: TOscillatorOptions) { + const finalOptions: TOscillatorOptions = { + ...OscillatorOptions, + ...options, + }; + + if (finalOptions.type === 'custom' && !finalOptions.periodicWave) { + throw new InvalidStateError( + "'type' cannot be set to 'custom' without providing a 'periodicWave'." + ); + } + + if (finalOptions.periodicWave) { + finalOptions.type = 'custom'; + } + + const node = context.context.createOscillator(finalOptions); super(context, node); this.frequency = new AudioParam(node.frequency, context); this.detune = new AudioParam(node.detune, context); diff --git a/packages/react-native-audio-api/src/core/PeriodicWave.ts b/packages/react-native-audio-api/src/core/PeriodicWave.ts index 2452f7ba3..58c45c014 100644 --- a/packages/react-native-audio-api/src/core/PeriodicWave.ts +++ b/packages/react-native-audio-api/src/core/PeriodicWave.ts @@ -52,7 +52,6 @@ export default class PeriodicWave { context.sampleRate, options ); - console.log('finalOptions', finalOptions); this.periodicWave = context.context.createPeriodicWave( finalOptions.real, finalOptions.imag, diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index 064ea0220..fdb582550 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -7,6 +7,7 @@ import { TPeriodicWaveConstraints, TAnalyserOptions, TBiquadFilterOptions, + TOscillatorOptions, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -55,3 +56,10 @@ export const ConstantSourceOptions: TConstantSourceOptions = { export const PeriodicWaveConstraints: TPeriodicWaveConstraints = { disableNormalization: false, }; + +export const OscillatorOptions: TOscillatorOptions = { + ...AudioNodeOptions, + type: 'sine', + frequency: 440, + detune: 0, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 7cd746a55..0e890f7ac 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -11,6 +11,7 @@ import { TConstantSourceOptions, TAnalyserOptions, TBiquadFilterOptions, + TOscillatorOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -62,7 +63,7 @@ export interface IBaseAudioContext { shareableWorklet: ShareableWorkletCallback, shouldUseUiRuntime: boolean ): IWorkletProcessingNode; - createOscillator(): IOscillatorNode; + createOscillator(oscillatorOptions: TOscillatorOptions): IOscillatorNode; createConstantSource( constantSourceOptions: TConstantSourceOptions ): IConstantSourceNode; diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 3be45fc91..ba5ed4ae3 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -1,4 +1,5 @@ import AudioBuffer from './core/AudioBuffer'; +import PeriodicWave from './core/PeriodicWave'; export type ChannelCountMode = 'max' | 'clamped-max' | 'explicit'; @@ -77,6 +78,13 @@ export interface TBiquadFilterOptions extends TAudioNodeOptions { gain?: number; } +export interface TOscillatorOptions { + type?: OscillatorType; + frequency?: number; + detune?: number; + periodicWave?: PeriodicWave; +} + export interface TConvolverOptions extends TAudioNodeOptions { buffer?: AudioBuffer | null; disableNormalization?: boolean; diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index 2cd837244..66689558d 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -51,7 +51,7 @@ export default class AudioContext implements BaseAudioContext { } createOscillator(): OscillatorNode { - return new OscillatorNode(this, this.context.createOscillator()); + return new OscillatorNode(this); } createConstantSource(): ConstantSourceNode { diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index ee3cc4ae8..8bc7eef84 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -57,7 +57,7 @@ export default class OfflineAudioContext implements BaseAudioContext { } createOscillator(): OscillatorNode { - return new OscillatorNode(this, this.context.createOscillator()); + return new OscillatorNode(this); } createConstantSource(): ConstantSourceNode { diff --git a/packages/react-native-audio-api/src/web-core/OscillatorNode.tsx b/packages/react-native-audio-api/src/web-core/OscillatorNode.tsx index 612cdd9f6..d180aa596 100644 --- a/packages/react-native-audio-api/src/web-core/OscillatorNode.tsx +++ b/packages/react-native-audio-api/src/web-core/OscillatorNode.tsx @@ -1,4 +1,4 @@ -import { OscillatorType } from '../types'; +import { OscillatorType, TOscillatorOptions } from '../types'; import { InvalidStateError } from '../errors'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import BaseAudioContext from './BaseAudioContext'; @@ -9,7 +9,8 @@ export default class OscillatorNode extends AudioScheduledSourceNode { readonly frequency: AudioParam; readonly detune: AudioParam; - constructor(context: BaseAudioContext, node: globalThis.OscillatorNode) { + constructor(context: BaseAudioContext, options?: TOscillatorOptions) { + const node = new globalThis.OscillatorNode(context.context, options); super(context, node); this.detune = new AudioParam(node.detune, context); From 1f62ba85b978bc796b14cde2f181a9869ebb3149 Mon Sep 17 00:00:00 2001 From: michal Date: Mon, 24 Nov 2025 13:49:29 +0100 Subject: [PATCH 14/16] feat: mobile audio buffer sources --- .../BaseAudioContextHostObject.cpp | 12 ++++--- .../audioapi/HostObjects/utils/NodeOptions.h | 13 ++++++++ .../HostObjects/utils/NodeOptionsParser.h | 31 +++++++++++++++++++ .../cpp/audioapi/core/BaseAudioContext.cpp | 9 +++--- .../cpp/audioapi/core/BaseAudioContext.h | 8 +++-- .../audioapi/core/effects/ConvolverNode.cpp | 2 -- .../sources/AudioBufferBaseSourceNode.cpp | 11 ++++--- .../core/sources/AudioBufferBaseSourceNode.h | 5 ++- .../sources/AudioBufferQueueSourceNode.cpp | 7 +++-- .../core/sources/AudioBufferQueueSourceNode.h | 5 ++- .../core/sources/AudioBufferSourceNode.cpp | 15 +++++---- .../core/sources/AudioBufferSourceNode.h | 5 ++- .../src/core/AudioBufferQueueSourceNode.ts | 15 +++++++++ .../src/core/AudioBufferSourceNode.ts | 12 +++++++ .../src/core/BaseAudioContext.ts | 28 +++-------------- .../react-native-audio-api/src/defaults.ts | 15 +++++++++ .../react-native-audio-api/src/interfaces.ts | 8 +++-- packages/react-native-audio-api/src/types.ts | 20 +++++++++--- .../src/web-core/AudioContext.tsx | 1 + .../src/web-core/OfflineAudioContext.tsx | 1 + 20 files changed, 165 insertions(+), 58 deletions(-) diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 24f4b637b..b8994118c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -207,15 +207,19 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBiquadFilter) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBufferSource) { - auto pitchCorrection = args[0].asBool(); - auto bufferSource = context_->createBufferSource(pitchCorrection); + auto options = args[0].asObject(runtime); + std::shared_ptr audioBufferSourceOptions = + audioapi::option_parser::parseAudioBufferSourceOptions(runtime, options); + auto bufferSource = context_->createBufferSource(audioBufferSourceOptions); auto bufferSourceHostObject = std::make_shared(bufferSource); return jsi::Object::createFromHostObject(runtime, bufferSourceHostObject); } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createBufferQueueSource) { - auto pitchCorrection = args[0].asBool(); - auto bufferSource = context_->createBufferQueueSource(pitchCorrection); + auto options = args[0].asObject(runtime); + std::shared_ptr baseAudioBufferSourceOptions = + audioapi::option_parser::parseBaseAudioBufferSourceOptions(runtime, options); + auto bufferSource = context_->createBufferQueueSource(baseAudioBufferSourceOptions); auto bufferStreamSourceHostObject = std::make_shared(bufferSource); return jsi::Object::createFromHostObject(runtime, bufferStreamSourceHostObject); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 751a0096a..1f8e20be9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -54,4 +54,17 @@ struct OscillatorOptions { float detune = 0.0f; OscillatorType type = OscillatorType::SINE; }; + +struct BaseAudioBufferSourceOptions { + float detune = 0.0f; + bool pitchCorrection = false; + float playbackRate = 1.0f; +}; + +struct AudioBufferSourceOptions : BaseAudioBufferSourceOptions { + std::shared_ptr buffer = nullptr; + bool loop = false; + float loopStart = 0.0f; + float loopEnd = 0.0f; +}; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 6abc0f415..0999d8295 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -168,4 +168,35 @@ std::shared_ptr parseOscillatorOptions( return std::make_shared(options); } + +std::shared_ptr parseBaseAudioBufferSourceOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + BaseAudioBufferSourceOptions options; + options.detune = static_cast(optionsObject.getProperty(runtime, "detune").getNumber()); + options.playbackRate = + static_cast(optionsObject.getProperty(runtime, "playbackRate").getNumber()); + options.pitchCorrection = + static_cast(optionsObject.getProperty(runtime, "pitchCorrection").getNumber()); + return std::make_shared(options); +} + +std::shared_ptr parseAudioBufferSourceOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + std::shared_ptr baseOptions = + parseBaseAudioBufferSourceOptions(runtime, optionsObject); + AudioBufferSourceOptions options(*baseOptions.get()); + if (optionsObject.hasProperty(runtime, "buffer")) { + auto bufferHostObject = optionsObject.getProperty(runtime, "buffer") + .getObject(runtime) + .asHostObject(runtime); + options.buffer = bufferHostObject->audioBuffer_; + } + options.loop = static_cast(optionsObject.getProperty(runtime, "loop").getNumber()); + options.loopStart = + static_cast(optionsObject.getProperty(runtime, "loopStart").getNumber()); + options.loopEnd = static_cast(optionsObject.getProperty(runtime, "loopEnd").getNumber()); + return std::make_shared(options); +} } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index d76c312f5..e0bfaf3e9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -152,15 +152,16 @@ std::shared_ptr BaseAudioContext::createBiquadFilter( return biquadFilter; } -std::shared_ptr BaseAudioContext::createBufferSource(bool pitchCorrection) { - auto bufferSource = std::make_shared(this, pitchCorrection); +std::shared_ptr BaseAudioContext::createBufferSource( + std::shared_ptr options) { + auto bufferSource = std::make_shared(this, options); nodeManager_->addSourceNode(bufferSource); return bufferSource; } std::shared_ptr BaseAudioContext::createBufferQueueSource( - bool pitchCorrection) { - auto bufferSource = std::make_shared(this, pitchCorrection); + std::shared_ptr options) { + auto bufferSource = std::make_shared(this, options); nodeManager_->addSourceNode(bufferSource); return bufferSource; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index d0d0dc398..9952613de 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -42,6 +42,8 @@ class ConstantSourceOptions; class AnalyserOptions; class BiquadFilterOptions; class OscillatorOptions; +class BaseAudioBufferSourceOptions; +class AudioBufferSourceOptions; class BaseAudioContext { public: @@ -80,8 +82,10 @@ class BaseAudioContext { std::shared_ptr options); std::shared_ptr createBiquadFilter( std::shared_ptr options); - std::shared_ptr createBufferSource(bool pitchCorrection); - std::shared_ptr createBufferQueueSource(bool pitchCorrection); + std::shared_ptr createBufferSource( + std::shared_ptr options); + std::shared_ptr createBufferQueueSource( + std::shared_ptr options); static std::shared_ptr createBuffer(int numberOfChannels, size_t length, float sampleRate); std::shared_ptr createPeriodicWave( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp index 03fb3e1f3..e27e1c7b7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp @@ -23,8 +23,6 @@ ConvolverNode::ConvolverNode( intermediateBus_(nullptr), buffer_(nullptr), internalBuffer_(nullptr) { - channelCount_ = 2; - channelCountMode_ = ChannelCountMode::CLAMPED_MAX; normalize_ = !options->disableNormalization; gainCalibrationSampleRate_ = context->getSampleRate(); setBuffer(options->bus); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp index 709b14f6c..69a8775e8 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,14 +13,16 @@ namespace audioapi { AudioBufferBaseSourceNode::AudioBufferBaseSourceNode( BaseAudioContext *context, - bool pitchCorrection) - : AudioScheduledSourceNode(context), pitchCorrection_(pitchCorrection), vReadIndex_(0.0) { + std::shared_ptr options) + : AudioScheduledSourceNode(context), + pitchCorrection_(options->pitchCorrection), + vReadIndex_(0.0) { onPositionChangedInterval_ = static_cast(context->getSampleRate() * 0.1); detuneParam_ = std::make_shared( - 0.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); + options->detune, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); playbackRateParam_ = std::make_shared( - 1.0, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); + options->playbackRate, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); playbackRateBus_ = std::make_shared(RENDER_QUANTUM_SIZE * 3, channelCount_, context_->getSampleRate()); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h index 38e5c792f..46dc1ecdd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h @@ -11,10 +11,13 @@ namespace audioapi { class AudioBus; class AudioParam; +class BaseAudioBufferSourceOptions; class AudioBufferBaseSourceNode : public AudioScheduledSourceNode { public: - explicit AudioBufferBaseSourceNode(BaseAudioContext *context, bool pitchCorrection); + explicit AudioBufferBaseSourceNode( + BaseAudioContext *context, + std::shared_ptr options); [[nodiscard]] std::shared_ptr getDetuneParam() const; [[nodiscard]] std::shared_ptr getPlaybackRateParam() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp index 489dff9bc..4fa949f73 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,12 +20,12 @@ namespace audioapi { AudioBufferQueueSourceNode::AudioBufferQueueSourceNode( BaseAudioContext *context, - bool pitchCorrection) - : AudioBufferBaseSourceNode(context, pitchCorrection) { + std::shared_ptr options) + : AudioBufferBaseSourceNode(context, options) { buffers_ = {}; stretch_->presetDefault(channelCount_, context_->getSampleRate()); - if (pitchCorrection) { + if (options->pitchCorrection) { // If pitch correction is enabled, add extra frames at the end // to compensate for processing latency. addExtraTailFrames_ = true; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h index 0d1efa449..1003b9d31 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h @@ -14,10 +14,13 @@ namespace audioapi { class AudioBus; class AudioParam; +class BaseAudioBufferSourceOptions; class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { public: - explicit AudioBufferQueueSourceNode(BaseAudioContext *context, bool pitchCorrection); + explicit AudioBufferQueueSourceNode( + BaseAudioContext *context, + std::shared_ptr options); ~AudioBufferQueueSourceNode() override; void stop(double when) override; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp index 86cbaace7..2a055ad3d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,13 +13,15 @@ namespace audioapi { -AudioBufferSourceNode::AudioBufferSourceNode(BaseAudioContext *context, bool pitchCorrection) - : AudioBufferBaseSourceNode(context, pitchCorrection), - loop_(false), +AudioBufferSourceNode::AudioBufferSourceNode( + BaseAudioContext *context, + std::shared_ptr options) + : AudioBufferBaseSourceNode(context, options), + loop_(options->loop), loopSkip_(false), - loopStart_(0), - loopEnd_(0) { - buffer_ = std::shared_ptr(nullptr); + loopStart_(options->loopStart), + loopEnd_(options->loopEnd) { + buffer_ = std::shared_ptr(options->buffer); alignedBus_ = std::shared_ptr(nullptr); isInitialized_ = true; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h index b0851f0e8..5a92bf050 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h @@ -13,10 +13,13 @@ namespace audioapi { class AudioBus; class AudioParam; +class AudioBufferSourceOptions; class AudioBufferSourceNode : public AudioBufferBaseSourceNode { public: - explicit AudioBufferSourceNode(BaseAudioContext *context, bool pitchCorrection); + explicit AudioBufferSourceNode( + BaseAudioContext *context, + std::shared_ptr options); ~AudioBufferSourceNode() override; [[nodiscard]] bool getLoop() const; diff --git a/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts index c780859c1..91935ffca 100644 --- a/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts +++ b/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts @@ -2,8 +2,23 @@ import { IAudioBufferQueueSourceNode } from '../interfaces'; import AudioBufferBaseSourceNode from './AudioBufferBaseSourceNode'; import AudioBuffer from './AudioBuffer'; import { RangeError } from '../errors'; +import BaseAudioContext from './BaseAudioContext'; +import { TBaseAudioBufferSourceOptions } from '../types'; +import { BaseAudioBufferSourceOptions } from '../defaults'; export default class AudioBufferQueueSourceNode extends AudioBufferBaseSourceNode { + constructor( + context: BaseAudioContext, + options?: TBaseAudioBufferSourceOptions + ) { + const finalOptions: TBaseAudioBufferSourceOptions = { + ...BaseAudioBufferSourceOptions, + ...options, + }; + const node = context.context.createBufferQueueSource(finalOptions); + super(context, node); + } + public enqueueBuffer(buffer: AudioBuffer): string { return (this.node as IAudioBufferQueueSourceNode).enqueueBuffer( buffer.buffer diff --git a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts index e8dba4a23..3aff9ac7e 100644 --- a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts +++ b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts @@ -4,11 +4,23 @@ import AudioBuffer from './AudioBuffer'; import { InvalidStateError, RangeError } from '../errors'; import { EventEmptyType } from '../events/types'; import { AudioEventSubscription } from '../events'; +import { TAudioBufferSourceOptions } from '../types'; +import BaseAudioContext from './BaseAudioContext'; +import { AudioBufferSourceOptions } from '../defaults'; export default class AudioBufferSourceNode extends AudioBufferBaseSourceNode { private onLoopEndedSubscription?: AudioEventSubscription; private onLoopEndedCallback?: (event: EventEmptyType) => void; + constructor(context: BaseAudioContext, options?: TAudioBufferSourceOptions) { + const finalOptions: TAudioBufferSourceOptions = { + ...AudioBufferSourceOptions, + ...options, + }; + const node = context.context.createBufferSource(finalOptions); + super(context, node); + } + public get buffer(): AudioBuffer | null { const buffer = (this.node as IAudioBufferSourceNode).buffer; if (!buffer) { diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 9686d0c94..0cba1605b 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -1,10 +1,6 @@ import { InvalidAccessError, NotSupportedError } from '../errors'; import { IBaseAudioContext } from '../interfaces'; -import { - AudioBufferBaseSourceNodeOptions, - ContextState, - AudioWorkletRuntime, -} from '../types'; +import { ContextState, AudioWorkletRuntime } from '../types'; import { assertWorkletsEnabled, workletsModule } from '../utils'; import WorkletSourceNode from './WorkletSourceNode'; import WorkletProcessingNode from './WorkletProcessingNode'; @@ -202,26 +198,12 @@ export default class BaseAudioContext { return new BiquadFilterNode(this); } - createBufferSource( - options?: AudioBufferBaseSourceNodeOptions - ): AudioBufferSourceNode { - const pitchCorrection = options?.pitchCorrection ?? false; - - return new AudioBufferSourceNode( - this, - this.context.createBufferSource(pitchCorrection) - ); + createBufferSource(): AudioBufferSourceNode { + return new AudioBufferSourceNode(this); } - createBufferQueueSource( - options?: AudioBufferBaseSourceNodeOptions - ): AudioBufferQueueSourceNode { - const pitchCorrection = options?.pitchCorrection ?? false; - - return new AudioBufferQueueSourceNode( - this, - this.context.createBufferQueueSource(pitchCorrection) - ); + createBufferQueueSource(): AudioBufferQueueSourceNode { + return new AudioBufferQueueSourceNode(this); } createBuffer( diff --git a/packages/react-native-audio-api/src/defaults.ts b/packages/react-native-audio-api/src/defaults.ts index fdb582550..9459a2b00 100644 --- a/packages/react-native-audio-api/src/defaults.ts +++ b/packages/react-native-audio-api/src/defaults.ts @@ -8,6 +8,8 @@ import { TAnalyserOptions, TBiquadFilterOptions, TOscillatorOptions, + TBaseAudioBufferSourceOptions, + TAudioBufferSourceOptions, } from './types'; export const AudioNodeOptions: TAudioNodeOptions = { @@ -63,3 +65,16 @@ export const OscillatorOptions: TOscillatorOptions = { frequency: 440, detune: 0, }; + +export const BaseAudioBufferSourceOptions: TBaseAudioBufferSourceOptions = { + playbackRate: 1, + detune: 0, + pitchCorrection: false, +}; + +export const AudioBufferSourceOptions: TAudioBufferSourceOptions = { + ...BaseAudioBufferSourceOptions, + loop: false, + loopStart: 0, + loopEnd: 0, +}; diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 0e890f7ac..0658c008a 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -12,6 +12,8 @@ import { TAnalyserOptions, TBiquadFilterOptions, TOscillatorOptions, + TBaseAudioBufferSourceOptions, + TAudioBufferSourceOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -74,9 +76,11 @@ export interface IBaseAudioContext { createBiquadFilter: ( biquadFilterOptions: TBiquadFilterOptions ) => IBiquadFilterNode; - createBufferSource: (pitchCorrection: boolean) => IAudioBufferSourceNode; + createBufferSource: ( + audioBufferSourceOptions: TAudioBufferSourceOptions + ) => IAudioBufferSourceNode; createBufferQueueSource: ( - pitchCorrection: boolean + audioBufferQueueSourceOptions: TBaseAudioBufferSourceOptions ) => IAudioBufferQueueSourceNode; createBuffer: ( channels: number, diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index ba5ed4ae3..5db4e9fdd 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -43,10 +43,6 @@ export interface AudioRecorderOptions { export type WindowType = 'blackman' | 'hann'; -export interface AudioBufferBaseSourceNodeOptions { - pitchCorrection: boolean; -} - export type ProcessorMode = 'processInPlace' | 'processThrough'; export interface TAudioNodeOptions { @@ -85,8 +81,22 @@ export interface TOscillatorOptions { periodicWave?: PeriodicWave; } +export interface TBaseAudioBufferSourceOptions { + detune?: number; + playbackRate?: number; + pitchCorrection?: boolean; +} + +export interface TAudioBufferSourceOptions + extends TBaseAudioBufferSourceOptions { + buffer?: AudioBuffer; + loop?: boolean; + loopStart?: number; + loopEnd?: number; +} + export interface TConvolverOptions extends TAudioNodeOptions { - buffer?: AudioBuffer | null; + buffer?: AudioBuffer; disableNormalization?: boolean; } diff --git a/packages/react-native-audio-api/src/web-core/AudioContext.tsx b/packages/react-native-audio-api/src/web-core/AudioContext.tsx index 66689558d..f8d8209b2 100644 --- a/packages/react-native-audio-api/src/web-core/AudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioContext.tsx @@ -1,6 +1,7 @@ import { ContextState, AudioContextOptions, + // @ts-ignore only-for-this-commit AudioBufferBaseSourceNodeOptions, } from '../types'; import { InvalidAccessError, NotSupportedError } from '../errors'; diff --git a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx index 8bc7eef84..b860f5375 100644 --- a/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx +++ b/packages/react-native-audio-api/src/web-core/OfflineAudioContext.tsx @@ -1,6 +1,7 @@ import { ContextState, OfflineAudioContextOptions, + // @ts-ignore only-for-this-commit AudioBufferBaseSourceNodeOptions, } from '../types'; import { InvalidAccessError, NotSupportedError } from '../errors'; From f80292ab402a2b5b180da57266d3bb75287e7e3e Mon Sep 17 00:00:00 2001 From: michal Date: Wed, 26 Nov 2025 12:08:36 +0100 Subject: [PATCH 15/16] feat: streamer node options --- .../audiodocs/docs/sources/streamer-node.mdx | 9 ++++--- .../BaseAudioContextHostObject.cpp | 7 ++++- .../sources/StreamerNodeHostObject.cpp | 6 +++++ .../sources/StreamerNodeHostObject.h | 1 + .../audioapi/HostObjects/utils/NodeOptions.h | 6 +++++ .../HostObjects/utils/NodeOptionsParser.h | 13 ++++++++- .../cpp/audioapi/core/BaseAudioContext.cpp | 5 ++-- .../cpp/audioapi/core/BaseAudioContext.h | 3 ++- .../audioapi/core/sources/StreamerNode.cpp | 7 +++-- .../cpp/audioapi/core/sources/StreamerNode.h | 8 +++++- .../src/core/BaseAudioContext.ts | 2 +- .../src/core/StreamerNode.ts | 27 ++++++++++++++++++- .../react-native-audio-api/src/interfaces.ts | 4 ++- packages/react-native-audio-api/src/types.ts | 4 +++ 14 files changed, 88 insertions(+), 14 deletions(-) diff --git a/packages/audiodocs/docs/sources/streamer-node.mdx b/packages/audiodocs/docs/sources/streamer-node.mdx index 0d22e0e62..479da9f02 100644 --- a/packages/audiodocs/docs/sources/streamer-node.mdx +++ b/packages/audiodocs/docs/sources/streamer-node.mdx @@ -45,18 +45,21 @@ function App() { ## Properties -`StreamerNode` does not define any additional properties. It inherits all properties from [`AudioScheduledSourceNode`](/docs/sources/audio-scheduled-source-node#properties). +| Name | Type | Description | +| :----: | :----: | :------- | +| `streamPath` | `string` | String value representing url to stream. | + ## Methods It inherits all methods from [`AudioScheduledSourceNode`](/docs/sources/audio-scheduled-source-node#methods). ### `initialize` -Initializes the streamer with a link to an external HLS source. +Initializes the streamer with a link to an external source. | Parameter | Type | Description | | :---: | :---: | :---- | -| `streamPath` | `string` | Link pointing to an external HLS source | +| `streamPath` | `string` | Link pointing to an external source | #### Returns `boolean` indicating if setup of streaming has worked. diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index b8994118c..b2ff7449c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -163,7 +163,12 @@ JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createOscillator) { } JSI_HOST_FUNCTION_IMPL(BaseAudioContextHostObject, createStreamer) { - auto streamer = context_->createStreamer(); + std::shared_ptr streamerOptions = std::make_shared(); + if (!args[0].isUndefined()) { + auto options = args[0].asObject(runtime); + streamerOptions = audioapi::option_parser::parseStreamerOptions(runtime, options); + } + auto streamer = context_->createStreamer(streamerOptions); auto streamerHostObject = std::make_shared(streamer); auto object = jsi::Object::createFromHostObject(runtime, streamerHostObject); object.setExternalMemoryPressure(runtime, StreamerNodeHostObject::getSizeInBytes()); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp index bee7fc82e..6da7c85e1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp @@ -10,6 +10,12 @@ namespace audioapi { StreamerNodeHostObject::StreamerNodeHostObject(const std::shared_ptr &node) : AudioScheduledSourceNodeHostObject(node) { addFunctions(JSI_EXPORT_FUNCTION(StreamerNodeHostObject, initialize)); + addGetters(JSI_EXPORT_PROPERTY_GETTER(StreamerNodeHostObject, streamPath)); +} + +JSI_PROPERTY_GETTER_IMPL(StreamerNodeHostObject, streamPath) { + auto streamerNode = std::static_pointer_cast(node_); + return jsi::String::createFromUtf8(runtime, streamerNode->getStreamPath()); } JSI_HOST_FUNCTION_IMPL(StreamerNodeHostObject, initialize) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h index 33e691679..43118bc0e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h @@ -19,6 +19,7 @@ class StreamerNodeHostObject : public AudioScheduledSourceNodeHostObject { return SIZE; } + JSI_PROPERTY_GETTER_DECL(streamPath); JSI_HOST_FUNCTION_DECL(initialize); private: diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h index 1f8e20be9..fc05192a9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptions.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -67,4 +68,9 @@ struct AudioBufferSourceOptions : BaseAudioBufferSourceOptions { float loopStart = 0.0f; float loopEnd = 0.0f; }; + +struct StreamerOptions { + std::string streamPath = ""; +}; + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h index 0999d8295..e51f47c6e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -199,4 +199,15 @@ std::shared_ptr parseAudioBufferSourceOptions( options.loopEnd = static_cast(optionsObject.getProperty(runtime, "loopEnd").getNumber()); return std::make_shared(options); } + +std::shared_ptr parseStreamerOptions( + jsi::Runtime &runtime, + const jsi::Object &optionsObject) { + auto options = StreamerOptions(); + if (optionsObject.hasProperty(runtime, "streamPath")) { + options.streamPath = + optionsObject.getProperty(runtime, "streamPath").asString(runtime).utf8(runtime); + } + return std::make_shared(options); +} } // namespace audioapi::option_parser diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index e0bfaf3e9..38e2735f1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -125,8 +125,9 @@ std::shared_ptr BaseAudioContext::createConstantSource( } #ifndef AUDIO_API_TEST_SUITE -std::shared_ptr BaseAudioContext::createStreamer() { - auto streamer = std::make_shared(this); +std::shared_ptr BaseAudioContext::createStreamer( + std::shared_ptr options) { + auto streamer = std::make_shared(this, options); nodeManager_->addSourceNode(streamer); return streamer; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h index 9952613de..2032142aa 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.h @@ -44,6 +44,7 @@ class BiquadFilterOptions; class OscillatorOptions; class BaseAudioBufferSourceOptions; class AudioBufferSourceOptions; +class StreamerOptions; class BaseAudioContext { public: @@ -76,7 +77,7 @@ class BaseAudioContext { std::shared_ptr createOscillator(std::shared_ptr options); std::shared_ptr createConstantSource( std::shared_ptr options); - std::shared_ptr createStreamer(); + std::shared_ptr createStreamer(std::shared_ptr options); std::shared_ptr createGain(std::shared_ptr options); std::shared_ptr createStereoPanner( std::shared_ptr options); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp index deccee21f..1e94c6b41 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp @@ -8,6 +8,7 @@ * FFmpeg, you must comply with the terms of the LGPL for FFmpeg itself. */ +#include #include #include #include @@ -21,7 +22,7 @@ #include namespace audioapi { -StreamerNode::StreamerNode(BaseAudioContext *context) +StreamerNode::StreamerNode(BaseAudioContext *context, std::shared_ptr options) : AudioScheduledSourceNode(context), fmtCtx_(nullptr), codecCtx_(nullptr), @@ -34,13 +35,15 @@ StreamerNode::StreamerNode(BaseAudioContext *context) bufferedBus_(nullptr), audio_stream_index_(-1), maxResampledSamples_(0), - processedSamples_(0) {} + processedSamples_(0), + streamPath_(options->streamPath) {} StreamerNode::~StreamerNode() { cleanup(); } bool StreamerNode::initialize(const std::string &input_url) { + streamPath_ = input_url; if (isInitialized_) { cleanup(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h index 4f5cd413b..b38e0bcc1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h @@ -61,10 +61,11 @@ struct StreamingData { namespace audioapi { class AudioBus; +class StreamerOptions; class StreamerNode : public AudioScheduledSourceNode { public: - explicit StreamerNode(BaseAudioContext *context); + explicit StreamerNode(BaseAudioContext *context, std::shared_ptr options); ~StreamerNode() override; /** @@ -72,6 +73,10 @@ class StreamerNode : public AudioScheduledSourceNode { */ bool initialize(const std::string &inputUrl); + std::string getStreamPath() const { + return streamPath_; + } + protected: std::shared_ptr processNode( const std::shared_ptr &processingBus, @@ -105,6 +110,7 @@ class StreamerNode : public AudioScheduledSourceNode { STREAMER_NODE_SPSC_OVERFLOW_STRATEGY, STREAMER_NODE_SPSC_WAIT_STRATEGY> receiver_; + std::string streamPath_; /** * @brief Setting up the resampler diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 0cba1605b..f140551ea 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -179,7 +179,7 @@ export default class BaseAudioContext { } createStreamer(): StreamerNode { - return new StreamerNode(this, this.context.createStreamer()); + return new StreamerNode(this); } createConstantSource(): ConstantSourceNode { diff --git a/packages/react-native-audio-api/src/core/StreamerNode.ts b/packages/react-native-audio-api/src/core/StreamerNode.ts index fcbb61d20..f1efb9f2e 100644 --- a/packages/react-native-audio-api/src/core/StreamerNode.ts +++ b/packages/react-native-audio-api/src/core/StreamerNode.ts @@ -1,8 +1,33 @@ import { IStreamerNode } from '../interfaces'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; +import { TStreamerOptions } from '../types'; +import { InvalidStateError } from '../errors'; +import BaseAudioContext from './BaseAudioContext'; export default class StreamerNode extends AudioScheduledSourceNode { + private hasBeenSetup: boolean = false; + constructor(context: BaseAudioContext, options?: TStreamerOptions) { + const node = context.context.createStreamer(options); + super(context, node); + if (options?.streamPath) { + if (this.initialize(options.streamPath)) { + this.hasBeenSetup = true; + } + } + } + public initialize(streamPath: string): boolean { - return (this.node as IStreamerNode).initialize(streamPath); + if (this.hasBeenSetup) { + throw new InvalidStateError('Node is already setup'); + } + const res = (this.node as IStreamerNode).initialize(streamPath); + if (res) { + this.hasBeenSetup = true; + } + return res; + } + + public get streamPath(): string { + return (this.node as IStreamerNode).streamPath; } } diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 0658c008a..3d3ddb684 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -14,6 +14,7 @@ import { TOscillatorOptions, TBaseAudioBufferSourceOptions, TAudioBufferSourceOptions, + TStreamerOptions, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -94,7 +95,7 @@ export interface IBaseAudioContext { ) => IPeriodicWave; createAnalyser: (analyserOptions: TAnalyserOptions) => IAnalyserNode; createConvolver: (convolverOptions: TConvolverOptions) => IConvolverNode; - createStreamer: () => IStreamerNode; + createStreamer: (streamerOptions?: TStreamerOptions) => IStreamerNode; } export interface IAudioContext extends IBaseAudioContext { @@ -175,6 +176,7 @@ export interface IOscillatorNode extends IAudioScheduledSourceNode { } export interface IStreamerNode extends IAudioNode { + readonly streamPath: string; initialize(streamPath: string): boolean; } diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 5db4e9fdd..84f36bd66 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -109,6 +109,10 @@ export interface TConstantSourceOptions { offset?: number; } +export interface TStreamerOptions { + streamPath?: string; +} + export interface TPeriodicWaveConstraints { disableNormalization?: boolean; } From 67df87dcb6baa1153c6e2dad2719da7763beab9b Mon Sep 17 00:00:00 2001 From: michal Date: Wed, 26 Nov 2025 12:24:30 +0100 Subject: [PATCH 16/16] feat: recorder adapter --- packages/react-native-audio-api/src/core/BaseAudioContext.ts | 2 +- .../react-native-audio-api/src/core/RecorderAdapterNode.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index f140551ea..6c0d6b6bb 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -171,7 +171,7 @@ export default class BaseAudioContext { } createRecorderAdapter(): RecorderAdapterNode { - return new RecorderAdapterNode(this, this.context.createRecorderAdapter()); + return new RecorderAdapterNode(this); } createOscillator(): OscillatorNode { diff --git a/packages/react-native-audio-api/src/core/RecorderAdapterNode.ts b/packages/react-native-audio-api/src/core/RecorderAdapterNode.ts index 75c194944..ecad274bc 100644 --- a/packages/react-native-audio-api/src/core/RecorderAdapterNode.ts +++ b/packages/react-native-audio-api/src/core/RecorderAdapterNode.ts @@ -1,10 +1,15 @@ import { IRecorderAdapterNode } from '../interfaces'; import AudioNode from './AudioNode'; +import BaseAudioContext from './BaseAudioContext'; export default class RecorderAdapterNode extends AudioNode { /** @internal */ public wasConnected: boolean = false; + constructor(context: BaseAudioContext) { + super(context, context.context.createRecorderAdapter()); + } + /** @internal */ public getNode(): IRecorderAdapterNode { return this.node as IRecorderAdapterNode;