diff --git a/.gitignore b/.gitignore index f942cdf9e..c8951115a 100644 --- a/.gitignore +++ b/.gitignore @@ -98,4 +98,5 @@ packages/react-native-audio-api/common/cpp/audioapi/external/**/*.a packages/react-native-audio-api/common/cpp/audioapi/external/*.xcframework packages/react-native-audio-api/common/cpp/audioapi/external/ffmpeg_ios/ +# Clangd cache .cache diff --git a/apps/common-app/src/examples/Streaming/Streaming.tsx b/apps/common-app/src/examples/Streaming/Streaming.tsx index b83009139..ab0393004 100644 --- a/apps/common-app/src/examples/Streaming/Streaming.tsx +++ b/apps/common-app/src/examples/Streaming/Streaming.tsx @@ -30,11 +30,8 @@ const Streaming: FC = () => { console.error('StreamerNode is already initialized'); return; } - streamerRef.current = aCtxRef.current.createStreamer(); + streamerRef.current = aCtxRef.current.createStreamer('https://liveradio.timesa.pl/2980-1.aac/playlist.m3u8'); - streamerRef.current.initialize( - 'https://liveradio.timesa.pl/2980-1.aac/playlist.m3u8' - ); streamerRef.current.connect(gainRef.current); gainRef.current.connect(aCtxRef.current.destination); streamerRef.current.start(aCtxRef.current.currentTime); diff --git a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj index 8f15be870..a3133dea7 100644 --- a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj +++ b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj @@ -191,14 +191,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks.sh\"\n"; @@ -234,14 +230,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources.sh\"\n"; @@ -424,7 +416,10 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -512,7 +507,10 @@ "-DFOLLY_HAVE_CLOCK_GETTIME=1", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ENABLE_EXPLICIT_MODULES = NO; diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock index e40cec97d..83b7e4707 100644 --- a/apps/fabric-example/ios/Podfile.lock +++ b/apps/fabric-example/ios/Podfile.lock @@ -2553,7 +2553,7 @@ SPEC CHECKSUMS: React-microtasksnativemodule: d1956f0eec54c619b63a379520fb4c618a55ccb9 react-native-background-timer: 4638ae3bee00320753647900b21260b10587b6f7 react-native-safe-area-context: ae7587b95fb580d1800c5b0b2a7bd48c2868e67a - react-native-skia: 268f7c9942c00dcecc58fae9758b7833e3d246f2 + react-native-skia: 5f68d3c3749bfb4f726e408410b8be5999392cd9 React-NativeModulesApple: 5ba0903927f6b8d335a091700e9fda143980f819 React-networking: 3a4b7f9ed2b2d1c0441beacb79674323a24bcca6 React-oscompat: ff26abf0ae3e3fdbe47b44224571e3fc7226a573 diff --git a/ghdocs/audio-node.md b/ghdocs/audio-node.md new file mode 100644 index 000000000..d765348cd --- /dev/null +++ b/ghdocs/audio-node.md @@ -0,0 +1,280 @@ +# How to create new AudioNode + +In this docs we present recommended patterns for creating new AudioNodes. + +## Layers + +Ususally, each AudioNode has three layers: + +- Core (C++) + + Class implementing core audio processing logic. Should be implemented in highly performant manner using internal data structures (if possible). + +```cpp +class GainNode : public AudioNode { + public: + explicit GainNode(const std::shared_ptr &context, const GainOptions &options); + + [[nodiscard]] std::shared_ptr getGainParam() const; + + protected: + std::shared_ptr processNode( + const std::shared_ptr &processingBuffer, + int framesToProcess) override; + + private: + std::shared_ptr gainParam_; +}; +``` + +- Host Object (HO) + + Interop class between C++ and JS, implemented on C++ side. HO is returned from C++ to JS from BaseAudioContext factory methods. JS has its own interfaces that works as a counterpart of C++ HO. There is no strong typing mechanism between C++ and JS. Implementation is based on the alignment between C++ HO and JS interface. + +```cpp +class GainNodeHostObject : public AudioNodeHostObject { + public: + explicit GainNodeHostObject( + const std::shared_ptr &context, + const GainOptions &options); + + JSI_PROPERTY_GETTER_DECL(gain); + + private: + std::shared_ptr gainParam_; +}; +``` +```ts +export interface IGainNode extends IAudioNode { + readonly gain: IAudioParam; +} +``` + +- Typescript (JS) + + Elegant typescript wrapper around JS HO interface. + +```ts +class GainNode extends AudioNode { + readonly gain: AudioParam; + + constructor(context: BaseAudioContext, options?: TGainOptions) { + const gainNode: IGainNode = context.context.createGain(options || {}); // context.context is C++ HO + super(context, gainNode); + this.gain = new AudioParam(gainNode.gain, context); + } +} +``` + +## Core (C++) implementation + +Each AudioNode should implement one virtual method: +```cpp +std::shared_ptr processNode( + const std::shared_ptr &processingBuffer, + int framesToProcess) +``` + +It is responsible for AudioNode's processing logic. It gets input buffer as argument - `processingBus` and should return processed buffer. + +```cpp +std::shared_ptr GainNode::processNode( + const std::shared_ptr &processingBuffer, + int framesToProcess) { + std::shared_ptr context = context_.lock(); + if (context == nullptr) + return processingBuffer; + double time = context->getCurrentTime(); + auto gainParamValues = gainParam_->processARateParam(framesToProcess, time); + auto gainValues = gainParamValues->getChannel(0); + + for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i++) { + auto channel = processingBuffer->getChannel(i); + channel->multiply(*gainValues, framesToProcess); + } + + return processingBuffer; +} +``` + +There are a few rules that should be followed when implementing C++ AudioNode core. + +- **Thread safety**: Each AudioNode should be created in thread safe manner. + +- **Heap allocations**: Heap allocations are not allowed on the Audio Thread, so all necessary data should be allocated in constructor, or pre-allocated on other thread and passed to AudioNode. + +- **Destructions** No destructions are allowed to happen on the Audio Thread. AudioNode destruction are handled by already active AudioDestructor. If you need to perform some cleanup, you have to delegate it to AudioDestructor. + +- **No locks on Audio Thread**: Locks are not allowed on the Audio Thread. Audio procssing have to be highly performant and efficient. + +- **No syscalls** Syscalls are not allowed on the Audio Thread, so if you need to perform some work that requires syscalls, you have to delegate it to other thread. + +## HostObject implementation + +We can distinguish three types of AudioNode's JS methods: + +1. **getter** + +```ts +const fftSize = analyserNode.fftSize; +``` + +C++ counter part is `JSI_PROPERTY_GETTER`. It just returns some value. + +```cpp +JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, fftSize) { + return {fftSize_}; +} +``` + +2. **setter** + +C++ counterpart is `JSI_PROPERTY_SETTER`. It just receives some value. + +```ts +analyserNode.fftSize = 2048; +``` + +```cpp +JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, minDecibels) { + auto analyserNode = std::static_pointer_cast(node_); + auto minDecibels = static_cast(value.getNumber()); + auto event = [analyserNode, minDecibels](BaseAudioContext&) { + analyserNode->setMinDecibels(minDecibels); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + minDecibels_ = minDecibels; +} +``` + + +3. **function** + +C++ counterpart is `JSI_HOST_FUNCTION`. It is a common function that can receive arguments and return some value. + +```ts +const fftOutput = new Uint8Array(analyser.frequencyBinCount); +analyserNode.getByteFrequencyData(fftOutput); +``` + +```cpp +JSI_HOST_FUNCTION_IMPL(AnalyserNodeHostObject, getByteFrequencyData) { + auto arrayBuffer = + args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + auto data = arrayBuffer.data(runtime); + auto length = static_cast(arrayBuffer.size(runtime)); + + auto analyserNode = std::static_pointer_cast(node_); + analyserNode->getByteFrequencyData(data, length); + + return jsi::Value::undefined(); +} +``` + +All methods should be registerd in C++ HO constructor: + +```cpp +AnalyserNodeHostObject::AnalyserNodeHostObject(const std::shared_ptr& context, const AnalyserOptions &options) + : /* ... */ { + addGetters(JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, fftSize)); + addSetters(JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, fftSize)); + addFunctions(JSI_EXPORT_FUNCTION(AnalyserNodeHostObject, getByteFrequencyData),); +} +``` + +#### Shadow state (C++) + +Shadow state is a mechanism introduced in order to make communication between JS and the Audio Thread lock-free. AudioNodeHostObject stores the set of properties, which are modified only by JS thread (the same set C++ AudioNode has). Everytime we want to access some property from JS, we can just return property from shadow state, when we modify some property we have to update shadow state and schedule update event on Audio Event Loop (SPSC). By following that manner we can skip accessing AudioNode state, that is also accessed by the Audio Thread - no need to lock or use atomic variables. + +```cpp +class OscillatorNodeHostObject : public AudioScheduledSourceNodeHostObject { + public: + /* ... */ + + JSI_PROPERTY_GETTER_DECL(type); + JSI_PROPERTY_SETTER_DECL(type); + + private: + /* ... */ + OscillatorType type_; +}; +``` + +```cpp +JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, type) { + return jsi::String::createFromUtf8(runtime, js_enum_parser::oscillatorTypeToString(type_)); +} + +JSI_PROPERTY_SETTER_IMPL(OscillatorNodeHostObject, type) { + auto oscillatorNode = std::static_pointer_cast(node_); + auto type = js_enum_parser::oscillatorTypeFromString(value.asString(runtime).utf8(runtime)); + + auto event = [oscillatorNode, type](BaseAudioContext &) { + oscillatorNode->setType(type); + }; + type_ = type; + + oscillatorNode->scheduleAudioEvent(std::move(event)); +} +``` + +#### Communication between JS Thread and Audio Thread + +**getters** and **setters** + +1. Property is primitive and is not modified by the Audio Thread. + + Shadow state design pattern should be followed. + +2. Property is not primitive and is not modified by the Audio Thread. + + It should be stored in TS layer and copied to AudioNode + +3. Property is primitive and can be modified by the Audio Thread. + + In C++ core it should be an atomic variable that allows to access it in thread-safe manner from both threads. + +```cpp +class AudioParam { +public: + /* ... */ + + [[nodiscard]] inline float getValue() const noexcept { + return value_.load(std::memory_order_relaxed); + } + + inline void setValue(float value) { + value_.store(std::clamp(value, minValue_, maxValue_), std::memory_order_release); + } + + /* ... */ + +private: + std::atomic value_; + + /* ... */ +}; +``` + +```cpp +JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, value) { + return {param_->getValue()}; +} + +JSI_PROPERTY_SETTER_IMPL(AudioParamHostObject, value) { + auto event = [param = param_, value = static_cast(value.getNumber())](BaseAudioContext &) { + param->setValue(value); + }; + + param_->scheduleAudioEvent(std::move(event)); +} +``` + +4. Property is not primitive and can be modified by the Audio Thread. + In C++ core triple buffer pattern should be followed. It allows to have one copy of property for reader, one for writer and one for pending update. On each update we just swap pending update with writer, and on each read we just read from reader. In that manner we can skip locks and just operate on atomic indices. + + Check AnalyserNode implementation for example or this [article](https://medium.com/@sgn00/triple-buffer-lock-free-concurrency-primitive-611848627a1e) for more details. + +**functions** + +Function's should follow the same thread-safe lock-free patterns as getters/setters. Set of properties read/write by the function determines mechanisms that should be used in implementation. diff --git a/packages/audiodocs/docs/analysis/analyser-node.mdx b/packages/audiodocs/docs/analysis/analyser-node.mdx index baed61620..db95918ec 100644 --- a/packages/audiodocs/docs/analysis/analyser-node.mdx +++ b/packages/audiodocs/docs/analysis/analyser-node.mdx @@ -51,15 +51,8 @@ It inherits all properties from [`AudioNode`](/docs/core/audio-node#properties). | `minDecibels` | `number` | Float value representing the minimum value for the range of results from [`getByteFrequencyData()`](/docs/analysis/analyser-node#getbytefrequencydata). | | `maxDecibels` | `number` | Float value representing the maximum value for the range of results from [`getByteFrequencyData()`](/docs/analysis/analyser-node#getbytefrequencydata). | | `smoothingTimeConstant` | `number` | Float value representing averaging constant with the last analysis frame. In general the higher value the smoother is the transition between values over time. | -| `window` | [`WindowType`](/docs/types/window-type) | Enumerated value that specifies the type of window function applied when extracting frequency data. | | `frequencyBinCount` | `number` | Integer value representing amount of the data obtained in frequency domain, half of the `fftSize` property. | | -:::caution - -On `Web`, the value of `window` is permanently `'blackman'`, and it cannot be set like on the `Android` or `iOS`. - -::: - ## Methods It inherits all methods from [`AudioNode`](/docs/core/audio-node#methods). @@ -128,6 +121,3 @@ Each value in the array is within the range 0 to 255, where value of 127 indicat - Nominal range is 0 to 1. - 0 means no averaging, 1 means "overlap the previous and current buffer quite a lot while computing the value". - Throws `IndexSizeError` if set value is outside the allowed range. - -#### `window` -- Default value is `'blackman'` diff --git a/packages/audiodocs/docs/core/base-audio-context.mdx b/packages/audiodocs/docs/core/base-audio-context.mdx index 08905ba56..d4cd30417 100644 --- a/packages/audiodocs/docs/core/base-audio-context.mdx +++ b/packages/audiodocs/docs/core/base-audio-context.mdx @@ -172,6 +172,10 @@ Creates [`StereoPannerNode`](/docs/effects/stereo-panner-node). Creates [`StreamerNode`](/docs/sources/streamer-node). +| Parameter | Type | Description | +| :---: | :---: | :---- | +| `options` | [`StreamerOptions`](/docs/sources/streamer-node#streameroptions) | Streamer options to initialize. | + #### Returns `StreamerNode`. ### `createWaveShaper` diff --git a/packages/audiodocs/docs/guides/create-your-own-effect.mdx b/packages/audiodocs/docs/guides/create-your-own-effect.mdx index fbdcbe9c1..f882ccc27 100644 --- a/packages/audiodocs/docs/guides/create-your-own-effect.mdx +++ b/packages/audiodocs/docs/guides/create-your-own-effect.mdx @@ -77,7 +77,7 @@ namespace audioapi { MyProcessorNode::MyProcessorNode(const std::shared_ptr &context) //highlight-next-line : AudioNode(context), gain(0.5) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr MyProcessorNode::processNode(const std::shared_ptr &buffer, diff --git a/packages/audiodocs/docs/sources/streamer-node.mdx b/packages/audiodocs/docs/sources/streamer-node.mdx index d3cb176f9..a619917f7 100644 --- a/packages/audiodocs/docs/sources/streamer-node.mdx +++ b/packages/audiodocs/docs/sources/streamer-node.mdx @@ -21,18 +21,18 @@ Similar to all of `AudioScheduledSourceNodes`, it can be started only once. If y ## Constructor ```tsx -constructor(context: BaseAudioContext, options?: StreamerOptions) +constructor(context: BaseAudioContext, options: StreamerOptions) ``` ### `StreamerOptions` | Parameter | Type | Default | | | :---: | :---: | :----: | :---- | -| `streamPath` | `string` | - | Initial value for [`streamPath`](/docs/sources/streamer-node#properties) | +| `streamPath` | `string` | - | Value for [`streamPath`](/docs/sources/streamer-node#properties) | Or by using `BaseAudioContext` factory method: -[`BaseAudioContext.createStreamer()`](/docs/core/base-audio-context#createstreamer-). +[`BaseAudioContext.createStreamer()`](/docs/core/base-audio-context#createstreamer). ## Example @@ -65,13 +65,3 @@ It inherits all properties from [`AudioScheduledSourceNode`](/docs/sources/audio ## Methods It inherits all methods from [`AudioScheduledSourceNode`](/docs/sources/audio-scheduled-source-node#methods). - -### `initialize` - -Initializes the streamer with a link to an external source. - -| Parameter | Type | Description | -| :---: | :---: | :---- | -| `streamPath` | `string` | Link pointing to an external source | - -#### Returns `boolean` indicating if setup of streaming has worked. diff --git a/packages/audiodocs/docs/types/window-type.mdx b/packages/audiodocs/docs/types/window-type.mdx deleted file mode 100644 index 113934d2c..000000000 --- a/packages/audiodocs/docs/types/window-type.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -sidebar_position: 8 ---- - -# WindowType - -`WindowType` type specifies which [window function](https://en.wikipedia.org/wiki/Window_function) is applied when extracting frequency data. - -**Acceptable values:** - - `blackman` - - Set [Blackman window](https://www.sciencedirect.com/topics/engineering/blackman-window) as window function. - - - `hann` - - Set [Hanning window](https://www.sciencedirect.com/topics/engineering/hanning-window) as window function. - -:::caution - -On `Web`, the value of `window` is permanently `'blackman'`, and it cannot be set like on the `Android` or `iOS`. - -::: diff --git a/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp b/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp index b1ffb9581..033597f6c 100644 --- a/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp +++ b/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp @@ -5,7 +5,7 @@ namespace audioapi { MyProcessorNode::MyProcessorNode( const std::shared_ptr &context, ) : AudioNode(context) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp index e15bafa35..c00abef23 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp @@ -10,7 +10,12 @@ namespace audioapi { AudioNodeHostObject::AudioNodeHostObject( const std::shared_ptr &node, const AudioNodeOptions &options) - : node_(node) { + : node_(node), + numberOfInputs_(options.numberOfInputs), + numberOfOutputs_(options.numberOfOutputs), + channelCount_(options.channelCount), + channelCountMode_(options.channelCountMode), + channelInterpretation_(options.channelInterpretation) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfInputs), JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfOutputs), @@ -30,25 +35,25 @@ AudioNodeHostObject::AudioNodeHostObject( AudioNodeHostObject::~AudioNodeHostObject() = default; JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, numberOfInputs) { - return {node_->getNumberOfInputs()}; + return {numberOfInputs_}; } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, numberOfOutputs) { - return {node_->getNumberOfOutputs()}; + return {numberOfOutputs_}; } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCount) { - return {static_cast(node_->getChannelCount())}; + return {static_cast(channelCount_)}; } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCountMode) { return jsi::String::createFromUtf8( - runtime, js_enum_parser::channelCountModeToString(node_->getChannelCountMode())); + runtime, js_enum_parser::channelCountModeToString(channelCountMode_)); } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelInterpretation) { return jsi::String::createFromUtf8( - runtime, js_enum_parser::channelInterpretationToString(node_->getChannelInterpretation())); + runtime, js_enum_parser::channelInterpretationToString(channelInterpretation_)); } JSI_HOST_FUNCTION_IMPL(AudioNodeHostObject, connect) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h index 6e5355b0d..5b8c12d3c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -29,5 +31,11 @@ class AudioNodeHostObject : public JsiHostObject { protected: std::shared_ptr node_; + + const int numberOfInputs_; + const int numberOfOutputs_; + size_t channelCount_; + const ChannelCountMode channelCountMode_; + const ChannelInterpretation channelInterpretation_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp index 6d501e25d..6fe82bb01 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp @@ -9,7 +9,10 @@ namespace audioapi { AudioParamHostObject::AudioParamHostObject(const std::shared_ptr ¶m) - : param_(param) { + : param_(param), + defaultValue_(param->getDefaultValue()), + minValue_(param->getMinValue()), + maxValue_(param->getMaxValue()) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioParamHostObject, value), JSI_EXPORT_PROPERTY_GETTER(AudioParamHostObject, defaultValue), @@ -33,47 +36,67 @@ JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, value) { } JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, defaultValue) { - return {param_->getDefaultValue()}; + return {defaultValue_}; } JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, minValue) { - return {param_->getMinValue()}; + return {minValue_}; } JSI_PROPERTY_GETTER_IMPL(AudioParamHostObject, maxValue) { - return {param_->getMaxValue()}; + return {maxValue_}; } JSI_PROPERTY_SETTER_IMPL(AudioParamHostObject, value) { - param_->setValue(static_cast(value.getNumber())); + auto event = [param = param_, value = static_cast(value.getNumber())](BaseAudioContext &) { + param->setValue(value); + }; + + param_->scheduleAudioEvent(std::move(event)); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueAtTime) { - auto value = static_cast(args[0].getNumber()); - double startTime = args[1].getNumber(); - param_->setValueAtTime(value, startTime); + auto event = [param = param_, + value = static_cast(args[0].getNumber()), + startTime = args[1].getNumber()](BaseAudioContext &) { + param->setValueAtTime(value, startTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, linearRampToValueAtTime) { - auto value = static_cast(args[0].getNumber()); - double endTime = args[1].getNumber(); - param_->linearRampToValueAtTime(value, endTime); + auto event = [param = param_, + value = static_cast(args[0].getNumber()), + endTime = args[1].getNumber()](BaseAudioContext &) { + param->linearRampToValueAtTime(value, endTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, exponentialRampToValueAtTime) { - auto value = static_cast(args[0].getNumber()); - double endTime = args[1].getNumber(); - param_->exponentialRampToValueAtTime(value, endTime); + auto event = [param = param_, + value = static_cast(args[0].getNumber()), + endTime = args[1].getNumber()](BaseAudioContext &) { + param->exponentialRampToValueAtTime(value, endTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setTargetAtTime) { - auto target = static_cast(args[0].getNumber()); - double startTime = args[1].getNumber(); - double timeConstant = args[2].getNumber(); - param_->setTargetAtTime(target, startTime, timeConstant); + auto event = [param = param_, + target = static_cast(args[0].getNumber()), + startTime = args[1].getNumber(), + timeConstant = args[2].getNumber()](BaseAudioContext &) { + param->setTargetAtTime(target, startTime, timeConstant); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -82,23 +105,35 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueCurveAtTime) { args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); auto rawValues = reinterpret_cast(arrayBuffer.data(runtime)); auto length = static_cast(arrayBuffer.size(runtime)); - auto values = std::make_unique(rawValues, length); + auto values = std::make_shared(rawValues, length); - double startTime = args[1].getNumber(); - double duration = args[2].getNumber(); - param_->setValueCurveAtTime(std::move(values), length, startTime, duration); + auto event = [param = param_, + values, + length, + startTime = args[1].getNumber(), + duration = args[2].getNumber()](BaseAudioContext &) { + param->setValueCurveAtTime(values, length, startTime, duration); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelScheduledValues) { - double cancelTime = args[0].getNumber(); - param_->cancelScheduledValues(cancelTime); + auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) { + param->cancelScheduledValues(cancelTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelAndHoldAtTime) { - double cancelTime = args[0].getNumber(); - param_->cancelAndHoldAtTime(cancelTime); + auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) { + param->cancelAndHoldAtTime(cancelTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h index 48109d7f7..64cd8b8fe 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h @@ -34,5 +34,8 @@ class AudioParamHostObject : public JsiHostObject { friend class AudioNodeHostObject; std::shared_ptr param_; + float defaultValue_; + float minValue_; + float maxValue_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp index 93a927af9..c5262e959 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp @@ -14,18 +14,15 @@ AnalyserNodeHostObject::AnalyserNodeHostObject( : AudioNodeHostObject(context->createAnalyser(options), options) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, fftSize), - JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, frequencyBinCount), JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, minDecibels), JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, maxDecibels), - JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, smoothingTimeConstant), - JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, window)); + JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, smoothingTimeConstant)); addSetters( JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, fftSize), JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, minDecibels), JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, maxDecibels), - JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, smoothingTimeConstant), - JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, window)); + JSI_EXPORT_PROPERTY_SETTER(AnalyserNodeHostObject, smoothingTimeConstant)); addFunctions( JSI_EXPORT_FUNCTION(AnalyserNodeHostObject, getFloatFrequencyData), @@ -36,12 +33,7 @@ AnalyserNodeHostObject::AnalyserNodeHostObject( JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, fftSize) { auto analyserNode = std::static_pointer_cast(node_); - return {static_cast(analyserNode->getFftSize())}; -} - -JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, frequencyBinCount) { - auto analyserNode = std::static_pointer_cast(node_); - return {static_cast(analyserNode->getFrequencyBinCount())}; + return {analyserNode->getFFTSize()}; } JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, minDecibels) { @@ -59,16 +51,11 @@ JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, smoothingTimeConstant) { return {analyserNode->getSmoothingTimeConstant()}; } -JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, window) { - auto analyserNode = std::static_pointer_cast(node_); - auto windowType = analyserNode->getWindowType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::windowTypeToString(windowType)); -} - JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, fftSize) { auto analyserNode = std::static_pointer_cast(node_); + auto fftSize = static_cast(value.getNumber()); - analyserNode->setFftSize(fftSize); + analyserNode->setFFTSize(fftSize); } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, minDecibels) { @@ -89,12 +76,6 @@ JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, smoothingTimeConstant) { analyserNode->setSmoothingTimeConstant(smoothingTimeConstant); } -JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, window) { - auto analyserNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - analyserNode->setWindowType(js_enum_parser::windowTypeFromString(type)); -} - JSI_HOST_FUNCTION_IMPL(AnalyserNodeHostObject, getFloatFrequencyData) { auto arrayBuffer = args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h index 42224529f..fe9405d1a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h @@ -17,17 +17,14 @@ class AnalyserNodeHostObject : public AudioNodeHostObject { const AnalyserOptions &options); JSI_PROPERTY_GETTER_DECL(fftSize); - JSI_PROPERTY_GETTER_DECL(frequencyBinCount); JSI_PROPERTY_GETTER_DECL(minDecibels); JSI_PROPERTY_GETTER_DECL(maxDecibels); JSI_PROPERTY_GETTER_DECL(smoothingTimeConstant); - JSI_PROPERTY_GETTER_DECL(window); JSI_PROPERTY_SETTER_DECL(fftSize); JSI_PROPERTY_SETTER_DECL(minDecibels); JSI_PROPERTY_SETTER_DECL(maxDecibels); JSI_PROPERTY_SETTER_DECL(smoothingTimeConstant); - JSI_PROPERTY_SETTER_DECL(window); JSI_HOST_FUNCTION_DECL(getFloatFrequencyData); JSI_HOST_FUNCTION_DECL(getByteFrequencyData); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp index 5084e383e..21f87e99b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp @@ -7,13 +7,20 @@ #include #include +#include namespace audioapi { BiquadFilterNodeHostObject::BiquadFilterNodeHostObject( const std::shared_ptr &context, const BiquadFilterOptions &options) - : AudioNodeHostObject(context->createBiquadFilter(options), options) { + : AudioNodeHostObject(context->createBiquadFilter(options), options), type_(options.type) { + auto biquadFilterNode = std::static_pointer_cast(node_); + frequencyParam_ = std::make_shared(biquadFilterNode->getFrequencyParam()); + detuneParam_ = std::make_shared(biquadFilterNode->getDetuneParam()); + QParam_ = std::make_shared(biquadFilterNode->getQParam()); + gainParam_ = std::make_shared(biquadFilterNode->getGainParam()); + addGetters( JSI_EXPORT_PROPERTY_GETTER(BiquadFilterNodeHostObject, frequency), JSI_EXPORT_PROPERTY_GETTER(BiquadFilterNodeHostObject, detune), @@ -27,40 +34,34 @@ BiquadFilterNodeHostObject::BiquadFilterNodeHostObject( } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, frequency) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto frequencyParam_ = - std::make_shared(biquadFilterNode->getFrequencyParam()); return jsi::Object::createFromHostObject(runtime, frequencyParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, detune) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto detuneParam_ = std::make_shared(biquadFilterNode->getDetuneParam()); return jsi::Object::createFromHostObject(runtime, detuneParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, Q) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto QParam_ = std::make_shared(biquadFilterNode->getQParam()); return jsi::Object::createFromHostObject(runtime, QParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, gain) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto gainParam_ = std::make_shared(biquadFilterNode->getGainParam()); return jsi::Object::createFromHostObject(runtime, gainParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, type) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto type = biquadFilterNode->getType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::filterTypeToString(type)); + return jsi::String::createFromUtf8(runtime, js_enum_parser::filterTypeToString(type_)); } JSI_PROPERTY_SETTER_IMPL(BiquadFilterNodeHostObject, type) { auto biquadFilterNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - biquadFilterNode->setType(js_enum_parser::filterTypeFromString(type)); + + auto type = js_enum_parser::filterTypeFromString(value.asString(runtime).utf8(runtime)); + auto event = [biquadFilterNode, type](BaseAudioContext &) { + biquadFilterNode->setType(type); + }; + biquadFilterNode->scheduleAudioEvent(std::move(event)); + type_ = type; } JSI_HOST_FUNCTION_IMPL(BiquadFilterNodeHostObject, getFrequencyResponse) { @@ -79,7 +80,8 @@ JSI_HOST_FUNCTION_IMPL(BiquadFilterNodeHostObject, getFrequencyResponse) { auto phaseResponseOut = reinterpret_cast(arrayBufferPhase.data(runtime)); auto biquadFilterNode = std::static_pointer_cast(node_); - biquadFilterNode->getFrequencyResponse(frequencyArray, magResponseOut, phaseResponseOut, length); + biquadFilterNode->getFrequencyResponse( + frequencyArray, magResponseOut, phaseResponseOut, length, type_); return jsi::Value::undefined(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h index 4c75be10d..e1958d1fd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct BiquadFilterOptions; class BaseAudioContext; +class AudioParamHostObject; class BiquadFilterNodeHostObject : public AudioNodeHostObject { public: @@ -25,5 +26,13 @@ class BiquadFilterNodeHostObject : public AudioNodeHostObject { JSI_PROPERTY_SETTER_DECL(type); JSI_HOST_FUNCTION_DECL(getFrequencyResponse); + + private: + std::shared_ptr frequencyParam_; + std::shared_ptr detuneParam_; + std::shared_ptr QParam_; + std::shared_ptr gainParam_; + + BiquadFilterType type_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp index 8449b4209..af1bcf22d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp @@ -2,53 +2,115 @@ #include #include #include +#include +#include #include +#include +#include +#include #include +#include +#include namespace audioapi { ConvolverNodeHostObject::ConvolverNodeHostObject( const std::shared_ptr &context, const ConvolverOptions &options) - : AudioNodeHostObject(context->createConvolver(options), options) { - addGetters( - JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, normalize), - JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, buffer)); + : AudioNodeHostObject(context->createConvolver(options), options), + normalize_(!options.disableNormalization) { + if (options.buffer != nullptr) { + setBuffer(options.buffer); + } + + addGetters(JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, normalize)); addSetters(JSI_EXPORT_PROPERTY_SETTER(ConvolverNodeHostObject, normalize)); addFunctions(JSI_EXPORT_FUNCTION(ConvolverNodeHostObject, setBuffer)); } JSI_PROPERTY_GETTER_IMPL(ConvolverNodeHostObject, normalize) { - auto convolverNode = std::static_pointer_cast(node_); - return {convolverNode->getNormalize_()}; -} - -JSI_PROPERTY_GETTER_IMPL(ConvolverNodeHostObject, buffer) { - auto convolverNode = std::static_pointer_cast(node_); - auto buffer = convolverNode->getBuffer(); - auto bufferHostObject = std::make_shared(buffer); - auto jsiObject = jsi::Object::createFromHostObject(runtime, bufferHostObject); - jsiObject.setExternalMemoryPressure(runtime, bufferHostObject->getSizeInBytes() + 16); - return jsiObject; + return jsi::Value(normalize_); } JSI_PROPERTY_SETTER_IMPL(ConvolverNodeHostObject, normalize) { - auto convolverNode = std::static_pointer_cast(node_); - convolverNode->setNormalize(value.getBool()); + normalize_ = value.getBool(); + ; } JSI_HOST_FUNCTION_IMPL(ConvolverNodeHostObject, setBuffer) { - auto convolverNode = std::static_pointer_cast(node_); - if (args[0].isUndefined()) { - convolverNode->setBuffer(nullptr); + if (!args[0].isObject()) { return jsi::Value::undefined(); } auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - convolverNode->setBuffer(bufferHostObject->audioBuffer_); thisValue.asObject(runtime).setExternalMemoryPressure( - runtime, bufferHostObject->getSizeInBytes() + 16); + runtime, bufferHostObject->getSizeInBytes()); + + setBuffer(bufferHostObject->audioBuffer_); + return jsi::Value::undefined(); } + +void ConvolverNodeHostObject::setBuffer(const std::shared_ptr &buffer) { + if (buffer == nullptr) { + return; + } + + auto convolverNode = std::static_pointer_cast(node_); + + auto copiedBuffer = std::make_shared(*buffer); + + float scaleFactor = 1.0f; + if (normalize_) { + scaleFactor = convolverNode->calculateNormalizationScale(copiedBuffer); + } + + auto threadPool = std::make_shared(4); + std::vector convolvers; + for (size_t i = 0; i < copiedBuffer->getNumberOfChannels(); ++i) { + AudioArray channelData(*copiedBuffer->getChannel(i)); + convolvers.emplace_back(); + convolvers.back().init(RENDER_QUANTUM_SIZE, channelData, copiedBuffer->getSize()); + } + if (copiedBuffer->getNumberOfChannels() == 1) { + // add one more convolver, because right now input is always stereo + AudioArray channelData(*copiedBuffer->getChannel(0)); + convolvers.emplace_back(); + convolvers.back().init(RENDER_QUANTUM_SIZE, channelData, copiedBuffer->getSize()); + } + + auto internalBuffer = std::make_shared( + RENDER_QUANTUM_SIZE * 2, convolverNode->getChannelCount(), copiedBuffer->getSampleRate()); + auto intermediateBuffer = std::make_shared( + RENDER_QUANTUM_SIZE, convolvers.size(), copiedBuffer->getSampleRate()); + + struct SetupData { + std::shared_ptr buffer; + std::vector convolvers; + std::shared_ptr threadPool; + std::shared_ptr internalBuffer; + std::shared_ptr intermediateBuffer; + float scaleFactor; + }; + + auto setupData = std::make_shared(SetupData{ + copiedBuffer, + std::move(convolvers), + threadPool, + internalBuffer, + intermediateBuffer, + scaleFactor}); + + auto event = [convolverNode, setupData](BaseAudioContext &) { + convolverNode->setBuffer( + setupData->buffer, + std::move(setupData->convolvers), + setupData->threadPool, + setupData->internalBuffer, + setupData->intermediateBuffer, + setupData->scaleFactor); + }; + convolverNode->scheduleAudioEvent(std::move(event)); +} } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h index a4366c973..fc10c9cd1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct ConvolverOptions; class BaseAudioContext; +class AudioBuffer; class ConvolverNodeHostObject : public AudioNodeHostObject { public: @@ -16,8 +17,11 @@ class ConvolverNodeHostObject : public AudioNodeHostObject { const std::shared_ptr &context, const ConvolverOptions &options); JSI_PROPERTY_GETTER_DECL(normalize); - JSI_PROPERTY_GETTER_DECL(buffer); JSI_PROPERTY_SETTER_DECL(normalize); JSI_HOST_FUNCTION_DECL(setBuffer); + + private: + bool normalize_; + void setBuffer(const std::shared_ptr &buffer); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp index 834ae6006..49220a93e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp @@ -12,23 +12,19 @@ DelayNodeHostObject::DelayNodeHostObject( const std::shared_ptr &context, const DelayOptions &options) : AudioNodeHostObject(context->createDelay(options), options) { + auto delayNode = std::static_pointer_cast(node_); + delayTimeParam_ = std::make_shared(delayNode->getDelayTimeParam()); addGetters(JSI_EXPORT_PROPERTY_GETTER(DelayNodeHostObject, delayTime)); } -size_t DelayNodeHostObject::getSizeInBytes() const { - auto delayNode = std::static_pointer_cast(node_); - auto base = sizeof(float) * delayNode->getDelayTimeParam()->getMaxValue(); - if (std::shared_ptr context = delayNode->context_.lock()) { - return base * context->getSampleRate(); - } else { - return base * 44100; // Fallback to common sample rate - } +JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { + return jsi::Object::createFromHostObject(runtime, delayTimeParam_); } -JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { +size_t DelayNodeHostObject::getSizeInBytes() const { auto delayNode = std::static_pointer_cast(node_); - auto delayTimeParam = std::make_shared(delayNode->getDelayTimeParam()); - return jsi::Object::createFromHostObject(runtime, delayTimeParam); + auto base = sizeof(float) * delayNode->getDelayTimeParam()->getMaxValue(); + return base * delayNode->getContextSampleRate(); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h index 3e1541374..df72a99bf 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct DelayOptions; class BaseAudioContext; +class AudioParamHostObject; class DelayNodeHostObject : public AudioNodeHostObject { public: @@ -19,5 +20,8 @@ class DelayNodeHostObject : public AudioNodeHostObject { [[nodiscard]] size_t getSizeInBytes() const; JSI_PROPERTY_GETTER_DECL(delayTime); + + private: + std::shared_ptr delayTimeParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp index 6f9ec01ac..ecacc7dc5 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp @@ -12,13 +12,14 @@ GainNodeHostObject::GainNodeHostObject( const std::shared_ptr &context, const GainOptions &options) : AudioNodeHostObject(context->createGain(options), options) { + auto gainNode = std::static_pointer_cast(node_); + gainParam_ = std::make_shared(gainNode->getGainParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(GainNodeHostObject, gain)); } JSI_PROPERTY_GETTER_IMPL(GainNodeHostObject, gain) { - auto gainNode = std::static_pointer_cast(node_); - auto gainParam = std::make_shared(gainNode->getGainParam()); - return jsi::Object::createFromHostObject(runtime, gainParam); + return jsi::Object::createFromHostObject(runtime, gainParam_); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h index 7fb1a05a0..eef92cb22 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct GainOptions; class BaseAudioContext; +class AudioParamHostObject; class GainNodeHostObject : public AudioNodeHostObject { public: @@ -17,5 +18,8 @@ class GainNodeHostObject : public AudioNodeHostObject { const GainOptions &options); JSI_PROPERTY_GETTER_DECL(gain); + + private: + std::shared_ptr gainParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp index 282af19a6..f9e50b9cd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp @@ -12,12 +12,13 @@ StereoPannerNodeHostObject::StereoPannerNodeHostObject( const std::shared_ptr &context, const StereoPannerOptions &options) : AudioNodeHostObject(context->createStereoPanner(options), options) { + auto stereoPannerNode = std::static_pointer_cast(node_); + panParam_ = std::make_shared(stereoPannerNode->getPanParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(StereoPannerNodeHostObject, pan)); } JSI_PROPERTY_GETTER_IMPL(StereoPannerNodeHostObject, pan) { - auto stereoPannerNode = std::static_pointer_cast(node_); - auto panParam_ = std::make_shared(stereoPannerNode->getPanParam()); return jsi::Object::createFromHostObject(runtime, panParam_); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h index 8116c8a7c..d1b181474 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct StereoPannerOptions; class BaseAudioContext; +class AudioParamHostObject; class StereoPannerNodeHostObject : public AudioNodeHostObject { public: @@ -17,5 +18,8 @@ class StereoPannerNodeHostObject : public AudioNodeHostObject { const StereoPannerOptions &options); JSI_PROPERTY_GETTER_DECL(pan); + + private: + std::shared_ptr panParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp index 82239673a..d50d04867 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp @@ -6,69 +6,55 @@ #include #include +#include namespace audioapi { WaveShaperNodeHostObject::WaveShaperNodeHostObject( const std::shared_ptr &context, const WaveShaperOptions &options) - : AudioNodeHostObject(context->createWaveShaper(options), options) { - addGetters( - JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, oversample), - JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, curve)); - + : AudioNodeHostObject(context->createWaveShaper(options), options), + oversample_(options.oversample) { + addGetters(JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, oversample)); addSetters(JSI_EXPORT_PROPERTY_SETTER(WaveShaperNodeHostObject, oversample)); addFunctions(JSI_EXPORT_FUNCTION(WaveShaperNodeHostObject, setCurve)); } JSI_PROPERTY_GETTER_IMPL(WaveShaperNodeHostObject, oversample) { - auto waveShaperNode = std::static_pointer_cast(node_); - return jsi::String::createFromUtf8( - runtime, js_enum_parser::overSampleTypeToString(waveShaperNode->getOversample())); -} - -JSI_PROPERTY_GETTER_IMPL(WaveShaperNodeHostObject, curve) { - auto waveShaperNode = std::static_pointer_cast(node_); - auto curve = waveShaperNode->getCurve(); - - if (curve == nullptr) { - return jsi::Value::null(); - } - - // copy AudioArray holding curve data to avoid subsequent modifications - auto audioArrayBuffer = std::make_shared(*curve); - auto arrayBuffer = jsi::ArrayBuffer(runtime, audioArrayBuffer); - - auto float32ArrayCtor = runtime.global().getPropertyAsFunction(runtime, "Float32Array"); - auto float32Array = float32ArrayCtor.callAsConstructor(runtime, arrayBuffer).getObject(runtime); - float32Array.setExternalMemoryPressure(runtime, audioArrayBuffer->size()); - - return float32Array; + return jsi::String::createFromUtf8(runtime, js_enum_parser::overSampleTypeToString(oversample_)); } JSI_PROPERTY_SETTER_IMPL(WaveShaperNodeHostObject, oversample) { auto waveShaperNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - waveShaperNode->setOversample(js_enum_parser::overSampleTypeFromString(type)); + + auto oversample = js_enum_parser::overSampleTypeFromString(value.asString(runtime).utf8(runtime)); + auto event = [waveShaperNode, oversample](BaseAudioContext &) { + waveShaperNode->setOversample(oversample); + }; + waveShaperNode->scheduleAudioEvent(std::move(event)); + oversample_ = oversample; } JSI_HOST_FUNCTION_IMPL(WaveShaperNodeHostObject, setCurve) { auto waveShaperNode = std::static_pointer_cast(node_); - if (args[0].isNull()) { - waveShaperNode->setCurve(nullptr); - return jsi::Value::undefined(); - } + std::shared_ptr curve = nullptr; - auto arrayBuffer = - args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + if (args[0].isObject()) { + auto arrayBuffer = + args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + // *2 because it is copied to internal curve array for processing + thisValue.asObject(runtime).setExternalMemoryPressure(runtime, arrayBuffer.size(runtime) * 2); - auto curve = std::make_shared( - reinterpret_cast(arrayBuffer.data(runtime)), - static_cast(arrayBuffer.size(runtime) / sizeof(float))); + auto size = static_cast(arrayBuffer.size(runtime) / sizeof(float)); + curve = std::make_shared( + reinterpret_cast(arrayBuffer.data(runtime)), size); + } - waveShaperNode->setCurve(curve); - thisValue.asObject(runtime).setExternalMemoryPressure(runtime, arrayBuffer.size(runtime)); + auto event = [waveShaperNode, curve](BaseAudioContext &) { + waveShaperNode->setCurve(curve); + }; + waveShaperNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h index 857d2f18b..72fa5cd76 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h @@ -18,9 +18,11 @@ class WaveShaperNodeHostObject : public AudioNodeHostObject { const WaveShaperOptions &options); JSI_PROPERTY_GETTER_DECL(oversample); - JSI_PROPERTY_GETTER_DECL(curve); JSI_PROPERTY_SETTER_DECL(oversample); JSI_HOST_FUNCTION_DECL(setCurve); + + private: + OverSampleType oversample_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp index 9f503d682..7f3d33102 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp @@ -1,16 +1,24 @@ -#include - #include +#include #include +#include #include + +#include #include +#include namespace audioapi { AudioBufferBaseSourceNodeHostObject::AudioBufferBaseSourceNodeHostObject( const std::shared_ptr &node, const BaseAudioBufferSourceOptions &options) - : AudioScheduledSourceNodeHostObject(node, options) { + : AudioScheduledSourceNodeHostObject(node, options), pitchCorrection_(options.pitchCorrection) { + auto sourceNode = std::static_pointer_cast(node_); + detuneParam_ = std::make_shared(sourceNode->getDetuneParam()); + playbackRateParam_ = std::make_shared(sourceNode->getPlaybackRateParam()); + onPositionChangedInterval_ = sourceNode->getOnPositionChangedInterval(); + addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, detune), JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, playbackRate), @@ -26,55 +34,75 @@ AudioBufferBaseSourceNodeHostObject::AudioBufferBaseSourceNodeHostObject( } AudioBufferBaseSourceNodeHostObject::~AudioBufferBaseSourceNodeHostObject() { - auto sourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - sourceNode->setOnPositionChangedCallbackId(0); + setOnPositionChangedCallbackId(0); } JSI_PROPERTY_GETTER_IMPL(AudioBufferBaseSourceNodeHostObject, detune) { - auto sourceNode = std::static_pointer_cast(node_); - auto detune = sourceNode->getDetuneParam(); - auto detuneHostObject = std::make_shared(detune); - return jsi::Object::createFromHostObject(runtime, detuneHostObject); + return jsi::Object::createFromHostObject(runtime, detuneParam_); } JSI_PROPERTY_GETTER_IMPL(AudioBufferBaseSourceNodeHostObject, playbackRate) { - auto sourceNode = std::static_pointer_cast(node_); - auto playbackRate = sourceNode->getPlaybackRateParam(); - auto playbackRateHostObject = std::make_shared(playbackRate); - return jsi::Object::createFromHostObject(runtime, playbackRateHostObject); + return jsi::Object::createFromHostObject(runtime, playbackRateParam_); } JSI_PROPERTY_GETTER_IMPL(AudioBufferBaseSourceNodeHostObject, onPositionChangedInterval) { - auto sourceNode = std::static_pointer_cast(node_); - return {sourceNode->getOnPositionChangedInterval()}; + return {onPositionChangedInterval_}; } JSI_PROPERTY_SETTER_IMPL(AudioBufferBaseSourceNodeHostObject, onPositionChanged) { - auto sourceNode = std::static_pointer_cast(node_); - - sourceNode->setOnPositionChangedCallbackId(std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnPositionChangedCallbackId(callbackId); } JSI_PROPERTY_SETTER_IMPL(AudioBufferBaseSourceNodeHostObject, onPositionChangedInterval) { auto sourceNode = std::static_pointer_cast(node_); + auto interval = static_cast(value.getNumber()); sourceNode->setOnPositionChangedInterval(static_cast(value.getNumber())); + auto event = [sourceNode, interval](BaseAudioContext &) { + sourceNode->setOnPositionChangedInterval(interval); + }; + + sourceNode->scheduleAudioEvent(std::move(event)); + onPositionChangedInterval_ = interval; } JSI_HOST_FUNCTION_IMPL(AudioBufferBaseSourceNodeHostObject, getInputLatency) { - auto audioBufferBaseSourceNode = std::static_pointer_cast(node_); - - return audioBufferBaseSourceNode->getInputLatency(); + return {inputLatency_}; } JSI_HOST_FUNCTION_IMPL(AudioBufferBaseSourceNodeHostObject, getOutputLatency) { - auto audioBufferBaseSourceNode = std::static_pointer_cast(node_); + return {outputLatency_}; +} - return audioBufferBaseSourceNode->getOutputLatency(); +void AudioBufferBaseSourceNodeHostObject::setOnPositionChangedCallbackId(uint64_t callbackId) { + auto sourceNode = std::static_pointer_cast(node_); + + auto event = [sourceNode, callbackId](BaseAudioContext &) { + sourceNode->setOnPositionChangedCallbackId(callbackId); + }; + + sourceNode->unregisterOnPositionChangedCallback(onPositionChangedCallbackId_); + sourceNode->scheduleAudioEvent(std::move(event)); + onPositionChangedCallbackId_ = callbackId; +} + +void AudioBufferBaseSourceNodeHostObject::initStretch(int channelCount, float sampleRate) { + auto sourceNode = std::static_pointer_cast(node_); + auto stretch = std::make_shared>(); + stretch->presetDefault(channelCount, sampleRate); + inputLatency_ = + std::max(dsp::sampleFrameToTime(stretch->inputLatency(), node_->getContextSampleRate()), 0.0); + outputLatency_ = std::max( + dsp::sampleFrameToTime(stretch->outputLatency(), node_->getContextSampleRate()), 0.0); + + auto event = [sourceNode, stretch](BaseAudioContext &) { + sourceNode->initStretch(stretch); + }; + sourceNode->scheduleAudioEvent(std::move(event)); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.h index 7b2071d02..8f27cf2a6 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; class AudioBufferBaseSourceNode; struct BaseAudioBufferSourceOptions; +class AudioParamHostObject; class AudioBufferBaseSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -27,6 +28,20 @@ class AudioBufferBaseSourceNodeHostObject : public AudioScheduledSourceNodeHostO JSI_HOST_FUNCTION_DECL(getInputLatency); JSI_HOST_FUNCTION_DECL(getOutputLatency); + + protected: + std::shared_ptr detuneParam_; + std::shared_ptr playbackRateParam_; + + int onPositionChangedInterval_; + uint64_t onPositionChangedCallbackId_ = 0; + + double inputLatency_ = 0; + double outputLatency_ = 0; + bool pitchCorrection_; + + void setOnPositionChangedCallbackId(uint64_t callbackId); + void initStretch(int channelCount, float sampleRate); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h index e298650b8..0f8db07d9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h @@ -28,7 +28,8 @@ class AudioBufferHostObject : public JsiHostObject { } [[nodiscard]] inline size_t getSizeInBytes() const { - return audioBuffer_->getSize() * audioBuffer_->getNumberOfChannels() * sizeof(float); + // *2 because every time buffer is passed we create a copy of it. + return audioBuffer_->getSize() * audioBuffer_->getNumberOfChannels() * sizeof(float) * 2; } JSI_PROPERTY_GETTER_DECL(sampleRate); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp index 28b8c1fee..57b071d8c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include namespace audioapi { @@ -25,32 +27,26 @@ AudioBufferQueueSourceNodeHostObject::AudioBufferQueueSourceNodeHostObject( } AudioBufferQueueSourceNodeHostObject::~AudioBufferQueueSourceNodeHostObject() { - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioBufferQueueSourceNode->setOnBufferEndedCallbackId(0); + setOnBufferEndedCallbackId(0); } JSI_PROPERTY_SETTER_IMPL(AudioBufferQueueSourceNodeHostObject, onBufferEnded) { - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - - audioBufferQueueSourceNode->setOnBufferEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnBufferEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, start) { - auto when = args[0].getNumber(); - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - if (!args[1].isNumber()) { - audioBufferQueueSourceNode->start(when); - } else { - auto offset = args[1].getNumber(); + auto event = [audioBufferQueueSourceNode, + when = args[0].getNumber(), + offset = args[1].getNumber()](BaseAudioContext &) { audioBufferQueueSourceNode->start(when, offset); - } + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -58,7 +54,10 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, start) { JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, pause) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - audioBufferQueueSourceNode->pause(); + auto event = [audioBufferQueueSourceNode](BaseAudioContext &) { + audioBufferQueueSourceNode->pause(); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -68,18 +67,39 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, enqueueBuffer) { auto audioBufferHostObject = args[0].getObject(runtime).asHostObject(runtime); + // TODO: add optimized memory management for buffer changes, e.g. + // when the same buffer is reused across threads and + // buffer modification is not allowed on JS thread + auto copiedBuffer = std::make_shared(*audioBufferHostObject->audioBuffer_); + std::shared_ptr tailBuffer = nullptr; + + if (pitchCorrection_ && !stretchHasBeenInit_) { + initStretch(copiedBuffer->getNumberOfChannels(), copiedBuffer->getSampleRate()); + int extraTailFrames = + static_cast((inputLatency_ + outputLatency_) * copiedBuffer->getSampleRate()); + tailBuffer = std::make_shared( + copiedBuffer->getNumberOfChannels(), extraTailFrames, copiedBuffer->getSampleRate()); + tailBuffer->zero(); + stretchHasBeenInit_ = true; + } - auto bufferId = audioBufferQueueSourceNode->enqueueBuffer(audioBufferHostObject->audioBuffer_); + auto event = [audioBufferQueueSourceNode, copiedBuffer, bufferId = bufferId_, tailBuffer]( + BaseAudioContext &) { + audioBufferQueueSourceNode->enqueueBuffer(copiedBuffer, bufferId, tailBuffer); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); - return jsi::String::createFromUtf8(runtime, bufferId); + return jsi::String::createFromUtf8(runtime, std::to_string(bufferId_++)); } JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, dequeueBuffer) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - auto bufferId = static_cast(args[0].getNumber()); - - audioBufferQueueSourceNode->dequeueBuffer(bufferId); + auto event = [audioBufferQueueSourceNode, + bufferId = static_cast(args[0].getNumber())](BaseAudioContext &) { + audioBufferQueueSourceNode->dequeueBuffer(bufferId); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -87,9 +107,24 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, dequeueBuffer) { JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, clearBuffers) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - audioBufferQueueSourceNode->clearBuffers(); + auto event = [audioBufferQueueSourceNode](BaseAudioContext &) { + audioBufferQueueSourceNode->clearBuffers(); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } +void AudioBufferQueueSourceNodeHostObject::setOnBufferEndedCallbackId(uint64_t callbackId) { + auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); + + auto event = [audioBufferQueueSourceNode, callbackId](BaseAudioContext &) { + audioBufferQueueSourceNode->setOnBufferEndedCallbackId(callbackId); + }; + + audioBufferQueueSourceNode->unregisterOnBufferEndedCallback(onBufferEndedCallbackId_); + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); + onBufferEndedCallbackId_ = callbackId; +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h index ca39f64b6..59503ae32 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h @@ -25,6 +25,13 @@ class AudioBufferQueueSourceNodeHostObject : public AudioBufferBaseSourceNodeHos JSI_HOST_FUNCTION_DECL(enqueueBuffer); JSI_HOST_FUNCTION_DECL(dequeueBuffer); JSI_HOST_FUNCTION_DECL(clearBuffers); + + protected: + size_t bufferId_ = 0; + uint64_t onBufferEndedCallbackId_ = 0; + bool stretchHasBeenInit_ = false; + + void setOnBufferEndedCallbackId(uint64_t callbackId); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp index 494cfeaa0..02f120edf 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp @@ -1,21 +1,31 @@ #include +#include #include #include #include #include +#include #include +#include namespace audioapi { AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject( const std::shared_ptr &context, const AudioBufferSourceOptions &options) - : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options), options) { + : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options), options), + loop_(options.loop), + loopSkip_(options.loopSkip), + loopStart_(options.loopStart), + loopEnd_(options.loopEnd) { + if (options.buffer != nullptr) { + setBuffer(options.buffer); + } + addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loop), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopSkip), - JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, buffer), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopStart), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopEnd)); @@ -35,93 +45,91 @@ AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject( } AudioBufferSourceNodeHostObject::~AudioBufferSourceNodeHostObject() { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioBufferSourceNode->setOnLoopEndedCallbackId(0); + setOnLoopEndedCallbackId(0); } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loop) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loop = audioBufferSourceNode->getLoop(); - return {loop}; + return {loop_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopSkip = audioBufferSourceNode->getLoopSkip(); - return {loopSkip}; -} - -JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, buffer) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto buffer = audioBufferSourceNode->getBuffer(); - - if (!buffer) { - return jsi::Value::null(); - } - - auto bufferHostObject = std::make_shared(buffer); - auto jsiObject = jsi::Object::createFromHostObject(runtime, bufferHostObject); - jsiObject.setExternalMemoryPressure(runtime, bufferHostObject->getSizeInBytes() + 16); - return jsiObject; + return {loopSkip_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopStart = audioBufferSourceNode->getLoopStart(); - return {loopStart}; + return {loopStart_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopEnd = audioBufferSourceNode->getLoopEnd(); - return {loopEnd}; + return {loopEnd_}; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loop) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoop(value.getBool()); + auto loop = value.getBool(); + + auto event = [audioBufferSourceNode, loop](BaseAudioContext &) { + audioBufferSourceNode->setLoop(loop); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loop_ = loop; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopSkip(value.getBool()); + auto loopSkip = value.getBool(); + + auto event = [audioBufferSourceNode, loopSkip](BaseAudioContext &) { + audioBufferSourceNode->setLoopSkip(loopSkip); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopSkip_ = loopSkip; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopStart(value.getNumber()); + auto loopStart = value.getNumber(); + + auto event = [audioBufferSourceNode, loopStart](BaseAudioContext &) { + audioBufferSourceNode->setLoopStart(loopStart); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopStart_ = loopStart; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopEnd(value.getNumber()); + auto loopEnd = value.getNumber(); + + auto event = [audioBufferSourceNode, loopEnd](BaseAudioContext &) { + audioBufferSourceNode->setLoopEnd(loopEnd); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopEnd_ = loopEnd; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, onLoopEnded) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - - audioBufferSourceNode->setOnLoopEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnLoopEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, start) { - auto when = args[0].getNumber(); - auto offset = args[1].getNumber(); - auto audioBufferSourceNode = std::static_pointer_cast(node_); - if (args[2].isUndefined()) { - audioBufferSourceNode->start(when, offset); - - return jsi::Value::undefined(); - } - - auto duration = args[2].getNumber(); - audioBufferSourceNode->start(when, offset, duration); + auto event = [audioBufferSourceNode, + when = args[0].getNumber(), + offset = args[1].getNumber(), + duration = args[2].isUndefined() ? -1 : args[2].getNumber()](BaseAudioContext &) { + audioBufferSourceNode->start(when, offset, duration); + }; + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -130,15 +138,74 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, setBuffer) { auto audioBufferSourceNode = std::static_pointer_cast(node_); if (args[0].isNull()) { - audioBufferSourceNode->setBuffer(std::shared_ptr(nullptr)); - return jsi::Value::undefined(); + setBuffer(nullptr); + } else { + auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); + thisValue.asObject(runtime).setExternalMemoryPressure( + runtime, bufferHostObject->getSizeInBytes()); + + setBuffer(bufferHostObject->audioBuffer_); } - auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - thisValue.asObject(runtime).setExternalMemoryPressure( - runtime, bufferHostObject->getSizeInBytes() + 16); - audioBufferSourceNode->setBuffer(bufferHostObject->audioBuffer_); return jsi::Value::undefined(); } +void AudioBufferSourceNodeHostObject::setOnLoopEndedCallbackId(uint64_t callbackId) { + auto audioBufferSourceNode = std::static_pointer_cast(node_); + + auto event = [audioBufferSourceNode, callbackId](BaseAudioContext &) { + audioBufferSourceNode->setOnLoopEndedCallbackId(callbackId); + }; + + audioBufferSourceNode->unregisterOnLoopEndedCallback(onLoopEndedCallbackId_); + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + onLoopEndedCallbackId_ = callbackId; +} + +void AudioBufferSourceNodeHostObject::setBuffer(const std::shared_ptr &buffer) { + // TODO: add optimized memory management for buffer changes, e.g. + // when the same buffer is reused across threads and + // buffer modification is not allowed on JS thread + auto audioBufferSourceNode = std::static_pointer_cast(node_); + + std::shared_ptr copiedBuffer; + std::shared_ptr playbackRateBuffer; + std::shared_ptr audioBuffer; + + if (buffer == nullptr) { + copiedBuffer = nullptr; + playbackRateBuffer = nullptr; + audioBuffer = std::make_shared( + RENDER_QUANTUM_SIZE, 1, audioBufferSourceNode->getContextSampleRate()); + } else { + if (pitchCorrection_) { + initStretch(static_cast(buffer->getNumberOfChannels()), buffer->getSampleRate()); + int extraTailFrames = + static_cast((inputLatency_ + outputLatency_) * buffer->getSampleRate()); + size_t totalSize = buffer->getSize() + extraTailFrames; + copiedBuffer = std::make_shared( + totalSize, buffer->getNumberOfChannels(), buffer->getSampleRate()); + copiedBuffer->copy(*buffer, 0, 0, buffer->getSize()); + copiedBuffer->zero(buffer->getSize(), extraTailFrames); + } else { + copiedBuffer = std::make_shared(*buffer); + } + + playbackRateBuffer = std::make_shared( + 3 * RENDER_QUANTUM_SIZE, + copiedBuffer->getNumberOfChannels(), + audioBufferSourceNode->getContextSampleRate()); + audioBuffer = std::make_shared( + RENDER_QUANTUM_SIZE, + copiedBuffer->getNumberOfChannels(), + audioBufferSourceNode->getContextSampleRate()); + } + + auto event = + [audioBufferSourceNode, copiedBuffer, playbackRateBuffer, audioBuffer](BaseAudioContext &) { + audioBufferSourceNode->setBuffer(copiedBuffer, playbackRateBuffer, audioBuffer); + }; + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h index 773d3bdd1..45f81fd30 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h @@ -9,6 +9,8 @@ using namespace facebook; struct AudioBufferSourceOptions; class BaseAudioContext; +class AudioBufferHostObject; +class AudioBuffer; class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObject { public: @@ -20,7 +22,6 @@ class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObje JSI_PROPERTY_GETTER_DECL(loop); JSI_PROPERTY_GETTER_DECL(loopSkip); - JSI_PROPERTY_GETTER_DECL(buffer); JSI_PROPERTY_GETTER_DECL(loopStart); JSI_PROPERTY_GETTER_DECL(loopEnd); @@ -32,6 +33,16 @@ class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObje JSI_HOST_FUNCTION_DECL(start); JSI_HOST_FUNCTION_DECL(setBuffer); + + protected: + bool loop_; + bool loopSkip_; + double loopStart_; + double loopEnd_; + uint64_t onLoopEndedCallbackId_ = 0; + + void setOnLoopEndedCallbackId(uint64_t callbackId); + void setBuffer(const std::shared_ptr &buffer); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp index 0f6c06c38..099be5b6f 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace audioapi { @@ -18,33 +19,49 @@ AudioScheduledSourceNodeHostObject::AudioScheduledSourceNodeHostObject( } AudioScheduledSourceNodeHostObject::~AudioScheduledSourceNodeHostObject() { - auto audioScheduledSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioScheduledSourceNode->setOnEndedCallbackId(0); + setOnEndedCallbackId(0); } JSI_PROPERTY_SETTER_IMPL(AudioScheduledSourceNodeHostObject, onEnded) { - auto audioScheduleSourceNode = std::static_pointer_cast(node_); - - audioScheduleSourceNode->setOnEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioScheduledSourceNodeHostObject, start) { - auto when = args[0].getNumber(); auto audioScheduleSourceNode = std::static_pointer_cast(node_); - audioScheduleSourceNode->start(when); + + auto event = [audioScheduleSourceNode, when = args[0].getNumber()](BaseAudioContext &) { + audioScheduleSourceNode->start(when); + }; + audioScheduleSourceNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioScheduledSourceNodeHostObject, stop) { - auto time = args[0].getNumber(); auto audioScheduleSourceNode = std::static_pointer_cast(node_); - audioScheduleSourceNode->stop(time); + + auto event = [audioScheduleSourceNode, when = args[0].getNumber()](BaseAudioContext &) { + audioScheduleSourceNode->stop(when); + }; + audioScheduleSourceNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } +void AudioScheduledSourceNodeHostObject::setOnEndedCallbackId(uint64_t callbackId) { + auto sourceNode = std::static_pointer_cast(node_); + + auto event = [sourceNode, callbackId](BaseAudioContext &) { + sourceNode->setOnEndedCallbackId(callbackId); + }; + + sourceNode->unregisterOnEndedCallback(onEndedCallbackId_); + sourceNode->scheduleAudioEvent(std::move(event)); + onEndedCallbackId_ = callbackId; +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h index 50e1548ca..717853115 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h @@ -22,5 +22,10 @@ class AudioScheduledSourceNodeHostObject : public AudioNodeHostObject { JSI_HOST_FUNCTION_DECL(start); JSI_HOST_FUNCTION_DECL(stop); + + private: + uint64_t onEndedCallbackId_ = 0; + + void setOnEndedCallbackId(uint64_t callbackId); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp index bc53bdcb9..b4522932b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp @@ -11,12 +11,13 @@ ConstantSourceNodeHostObject::ConstantSourceNodeHostObject( const std::shared_ptr &context, const ConstantSourceOptions &options) : AudioScheduledSourceNodeHostObject(context->createConstantSource(options), options) { + auto constantSourceNode = std::static_pointer_cast(node_); + offsetParam_ = std::make_shared(constantSourceNode->getOffsetParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(ConstantSourceNodeHostObject, offset)); } JSI_PROPERTY_GETTER_IMPL(ConstantSourceNodeHostObject, offset) { - auto constantSourceNode = std::static_pointer_cast(node_); - auto offsetParam_ = std::make_shared(constantSourceNode->getOffsetParam()); return jsi::Object::createFromHostObject(runtime, offsetParam_); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h index 7809f846e..9a57f2fe0 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h @@ -9,6 +9,7 @@ using namespace facebook; struct ConstantSourceOptions; class BaseAudioContext; +class AudioParamHostObject; class ConstantSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -17,5 +18,8 @@ class ConstantSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { const ConstantSourceOptions &options); JSI_PROPERTY_GETTER_DECL(offset); + + private: + std::shared_ptr offsetParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp index c0311d39b..c88189572 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp @@ -7,13 +7,19 @@ #include #include #include +#include namespace audioapi { OscillatorNodeHostObject::OscillatorNodeHostObject( const std::shared_ptr &context, const OscillatorOptions &options) - : AudioScheduledSourceNodeHostObject(context->createOscillator(options), options) { + : AudioScheduledSourceNodeHostObject(context->createOscillator(options), options), + type_(options.type) { + auto oscillatorNode = std::static_pointer_cast(node_); + frequencyParam_ = std::make_shared(oscillatorNode->getFrequencyParam()); + detuneParam_ = std::make_shared(oscillatorNode->getDetuneParam()); + addGetters( JSI_EXPORT_PROPERTY_GETTER(OscillatorNodeHostObject, frequency), JSI_EXPORT_PROPERTY_GETTER(OscillatorNodeHostObject, detune), @@ -25,35 +31,39 @@ OscillatorNodeHostObject::OscillatorNodeHostObject( } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, frequency) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto frequencyParam_ = - std::make_shared(oscillatorNode->getFrequencyParam()); return jsi::Object::createFromHostObject(runtime, frequencyParam_); } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, detune) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto detuneParam_ = std::make_shared(oscillatorNode->getDetuneParam()); return jsi::Object::createFromHostObject(runtime, detuneParam_); } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, type) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto waveType = oscillatorNode->getType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::oscillatorTypeToString(waveType)); + return jsi::String::createFromUtf8(runtime, js_enum_parser::oscillatorTypeToString(type_)); } JSI_HOST_FUNCTION_IMPL(OscillatorNodeHostObject, setPeriodicWave) { auto oscillatorNode = std::static_pointer_cast(node_); auto periodicWave = args[0].getObject(runtime).getHostObject(runtime); - oscillatorNode->setPeriodicWave(periodicWave->periodicWave_); + + auto event = [oscillatorNode, periodicWave = periodicWave->periodicWave_](BaseAudioContext &) { + oscillatorNode->setPeriodicWave(periodicWave); + }; + oscillatorNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } JSI_PROPERTY_SETTER_IMPL(OscillatorNodeHostObject, type) { auto oscillatorNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - oscillatorNode->setType(js_enum_parser::oscillatorTypeFromString(type)); + auto type = js_enum_parser::oscillatorTypeFromString(value.asString(runtime).utf8(runtime)); + + auto event = [oscillatorNode, type](BaseAudioContext &) { + oscillatorNode->setType(type); + }; + type_ = type; + + oscillatorNode->scheduleAudioEvent(std::move(event)); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h index 6668eb623..99c238757 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h @@ -10,6 +10,7 @@ using namespace facebook; struct OscillatorOptions; class BaseAudioContext; +class AudioParamHostObject; class OscillatorNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -24,5 +25,10 @@ class OscillatorNodeHostObject : public AudioScheduledSourceNodeHostObject { JSI_HOST_FUNCTION_DECL(setPeriodicWave); JSI_PROPERTY_SETTER_DECL(type); + + private: + std::shared_ptr frequencyParam_; + std::shared_ptr detuneParam_; + OscillatorType type_; }; } // namespace audioapi 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 deleted file mode 100644 index 9643b67f9..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -namespace audioapi { - -StreamerNodeHostObject::StreamerNodeHostObject( - const std::shared_ptr &context, - const StreamerOptions &options) - : AudioScheduledSourceNodeHostObject(context->createStreamer(options), options) { - 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) { -#if !RN_AUDIO_API_FFMPEG_DISABLED - auto streamerNode = std::static_pointer_cast(node_); - auto path = args[0].getString(runtime).utf8(runtime); - auto result = streamerNode->initialize(path); - return {result}; -#else - return false; -#endif -} - -} // namespace audioapi 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 a5e265a4e..38fb94a8d 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 @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include @@ -14,15 +17,13 @@ class StreamerNodeHostObject : public AudioScheduledSourceNodeHostObject { public: explicit StreamerNodeHostObject( const std::shared_ptr &context, - const StreamerOptions &options); + const StreamerOptions &options) + : AudioScheduledSourceNodeHostObject(context->createStreamer(options), options) {} [[nodiscard]] static inline size_t getSizeInBytes() { return SIZE; } - JSI_PROPERTY_GETTER_DECL(streamPath); - JSI_HOST_FUNCTION_DECL(initialize); - private: static constexpr size_t SIZE = 4'000'000; // 4MB }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp index 429fc31c2..8c6f0ba44 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp @@ -1,32 +1,8 @@ #include #include -using WindowType = audioapi::AnalyserNode::WindowType; - namespace audioapi::js_enum_parser { -WindowType windowTypeFromString(const std::string &type) { - if (type == "blackman") { - return WindowType::BLACKMAN; - } - if (type == "hann") { - return WindowType::HANN; - } - - throw std::invalid_argument("Unknown window type"); -} - -std::string windowTypeToString(WindowType type) { - switch (type) { - case WindowType::BLACKMAN: - return "blackman"; - case WindowType::HANN: - return "hann"; - default: - throw std::invalid_argument("Unknown window type"); - } -} - BiquadFilterType filterTypeFromString(const std::string &type) { if (type == "lowpass") return BiquadFilterType::LOWPASS; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h index fadf44720..089befda7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h @@ -11,8 +11,6 @@ #include namespace audioapi::js_enum_parser { -std::string windowTypeToString(AnalyserNode::WindowType type); -AnalyserNode::WindowType windowTypeFromString(const std::string &type); std::string overSampleTypeToString(OverSampleType type); OverSampleType overSampleTypeFromString(const std::string &type); std::string oscillatorTypeToString(OscillatorType type); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp index 0e6a6c7f4..efb3a0d08 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp @@ -51,7 +51,7 @@ bool AudioContext::resume() { return true; } - if (isInitialized_ && audioPlayer_->resume()) { + if (isInitialized_.load(std::memory_order_acquire) && audioPlayer_->resume()) { setState(ContextState::RUNNING); return true; } @@ -79,8 +79,8 @@ bool AudioContext::start() { return false; } - if (!isInitialized_ && audioPlayer_->start()) { - isInitialized_ = true; + if (!isInitialized_.load(std::memory_order_acquire) && audioPlayer_->start()) { + isInitialized_.store(true, std::memory_order_release); setState(ContextState::RUNNING); return true; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h index 967b38426..57295baec 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h @@ -33,7 +33,7 @@ class AudioContext : public BaseAudioContext { #else std::shared_ptr audioPlayer_; #endif - bool isInitialized_; + std::atomic isInitialized_{false}; bool isDriverRunning() const override; 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 d5d3d1c29..ed0540b11 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 @@ -24,31 +24,15 @@ AudioNode::AudioNode( } AudioNode::~AudioNode() { - if (isInitialized_) { + if (isInitialized_.load(std::memory_order_acquire)) { cleanup(); } } -int AudioNode::getNumberOfInputs() const { - return numberOfInputs_; -} - -int AudioNode::getNumberOfOutputs() const { - return numberOfOutputs_; -} - size_t AudioNode::getChannelCount() const { return channelCount_; } -ChannelCountMode AudioNode::getChannelCountMode() const { - return channelCountMode_; -} - -ChannelInterpretation AudioNode::getChannelInterpretation() const { - return channelInterpretation_; -} - void AudioNode::connect(const std::shared_ptr &node) { if (std::shared_ptr context = context_.lock()) { context->getGraphManager()->addPendingNodeConnection( @@ -120,7 +104,7 @@ std::shared_ptr AudioNode::processAudio( const std::shared_ptr &outputBuffer, int framesToProcess, bool checkIsAlreadyProcessed) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return outputBuffer; } @@ -272,7 +256,7 @@ void AudioNode::onInputDisabled() { } void AudioNode::onInputConnected(AudioNode *node) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return; } @@ -284,7 +268,7 @@ void AudioNode::onInputConnected(AudioNode *node) { } void AudioNode::onInputDisconnected(AudioNode *node) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return; } @@ -300,7 +284,7 @@ void AudioNode::onInputDisconnected(AudioNode *node) { } void AudioNode::cleanup() { - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); for (auto it = outputNodes_.begin(), end = outputNodes_.end(); it != end; ++it) { it->get()->onInputDisconnected(this); 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 2b7abe32d..4890f3848 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 @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,12 +10,12 @@ #include #include #include +#include #include namespace audioapi { class AudioBuffer; -class BaseAudioContext; class AudioParam; class AudioNode : public std::enable_shared_from_this { @@ -24,11 +25,7 @@ class AudioNode : public std::enable_shared_from_this { const AudioNodeOptions &options = AudioNodeOptions()); virtual ~AudioNode(); - int getNumberOfInputs() const; - int getNumberOfOutputs() const; size_t getChannelCount() const; - ChannelCountMode getChannelCountMode() const; - ChannelInterpretation getChannelInterpretation() const; void connect(const std::shared_ptr &node); void connect(const std::shared_ptr ¶m); void disconnect(); @@ -39,10 +36,31 @@ class AudioNode : public std::enable_shared_from_this { int framesToProcess, bool checkIsAlreadyProcessed); + float getContextSampleRate() const { + if (std::shared_ptr context = context_.lock()) { + return context->getSampleRate(); + } + + return DEFAULT_SAMPLE_RATE; + } + + float getNyquistFrequency() const { + return getContextSampleRate() / 2.0f; + } + + /// @note JS Thread only bool isEnabled() const; + /// @note JS Thread only bool requiresTailProcessing() const; - void enable(); - virtual void disable(); + + template + bool inline scheduleAudioEvent(F &&event) noexcept { + if (std::shared_ptr context = context_.lock()) { + return context->scheduleAudioEvent(std::forward(event)); + } + + return false; + } protected: friend class AudioGraphManager; @@ -65,12 +83,15 @@ class AudioNode : public std::enable_shared_from_this { std::unordered_set> outputParams_ = {}; int numberOfEnabledInputNodes_ = 0; - bool isInitialized_ = false; - bool isEnabled_ = true; + std::atomic isInitialized_ = false; std::size_t lastRenderedFrame_{SIZE_MAX}; + void enable(); + virtual void disable(); + private: + bool isEnabled_ = true; std::vector> inputBuffers_ = {}; virtual std::shared_ptr processInputs( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp index be3b6f82c..bb440803b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp @@ -19,7 +19,6 @@ AudioParam::AudioParam( minValue_(minValue), maxValue_(maxValue), eventsQueue_(), - eventScheduler_(32), startTime_(0), endTime_(0), startValue_(defaultValue), @@ -48,130 +47,117 @@ float AudioParam::getValueAtTime(double time) { } // Calculate value using the current automation function and clamp to valid - setValue(calculateValue_(startTime_, endTime_, startValue_, endValue_, time)); - return value_; + auto value = calculateValue_(startTime_, endTime_, startValue_, endValue_, time); + setValue(value); + return value; } void AudioParam::setValueAtTime(float value, double startTime) { - auto event = [value, startTime](AudioParam ¶m) { - // Ignore events scheduled before the end of existing automation - if (startTime < param.getQueueEndTime()) { - return; - } + // Ignore events scheduled before the end of existing automation + if (startTime < this->getQueueEndTime()) { + return; + } - // Step function: instant change at startTime - auto calculateValue = - [](double startTime, double /* endTime */, float startValue, float endValue, double time) { - if (time < startTime) { - return startValue; - } - - return endValue; - }; - - param.updateQueue(ParamChangeEvent( - startTime, - startTime, - param.getQueueEndValue(), - value, - std::move(calculateValue), - ParamChangeEventType::SET_VALUE)); - }; - eventScheduler_.scheduleEvent(std::move(event)); + // Step function: instant change at startTime + auto calculateValue = + [](double startTime, double /* endTime */, float startValue, float endValue, double time) { + if (time < startTime) { + return startValue; + } + + return endValue; + }; + + this->updateQueue(ParamChangeEvent( + startTime, + startTime, + this->getQueueEndValue(), + value, + std::move(calculateValue), + ParamChangeEventType::SET_VALUE)); } void AudioParam::linearRampToValueAtTime(float value, double endTime) { - auto event = [value, endTime](AudioParam ¶m) { - // Ignore events scheduled before the end of existing automation - if (endTime < param.getQueueEndTime()) { - return; - } + // Ignore events scheduled before the end of existing automation + if (endTime < this->getQueueEndTime()) { + return; + } - // Linear interpolation function - auto calculateValue = - [](double startTime, double endTime, float startValue, float endValue, double time) { - if (time < startTime) { - return startValue; - } - - if (time < endTime) { - return static_cast( - startValue + (endValue - startValue) * (time - startTime) / (endTime - startTime)); - } - - return endValue; - }; - - param.updateQueue(ParamChangeEvent( - param.getQueueEndTime(), - endTime, - param.getQueueEndValue(), - value, - std::move(calculateValue), - ParamChangeEventType::LINEAR_RAMP)); - }; - eventScheduler_.scheduleEvent(std::move(event)); + // Linear interpolation function + auto calculateValue = + [](double startTime, double endTime, float startValue, float endValue, double time) { + if (time < startTime) { + return startValue; + } + + if (time < endTime) { + return static_cast( + startValue + (endValue - startValue) * (time - startTime) / (endTime - startTime)); + } + + return endValue; + }; + + this->updateQueue(ParamChangeEvent( + this->getQueueEndTime(), + endTime, + this->getQueueEndValue(), + value, + std::move(calculateValue), + ParamChangeEventType::LINEAR_RAMP)); } void AudioParam::exponentialRampToValueAtTime(float value, double endTime) { - auto event = [value, endTime](AudioParam ¶m) { - if (endTime <= param.getQueueEndTime()) { - return; - } + if (endTime <= this->getQueueEndTime()) { + return; + } - // Exponential curve function using power law - auto calculateValue = - [](double startTime, double endTime, float startValue, float endValue, double time) { - if (time < startTime) { - return startValue; - } - - if (time < endTime) { - return static_cast( - startValue * - pow(endValue / startValue, (time - startTime) / (endTime - startTime))); - } - - return endValue; - }; - - param.updateQueue(ParamChangeEvent( - param.getQueueEndTime(), - endTime, - param.getQueueEndValue(), - value, - std::move(calculateValue), - ParamChangeEventType::EXPONENTIAL_RAMP)); - }; - eventScheduler_.scheduleEvent(std::move(event)); + // Exponential curve function using power law + auto calculateValue = + [](double startTime, double endTime, float startValue, float endValue, double time) { + if (time < startTime) { + return startValue; + } + + if (time < endTime) { + return static_cast( + startValue * pow(endValue / startValue, (time - startTime) / (endTime - startTime))); + } + + return endValue; + }; + + this->updateQueue(ParamChangeEvent( + this->getQueueEndTime(), + endTime, + this->getQueueEndValue(), + value, + std::move(calculateValue), + ParamChangeEventType::EXPONENTIAL_RAMP)); } void AudioParam::setTargetAtTime(float target, double startTime, double timeConstant) { - auto event = [target, startTime, timeConstant](AudioParam ¶m) { - if (startTime <= param.getQueueEndTime()) { - return; + if (startTime <= this->getQueueEndTime()) { + return; + } + // Exponential decay function towards target value + auto calculateValue = [timeConstant, target]( + double startTime, double, float startValue, float, double time) { + if (time < startTime) { + return startValue; } - // Exponential decay function towards target value - auto calculateValue = [timeConstant, target]( - double startTime, double, float startValue, float, double time) { - if (time < startTime) { - return startValue; - } - - return static_cast( - target + (startValue - target) * exp(-(time - startTime) / timeConstant)); - }; - param.updateQueue(ParamChangeEvent( - startTime, - startTime, // SetTarget events have infinite duration conceptually - param.getQueueEndValue(), - param.getQueueEndValue(), // End value is not meaningful for - // infinite events - std::move(calculateValue), - ParamChangeEventType::SET_TARGET)); - }; - eventScheduler_.scheduleEvent(std::move(event)); + return static_cast( + target + (startValue - target) * exp(-(time - startTime) / timeConstant)); + }; + this->updateQueue(ParamChangeEvent( + startTime, + startTime, // SetTarget events have infinite duration conceptually + this->getQueueEndValue(), + this->getQueueEndValue(), // End value is not meaningful for + // infinite events + std::move(calculateValue), + ParamChangeEventType::SET_TARGET)); } void AudioParam::setValueCurveAtTime( @@ -179,54 +165,45 @@ void AudioParam::setValueCurveAtTime( size_t length, double startTime, double duration) { - auto event = [values, length, startTime, duration](AudioParam ¶m) { - if (startTime <= param.getQueueEndTime()) { - return; - } - - auto calculateValue = - [values, length]( - double startTime, double endTime, float startValue, float endValue, double time) { - if (time < startTime) { - return startValue; - } - - if (time < endTime) { - // Calculate position in the array based on time progress - auto k = static_cast(std::floor( - static_cast(length - 1) / (endTime - startTime) * (time - startTime))); - // Calculate interpolation factor between adjacent array elements - auto factor = static_cast( - (time - startTime) * static_cast(length - 1) / (endTime - startTime) - k); - return dsp::linearInterpolate(values->span(), k, k + 1, factor); - } - - return endValue; - }; - - param.updateQueue(ParamChangeEvent( - startTime, - startTime + duration, - param.getQueueEndValue(), - values->span()[length - 1], - std::move(calculateValue), - ParamChangeEventType::SET_VALUE_CURVE)); - }; + if (startTime <= this->getQueueEndTime()) { + return; + } - /// Schedules an event that modifies this param - /// It will be executed on next audio render cycle - eventScheduler_.scheduleEvent(std::move(event)); + auto calculateValue = + [values, length]( + double startTime, double endTime, float startValue, float endValue, double time) { + if (time < startTime) { + return startValue; + } + + if (time < endTime) { + // Calculate position in the array based on time progress + auto k = static_cast(std::floor( + static_cast(length - 1) / (endTime - startTime) * (time - startTime))); + // Calculate interpolation factor between adjacent array elements + auto factor = static_cast( + (time - startTime) * static_cast(length - 1) / (endTime - startTime) - k); + return dsp::linearInterpolate(values->span(), k, k + 1, factor); + } + + return endValue; + }; + + this->updateQueue(ParamChangeEvent( + startTime, + startTime + duration, + this->getQueueEndValue(), + values->span()[length - 1], + std::move(calculateValue), + ParamChangeEventType::SET_VALUE_CURVE)); } void AudioParam::cancelScheduledValues(double cancelTime) { - eventScheduler_.scheduleEvent( - [cancelTime](AudioParam ¶m) { param.eventsQueue_.cancelScheduledValues(cancelTime); }); + this->eventsQueue_.cancelScheduledValues(cancelTime); } void AudioParam::cancelAndHoldAtTime(double cancelTime) { - eventScheduler_.scheduleEvent([cancelTime](AudioParam ¶m) { - param.eventsQueue_.cancelAndHoldAtTime(cancelTime, param.endTime_); - }); + this->eventsQueue_.cancelAndHoldAtTime(cancelTime, this->endTime_); } void AudioParam::addInputNode(AudioNode *node) { @@ -256,7 +233,6 @@ std::shared_ptr AudioParam::calculateInputs( } std::shared_ptr AudioParam::processARateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBuffer = calculateInputs(audioBuffer_, framesToProcess); std::shared_ptr context = context_.lock(); @@ -278,7 +254,6 @@ std::shared_ptr AudioParam::processARateParam(int framesToProcess, } float AudioParam::processKRateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBuffer = calculateInputs(audioBuffer_, framesToProcess); // Return block-rate parameter value plus first sample of input modulation diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h index 4ab042617..06abccab8 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -22,72 +23,75 @@ class AudioParam { float maxValue, const std::shared_ptr &context); - /// JS-Thread only methods - /// These methods are called only from HostObjects invoked on the JS thread. - - // JS-Thread only [[nodiscard]] inline float getValue() const noexcept { return value_.load(std::memory_order_relaxed); } - // JS-Thread only [[nodiscard]] inline float getDefaultValue() const noexcept { return defaultValue_; } - // JS-Thread only [[nodiscard]] inline float getMinValue() const noexcept { return minValue_; } - // JS-Thread only [[nodiscard]] inline float getMaxValue() const noexcept { return maxValue_; } - // JS-Thread only inline void setValue(float value) { value_.store(std::clamp(value, minValue_, maxValue_), std::memory_order_release); } - // JS-Thread only + /// @note Audio Thread only void setValueAtTime(float value, double startTime); - // JS-Thread only + /// @note Audio Thread only void linearRampToValueAtTime(float value, double endTime); - // JS-Thread only + /// @note Audio Thread only void exponentialRampToValueAtTime(float value, double endTime); - // JS-Thread only + /// @note Audio Thread only void setTargetAtTime(float target, double startTime, double timeConstant); - // JS-Thread only + /// @note Audio Thread only void setValueCurveAtTime( const std::shared_ptr &values, size_t length, double startTime, double duration); - // JS-Thread only + /// @note Audio Thread only void cancelScheduledValues(double cancelTime); - // JS-Thread only + /// @note Audio Thread only void cancelAndHoldAtTime(double cancelTime); + template < + typename F, + typename = std::enable_if_t, BaseAudioContext &>>> + bool inline scheduleAudioEvent(F &&event) noexcept { + if (std::shared_ptr context = context_.lock()) { + return context->scheduleAudioEvent(std::forward(event)); + } + + return false; + } + /// Audio-Thread only methods /// These methods are called only from the Audio rendering thread. - // Audio-Thread only (indirectly through AudioNode::connectParam by AudioGraphManager) + /// @note Audio Thread only void addInputNode(AudioNode *node); - // Audio-Thread only (indirectly through AudioNode::disconnectParam by AudioGraphManager) + /// @note Audio Thread only void removeInputNode(AudioNode *node); - // Audio-Thread only + /// @note Audio Thread only std::shared_ptr processARateParam(int framesToProcess, double time); - // Audio-Thread only + /// @note Audio Thread only float processKRateParam(int framesToProcess, double time); private: @@ -99,7 +103,6 @@ class AudioParam { float maxValue_; AudioParamEventQueue eventsQueue_; - CrossThreadEventScheduler eventScheduler_; // Current automation state (cached for performance) double startTime_; @@ -131,11 +134,6 @@ class AudioParam { return eventsQueue_.back().getEndValue(); } - /// @brief Process all scheduled events. - inline void processScheduledEvents() noexcept(noexcept(eventScheduler_.processAllEvents(*this))) { - eventScheduler_.processAllEvents(*this); - } - /// @brief Update the parameter queue with a new event. /// @param event The new event to add to the queue. /// @note Handles connecting start value of the new event to the end value of the previous event. 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 254fbd210..5d0584b31 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 @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -42,7 +41,8 @@ BaseAudioContext::BaseAudioContext( sampleRate_(sampleRate), graphManager_(std::make_shared()), audioEventHandlerRegistry_(audioEventHandlerRegistry), - runtimeRegistry_(runtimeRegistry) {} + runtimeRegistry_(runtimeRegistry), + audioEventScheduler_(AUDIO_SCHEDULER_CAPACITY) {} void BaseAudioContext::initialize() { destination_ = std::make_shared(shared_from_this()); @@ -217,10 +217,6 @@ std::shared_ptr BaseAudioContext::createWaveShaper( return waveShaper; } -float BaseAudioContext::getNyquistFrequency() const { - return getSampleRate() / 2.0f; -} - std::shared_ptr BaseAudioContext::getBasicWaveForm(OscillatorType type) { switch (type) { case OscillatorType::SINE: 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 26213374e..fa3c9efad 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 @@ -3,12 +3,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace audioapi { @@ -103,13 +105,27 @@ class BaseAudioContext : public std::enable_shared_from_this { std::shared_ptr createWaveShaper(const WaveShaperOptions &options); std::shared_ptr getBasicWaveForm(OscillatorType type); - [[nodiscard]] float getNyquistFrequency() const; std::shared_ptr getGraphManager() const; std::shared_ptr getAudioEventHandlerRegistry() const; const RuntimeRegistry &getRuntimeRegistry() const; virtual void initialize(); + void inline processAudioEvents() { + audioEventScheduler_.processAllEvents(*this); + } + + template + bool inline scheduleAudioEvent(F &&event) noexcept { + if (getState() != ContextState::RUNNING) { + processAudioEvents(); + event(*this); + return true; + } + + return audioEventScheduler_.scheduleEvent(std::forward(event)); + } + protected: std::shared_ptr destination_; @@ -125,6 +141,9 @@ class BaseAudioContext : public std::enable_shared_from_this { std::shared_ptr cachedSawtoothWave_ = nullptr; std::shared_ptr cachedTriangleWave_ = nullptr; + static constexpr size_t AUDIO_SCHEDULER_CAPACITY = 1024; + CrossThreadEventScheduler audioEventScheduler_; + [[nodiscard]] virtual bool isDriverRunning() const = 0; }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.cpp index 37ae786ef..2b21002af 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.cpp @@ -1,4 +1,4 @@ -#include "OfflineAudioContext.h" +#include #include #include diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.h index 98d0fa838..ce2015d4a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/OfflineAudioContext.h @@ -1,7 +1,7 @@ #pragma once +#include #include -#include "BaseAudioContext.h" #include #include @@ -22,9 +22,13 @@ class OfflineAudioContext : public BaseAudioContext { const RuntimeRegistry &runtimeRegistry); ~OfflineAudioContext() override; + /// @note JS Thread only void resume(); + + /// @note JS Thread only void suspend(double when, const OfflineAudioContextSuspendCallback &callback); + /// @note JS Thread only void startRendering(OfflineAudioContextResultCallback callback); private: 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 20c1ee4b6..8f1f3bdb5 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 @@ -2,9 +2,7 @@ #include #include #include -#include #include -#include #include #include @@ -18,73 +16,27 @@ AnalyserNode::AnalyserNode( const std::shared_ptr &context, const AnalyserOptions &options) : AudioNode(context, options), - fftSize_(options.fftSize), - minDecibels_(options.minDecibels), - maxDecibels_(options.maxDecibels), - smoothingTimeConstant_(options.smoothingTimeConstant), - windowType_(WindowType::BLACKMAN), inputArray_(std::make_unique(MAX_FFT_SIZE * 2)), downMixBuffer_( std::make_unique(RENDER_QUANTUM_SIZE, 1, context->getSampleRate())), - tempArray_(std::make_unique(fftSize_)), - fft_(std::make_unique(fftSize_)), - complexData_(std::vector>(fftSize_)), - magnitudeArray_(std::make_unique(fftSize_ / 2)) { - setWindowData(windowType_, fftSize_); - isInitialized_ = true; -} - -int AnalyserNode::getFftSize() const { - return fftSize_; -} - -int AnalyserNode::getFrequencyBinCount() const { - return fftSize_ / 2; -} - -float AnalyserNode::getMinDecibels() const { - return minDecibels_; -} - -float AnalyserNode::getMaxDecibels() const { - return maxDecibels_; -} - -float AnalyserNode::getSmoothingTimeConstant() const { - return smoothingTimeConstant_; -} - -AnalyserNode::WindowType AnalyserNode::getWindowType() const { - return windowType_; + minDecibels_(options.minDecibels), + maxDecibels_(options.maxDecibels), + smoothingTimeConstant_(options.smoothingTimeConstant) { + setFFTSize(options.fftSize); + isInitialized_.store(true, std::memory_order_release); } -void AnalyserNode::setFftSize(int fftSize) { - if (fftSize_ == fftSize) { +void AnalyserNode::setFFTSize(int fftSize) { + if (fftSize == fftSize_.load(std::memory_order_acquire)) { return; } - fftSize_ = fftSize; - fft_ = std::make_unique(fftSize_); - complexData_ = std::vector>(fftSize_); - magnitudeArray_ = std::make_unique(fftSize_ / 2); - tempArray_ = std::make_unique(fftSize_); - setWindowData(windowType_, fftSize_); -} - -void AnalyserNode::setMinDecibels(float minDecibels) { - minDecibels_ = minDecibels; -} - -void AnalyserNode::setMaxDecibels(float maxDecibels) { - maxDecibels_ = maxDecibels; -} - -void AnalyserNode::setSmoothingTimeConstant(float smoothingTimeConstant) { - smoothingTimeConstant_ = smoothingTimeConstant; -} - -void AnalyserNode::setWindowType(AnalyserNode::WindowType type) { - setWindowData(type, fftSize_); + fft_ = std::make_unique(fftSize); + complexData_ = std::vector>(fftSize); + magnitudeArray_ = std::make_unique(fftSize / 2); + tempArray_ = std::make_unique(fftSize); + initializeWindowData(fftSize); + fftSize_.store(fftSize, std::memory_order_release); } void AnalyserNode::getFloatFrequencyData(float *data, int length) { @@ -112,39 +64,26 @@ void AnalyserNode::getByteFrequencyData(uint8_t *data, int length) { magnitudeBufferData[i] == 0 ? minDecibels_ : dsp::linearToDecibels(magnitudeBufferData[i]); auto scaledValue = UINT8_MAX * (dbMag - minDecibels_) * rangeScaleFactor; - if (scaledValue < 0) { - scaledValue = 0; - } - if (scaledValue > UINT8_MAX) { - scaledValue = UINT8_MAX; - } - - data[i] = static_cast(scaledValue); + data[i] = static_cast(std::clamp(scaledValue, 0.0f, static_cast(UINT8_MAX))); } } void AnalyserNode::getFloatTimeDomainData(float *data, int length) { - auto size = std::min(fftSize_, length); + auto *frame = analysisBuffer_.getForReader(); + auto size = std::min(frame->fftSize, length); - inputArray_->pop_back(data, size, std::max(0, fftSize_ - size), true); + frame->timeDomain.copyTo(data, 0, 0, size); } void AnalyserNode::getByteTimeDomainData(uint8_t *data, int length) { - auto size = std::min(fftSize_, length); - - inputArray_->pop_back(*tempArray_, size, std::max(0, fftSize_ - size), true); + auto *frame = analysisBuffer_.getForReader(); + auto size = std::min(frame->fftSize, length); - auto values = tempArray_->span(); + auto values = frame->timeDomain.span(); for (int i = 0; i < size; i++) { float scaledValue = 128 * (values[i] + 1); - - if (scaledValue < 0) { - scaledValue = 0; - } - if (scaledValue > UINT8_MAX) { - scaledValue = UINT8_MAX; - } + scaledValue = std::clamp(scaledValue, 0.0f, static_cast(UINT8_MAX)); data[i] = static_cast(scaledValue); } @@ -161,23 +100,36 @@ std::shared_ptr AnalyserNode::processNode( // Copy the down mixed buffer to the input buffer (circular buffer) inputArray_->push_back(*downMixBuffer_->getChannel(0), framesToProcess, true); - shouldDoFFTAnalysis_ = true; + // Snapshot the latest fftSize_ samples into the triple buffer for the JS thread. + auto *frame = analysisBuffer_.getForWriter(); + auto fftSize = fftSize_.load(std::memory_order_acquire); + frame->fftSize = fftSize; + frame->sequenceNumber = ++publishSequence_; + inputArray_->pop_back(frame->timeDomain, fftSize, 0, true); + analysisBuffer_.publish(); return processingBuffer; } void AnalyserNode::doFFTAnalysis() { - if (!shouldDoFFTAnalysis_) { + auto *frame = analysisBuffer_.getForReader(); + + if (frame->sequenceNumber == lastAnalyzedSequence_) { return; } - shouldDoFFTAnalysis_ = false; + auto fftSize = frame->fftSize; + + // relaxed because fftSize_ is only updated on the JS thread. + if (fftSize != fftSize_.load(std::memory_order_relaxed)) { + return; + } - // We want to copy last fftSize_ elements added to the input buffer to apply - // the window. - inputArray_->pop_back(*tempArray_, fftSize_, 0, true); + lastAnalyzedSequence_ = frame->sequenceNumber; - tempArray_->multiply(*windowData_, fftSize_); + // Copy the snapshot from the triple buffer and apply the window. + tempArray_->copy(frame->timeDomain, 0, 0, fftSize); + tempArray_->multiply(*windowData_, fftSize); // do fft analysis - get frequency domain data fft_->doFFT(*tempArray_, complexData_); @@ -185,7 +137,7 @@ void AnalyserNode::doFFTAnalysis() { // Zero out nquist component complexData_[0] = std::complex(complexData_[0].real(), 0); - const float magnitudeScale = 1.0f / static_cast(fftSize_); + const float magnitudeScale = 1.0f / static_cast(fftSize); auto magnitudeBufferData = magnitudeArray_->span(); for (int i = 0; i < magnitudeArray_->getSize(); i++) { @@ -196,23 +148,20 @@ void AnalyserNode::doFFTAnalysis() { } } -void AnalyserNode::setWindowData(AnalyserNode::WindowType type, int size) { - if (windowType_ == type && windowData_ != nullptr && windowData_->getSize() == size) { - return; - } +void AnalyserNode::initializeWindowData(int fftSize) { + windowData_ = std::make_unique(fftSize); + auto data = windowData_->span(); + auto size = windowData_->getSize(); - windowType_ = type; - if (windowData_ == nullptr || windowData_->getSize() != size) { - windowData_ = std::make_shared(size); - } + const auto invSizeMinusOne = 1.0f / static_cast(size - 1); + const auto alpha = 2.0f * std::numbers::pi_v * invSizeMinusOne; - switch (windowType_) { - case WindowType::BLACKMAN: - dsp::Blackman().apply(windowData_->span()); - break; - case WindowType::HANN: - dsp::Hann().apply(windowData_->span()); - break; + for (size_t i = 0; i < size; ++i) { + const auto phase = alpha * i; + // 4*PI*x is just 2 * (2*PI*x) + const auto window = 0.42f - 0.50f * std::cos(phase) + 0.08f * std::cos(2.0f * phase); + data[i] = window; } } + } // namespace audioapi 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 30c965748..c806e820b 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 @@ -1,43 +1,77 @@ #pragma once #include +#include #include +#include +#include +#include #include #include +#include #include #include namespace audioapi { class AudioBuffer; -class AudioArray; class CircularAudioArray; struct AnalyserOptions; class AnalyserNode : public AudioNode { public: - enum class WindowType { BLACKMAN, HANN }; explicit AnalyserNode( const std::shared_ptr &context, const AnalyserOptions &options); - int getFftSize() const; - int getFrequencyBinCount() const; - float getMinDecibels() const; - float getMaxDecibels() const; - float getSmoothingTimeConstant() const; - AnalyserNode::WindowType getWindowType() const; + /// @note JS Thread only + float getMinDecibels() const { + return minDecibels_; + } - void setFftSize(int fftSize); - void setMinDecibels(float minDecibels); - void setMaxDecibels(float maxDecibels); - void setSmoothingTimeConstant(float smoothingTimeConstant); - void setWindowType(AnalyserNode::WindowType); + /// @note JS Thread only + float getMaxDecibels() const { + return maxDecibels_; + } + /// @note JS Thread only + float getSmoothingTimeConstant() const { + return smoothingTimeConstant_; + } + + int getFFTSize() const { + return fftSize_.load(std::memory_order_acquire); + } + + /// @note JS Thread only + void setMinDecibels(float minDecibels) { + minDecibels_ = minDecibels; + } + + /// @note JS Thread only + void setMaxDecibels(float maxDecibels) { + maxDecibels_ = maxDecibels; + } + + /// @note JS Thread only + void setSmoothingTimeConstant(float smoothingTimeConstant) { + smoothingTimeConstant_ = smoothingTimeConstant; + } + + /// @note JS Thread only + void setFFTSize(int fftSize); + + /// @note JS Thread only void getFloatFrequencyData(float *data, int length); + + /// @note JS Thread only void getByteFrequencyData(uint8_t *data, int length); + + /// @note JS Thread only void getFloatTimeDomainData(float *data, int length); + + /// @note JS Thread only void getByteTimeDomainData(uint8_t *data, int length); protected: @@ -46,26 +80,42 @@ class AnalyserNode : public AudioNode { int framesToProcess) override; private: - int fftSize_; - float minDecibels_; - float maxDecibels_; - float smoothingTimeConstant_; - - WindowType windowType_; - std::shared_ptr windowData_; + std::atomic fftSize_; + // Audio Thread data structures std::unique_ptr inputArray_; std::unique_ptr downMixBuffer_; - std::unique_ptr tempArray_; + // JS Thread parameters + float minDecibels_; + float maxDecibels_; + float smoothingTimeConstant_; + + // JS Thread data structures std::unique_ptr fft_; + std::unique_ptr tempArray_; + std::unique_ptr windowData_; std::vector> complexData_; std::unique_ptr magnitudeArray_; - bool shouldDoFFTAnalysis_{true}; + + struct AnalysisFrame { + AudioArray timeDomain; + size_t sequenceNumber = 0; + int fftSize = 0; + + explicit AnalysisFrame(size_t size) : timeDomain(size) {} + + AnalysisFrame(const AnalysisFrame &) = delete; + AnalysisFrame &operator=(const AnalysisFrame &) = delete; + }; + + TripleBuffer analysisBuffer_{MAX_FFT_SIZE}; + size_t publishSequence_ = 0; // audio thread only + size_t lastAnalyzedSequence_ = 0; // JS thread only void doFFTAnalysis(); - void setWindowData(WindowType type, int size); + void initializeWindowData(int fftSize); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp index e11763597..53628b4c7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp @@ -10,7 +10,7 @@ namespace audioapi { AudioDestinationNode::AudioDestinationNode(const std::shared_ptr &context) : AudioNode(context, AudioDestinationOptions()), currentSampleFrame_(0) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::size_t AudioDestinationNode::getCurrentSampleFrame() const { @@ -18,21 +18,18 @@ std::size_t AudioDestinationNode::getCurrentSampleFrame() const { } double AudioDestinationNode::getCurrentTime() const { - if (std::shared_ptr context = context_.lock()) { - return static_cast(getCurrentSampleFrame()) / context->getSampleRate(); - } else { - return 0.0; - } + return static_cast(getCurrentSampleFrame()) / getContextSampleRate(); } void AudioDestinationNode::renderAudio( const std::shared_ptr &destinationBuffer, int numFrames) { - if (numFrames < 0 || !destinationBuffer || !isInitialized_) { + if (numFrames < 0 || !destinationBuffer || !isInitialized_.load(std::memory_order_acquire)) { return; } if (std::shared_ptr context = context_.lock()) { + context->processAudioEvents(); context->getGraphManager()->preProcessGraph(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.h index 59a7111da..42d54b689 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.h @@ -16,9 +16,13 @@ class AudioDestinationNode : public AudioNode { public: explicit AudioDestinationNode(const std::shared_ptr &context); + /// @note Thread safe std::size_t getCurrentSampleFrame() const; + + /// @note Thread safe double getCurrentTime() const; + /// @note Audio Thread only void renderAudio(const std::shared_ptr &audioData, int numFrames); protected: 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 32c46da0f..5c77ffe48 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 @@ -28,11 +28,11 @@ #include #include +#include #include #include #include #include -#include "audioapi/core/utils/Constants.h" // https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html - math // formulas for filters @@ -43,16 +43,8 @@ BiquadFilterNode::BiquadFilterNode( const std::shared_ptr &context, const BiquadFilterOptions &options) : AudioNode(context, options), - x1_(MAX_CHANNEL_COUNT), - x2_(MAX_CHANNEL_COUNT), - y1_(MAX_CHANNEL_COUNT), - y2_(MAX_CHANNEL_COUNT), frequencyParam_( - std::make_shared( - options.frequency, - 0.0f, - context->getNyquistFrequency(), - context)), + std::make_shared(options.frequency, 0.0f, getNyquistFrequency(), context)), detuneParam_( std::make_shared( options.detune, @@ -71,12 +63,12 @@ BiquadFilterNode::BiquadFilterNode( MOST_NEGATIVE_SINGLE_FLOAT, BIQUAD_GAIN_DB_FACTOR * LOG10_MOST_POSITIVE_SINGLE_FLOAT, context)), - type_(options.type) { - isInitialized_ = true; -} - -BiquadFilterType BiquadFilterNode::getType() { - return type_; + type_(options.type), + x1_(MAX_CHANNEL_COUNT), + x2_(MAX_CHANNEL_COUNT), + y1_(MAX_CHANNEL_COUNT), + y2_(MAX_CHANNEL_COUNT) { + isInitialized_.store(true, std::memory_order_release); } void BiquadFilterNode::setType(BiquadFilterType type) { @@ -122,22 +114,16 @@ void BiquadFilterNode::getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - const size_t length) { -#if !RN_AUDIO_API_TEST - applyFilter(); -#endif - - // Use double precision for later calculations - auto b0 = static_cast(b0_); - auto b1 = static_cast(b1_); - auto b2 = static_cast(b2_); - auto a1 = static_cast(a1_); - auto a2 = static_cast(a2_); - - std::shared_ptr context = context_.lock(); - if (!context) - return; - float nyquist = context->getNyquistFrequency(); + const size_t length, + BiquadFilterType type) { + auto frequency = frequencyParam_->getValue(); + auto Q = QParam_->getValue(); + auto gain = gainParam_->getValue(); + auto detune = detuneParam_->getValue(); + + auto coeffs = applyFilter(frequency, Q, gain, detune, type); + + float nyquist = getNyquistFrequency(); for (size_t i = 0; i < length; i++) { // Convert from frequency in Hz to normalized frequency [0, 1] @@ -152,37 +138,23 @@ void BiquadFilterNode::getFrequencyResponse( double omega = -PI * normalizedFreq; auto z = std::complex(std::cos(omega), std::sin(omega)); - auto response = (b0 + (b1 + b2 * z) * z) / (std::complex(1, 0) + (a1 + a2 * z) * z); + auto response = (coeffs.b0 + (coeffs.b1 + coeffs.b2 * z) * z) / + (std::complex(1, 0) + (coeffs.a1 + coeffs.a2 * z) * z); magResponseOutput[i] = static_cast(std::abs(response)); phaseResponseOutput[i] = static_cast(atan2(imag(response), real(response))); } } -void BiquadFilterNode::setNormalizedCoefficients( - float b0, - float b1, - float b2, - float a0, - float a1, - float a2) { - auto a0Inverted = 1.0f / a0; - b0_ = b0 * a0Inverted; - b1_ = b1 * a0Inverted; - b2_ = b2 * a0Inverted; - a1_ = a1 * a0Inverted; - a2_ = a2 * a0Inverted; -} - -void BiquadFilterNode::setLowpassCoefficients(float frequency, float Q) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getLowpassCoefficients( + float frequency, + float Q) { // Limit frequency to [0, 1] range if (frequency >= 1.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (frequency <= 0.0f) { - setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float g = std::pow(10.0f, 0.05f * Q); @@ -192,17 +164,17 @@ void BiquadFilterNode::setLowpassCoefficients(float frequency, float Q) { float cosW = std::cos(theta); float beta = (1 - cosW) / 2; - setNormalizedCoefficients(beta, 2 * beta, beta, 1 + alpha, -2 * cosW, 1 - alpha); + return getNormalizedCoefficients(beta, 2 * beta, beta, 1 + alpha, -2 * cosW, 1 - alpha); } -void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getHighpassCoefficients( + float frequency, + float Q) { if (frequency >= 1.0f) { - setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (frequency <= 0.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float g = std::pow(10.0f, 0.05f * Q); @@ -212,40 +184,40 @@ void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) { float cosW = std::cos(theta); float beta = (1 + cosW) / 2; - setNormalizedCoefficients(beta, -2 * beta, beta, 1 + alpha, -2 * cosW, 1 - alpha); + return getNormalizedCoefficients(beta, -2 * beta, beta, 1 + alpha, -2 * cosW, 1 - alpha); } -void BiquadFilterNode::setBandpassCoefficients(float frequency, float Q) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getBandpassCoefficients( + float frequency, + float Q) { // Limit frequency to [0, 1] range if (frequency <= 0.0f || frequency >= 1.0f) { - setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } // Limit Q to positive values if (Q <= 0.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; float alpha = std::sin(w0) / (2 * Q); float cosW = std::cos(w0); - setNormalizedCoefficients(alpha, 0.0f, -alpha, 1.0f + alpha, -2 * cosW, 1.0f - alpha); + return getNormalizedCoefficients(alpha, 0.0f, -alpha, 1.0f + alpha, -2 * cosW, 1.0f - alpha); } -void BiquadFilterNode::setLowshelfCoefficients(float frequency, float gain) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getLowshelfCoefficients( + float frequency, + float gain) { float A = std::pow(10.0f, gain / 40.0f); if (frequency >= 1.0f) { - setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (frequency <= 0.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; @@ -253,7 +225,7 @@ void BiquadFilterNode::setLowshelfCoefficients(float frequency, float gain) { float cosW = std::cos(w0); float gamma = 2.0f * std::sqrt(A) * alpha; - setNormalizedCoefficients( + return getNormalizedCoefficients( A * (A + 1 - (A - 1) * cosW + gamma), 2.0f * A * (A - 1 - (A + 1) * cosW), A * (A + 1 - (A - 1) * cosW - gamma), @@ -262,17 +234,17 @@ void BiquadFilterNode::setLowshelfCoefficients(float frequency, float gain) { A + 1 + (A - 1) * cosW - gamma); } -void BiquadFilterNode::setHighshelfCoefficients(float frequency, float gain) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getHighshelfCoefficients( + float frequency, + float gain) { float A = std::pow(10.0f, gain / 40.0f); if (frequency >= 1.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (frequency <= 0.0f) { - setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; @@ -282,7 +254,7 @@ void BiquadFilterNode::setHighshelfCoefficients(float frequency, float gain) { float cosW = std::cos(w0); float gamma = 2.0f * std::sqrt(A) * alpha; - setNormalizedCoefficients( + return getNormalizedCoefficients( A * (A + 1 + (A - 1) * cosW + gamma), -2.0f * A * (A - 1 + (A + 1) * cosW), A * (A + 1 + (A - 1) * cosW - gamma), @@ -291,159 +263,172 @@ void BiquadFilterNode::setHighshelfCoefficients(float frequency, float gain) { A + 1 - (A - 1) * cosW - gamma); } -void BiquadFilterNode::setPeakingCoefficients(float frequency, float Q, float gain) { +BiquadFilterNode::FilterCoefficients +BiquadFilterNode::getPeakingCoefficients(float frequency, float Q, float gain) { float A = std::pow(10.0f, gain / 40.0f); if (frequency <= 0.0f || frequency >= 1.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (Q <= 0.0f) { - setNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(A * A, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; float alpha = std::sin(w0) / (2 * Q); float cosW = std::cos(w0); - setNormalizedCoefficients( + return getNormalizedCoefficients( 1 + alpha * A, -2 * cosW, 1 - alpha * A, 1 + alpha / A, -2 * cosW, 1 - alpha / A); } -void BiquadFilterNode::setNotchCoefficients(float frequency, float Q) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getNotchCoefficients( + float frequency, + float Q) { if (frequency <= 0.0f || frequency >= 1.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (Q <= 0.0f) { - setNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; float alpha = std::sin(w0) / (2 * Q); float cosW = std::cos(w0); - setNormalizedCoefficients(1.0f, -2 * cosW, 1.0f, 1 + alpha, -2 * cosW, 1 - alpha); + return getNormalizedCoefficients(1.0f, -2 * cosW, 1.0f, 1 + alpha, -2 * cosW, 1 - alpha); } -void BiquadFilterNode::setAllpassCoefficients(float frequency, float Q) { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getAllpassCoefficients( + float frequency, + float Q) { if (frequency <= 0.0f || frequency >= 1.0f) { - setNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } if (Q <= 0.0f) { - setNormalizedCoefficients(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); - return; + return getNormalizedCoefficients(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); } float w0 = PI * frequency; float alpha = std::sin(w0) / (2 * Q); float cosW = std::cos(w0); - setNormalizedCoefficients(1 - alpha, -2 * cosW, 1 + alpha, 1 + alpha, -2 * cosW, 1 - alpha); + return getNormalizedCoefficients( + 1 - alpha, -2 * cosW, 1 + alpha, 1 + alpha, -2 * cosW, 1 - alpha); } -void BiquadFilterNode::applyFilter() { +BiquadFilterNode::FilterCoefficients BiquadFilterNode::getNormalizedCoefficients( + float b0, + float b1, + float b2, + float a0, + float a1, + float a2) { + auto a0Inverted = 1.0f / a0; + return {b0 * a0Inverted, b1 * a0Inverted, b2 * a0Inverted, a1 * a0Inverted, a2 * a0Inverted}; +} + +BiquadFilterNode::FilterCoefficients BiquadFilterNode::applyFilter( + float frequency, + float Q, + float gain, + float detune, + BiquadFilterType type) { // NyquistFrequency is half of the sample rate. // Normalized frequency is therefore: // frequency / (sampleRate / 2) = (2 * frequency) / sampleRate - float normalizedFrequency; - double currentTime; - if (std::shared_ptr context = context_.lock()) { - currentTime = context->getCurrentTime(); - float frequency = frequencyParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); - normalizedFrequency = frequency / context->getNyquistFrequency(); - } else { - return; - } - float detune = detuneParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); - auto Q = QParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); - auto gain = gainParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); + float normalizedFrequency = frequency / getNyquistFrequency(); if (detune != 0.0f) { normalizedFrequency *= std::pow(2.0f, detune / 1200.0f); } - switch (type_) { + FilterCoefficients coeffs = {1.0, 0.0, 0.0, 0.0, 0.0}; + + switch (type) { case BiquadFilterType::LOWPASS: - setLowpassCoefficients(normalizedFrequency, Q); + coeffs = getLowpassCoefficients(normalizedFrequency, Q); break; case BiquadFilterType::HIGHPASS: - setHighpassCoefficients(normalizedFrequency, Q); + coeffs = getHighpassCoefficients(normalizedFrequency, Q); break; case BiquadFilterType::BANDPASS: - setBandpassCoefficients(normalizedFrequency, Q); + coeffs = getBandpassCoefficients(normalizedFrequency, Q); break; case BiquadFilterType::LOWSHELF: - setLowshelfCoefficients(normalizedFrequency, gain); + coeffs = getLowshelfCoefficients(normalizedFrequency, gain); break; case BiquadFilterType::HIGHSHELF: - setHighshelfCoefficients(normalizedFrequency, gain); + coeffs = getHighshelfCoefficients(normalizedFrequency, gain); break; case BiquadFilterType::PEAKING: - setPeakingCoefficients(normalizedFrequency, Q, gain); + coeffs = getPeakingCoefficients(normalizedFrequency, Q, gain); break; case BiquadFilterType::NOTCH: - setNotchCoefficients(normalizedFrequency, Q); + coeffs = getNotchCoefficients(normalizedFrequency, Q); break; case BiquadFilterType::ALLPASS: - setAllpassCoefficients(normalizedFrequency, Q); + coeffs = getAllpassCoefficients(normalizedFrequency, Q); break; default: break; } + + return coeffs; } std::shared_ptr BiquadFilterNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - int numChannels = processingBuffer->getNumberOfChannels(); + if (std::shared_ptr context = context_.lock()) { + auto currentTime = context->getCurrentTime(); + float frequency = frequencyParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); + float detune = detuneParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); + auto Q = QParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); + auto gain = gainParam_->processKRateParam(RENDER_QUANTUM_SIZE, currentTime); - applyFilter(); + auto coeffs = applyFilter(frequency, Q, gain, detune, type_); - // local copies for micro-optimization - float b0 = b0_; - float b1 = b1_; - float b2 = b2_; - float a1 = a1_; - float a2 = a2_; + float x1, x2, y1, y2; - float x1, x2, y1, y2; + auto numChannels = processingBuffer->getNumberOfChannels(); - for (int c = 0; c < numChannels; ++c) { - auto channel = processingBuffer->getChannel(c)->subSpan(framesToProcess); + for (size_t c = 0; c < numChannels; ++c) { + auto channel = processingBuffer->getChannel(c)->subSpan(framesToProcess); - x1 = x1_[c]; - x2 = x2_[c]; - y1 = y1_[c]; - y2 = y2_[c]; + x1 = x1_[c]; + x2 = x2_[c]; + y1 = y1_[c]; + y2 = y2_[c]; - for (float &sample : channel) { - auto input = sample; - auto output = b0 * input + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + for (float &sample : channel) { + auto input = sample; + auto output = + coeffs.b0 * input + coeffs.b1 * x1 + coeffs.b2 * x2 - coeffs.a1 * y1 - coeffs.a2 * y2; - // Avoid denormalized numbers - if (std::abs(output) < 1e-15f) { - output = 0.0f; - } + // Avoid denormalized numbers + if (std::abs(output) < 1e-15f) { + output = 0.0f; + } - sample = output; + sample = static_cast(output); - x2 = x1; - x1 = input; - y2 = y1; - y1 = output; - } + x2 = x1; + x1 = input; + y2 = y1; + y1 = static_cast(output); + } - x1_[c] = x1; - x2_[c] = x2; - y1_[c] = y1; - y2_[c] = y2; + x1_[c] = x1; + x2_[c] = x2; + y1_[c] = y1; + y2_[c] = y2; + } + } else { + processingBuffer->zero(); } return processingBuffer; 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 da15dcaad..e5dcc830a 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 @@ -53,17 +53,19 @@ class BiquadFilterNode : public AudioNode { const std::shared_ptr &context, const BiquadFilterOptions &options); - [[nodiscard]] BiquadFilterType getType(); void setType(BiquadFilterType); [[nodiscard]] std::shared_ptr getFrequencyParam() const; [[nodiscard]] std::shared_ptr getDetuneParam() const; [[nodiscard]] std::shared_ptr getQParam() const; [[nodiscard]] std::shared_ptr getGainParam() const; + + /// @note JS Thread only void getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - size_t length); + size_t length, + BiquadFilterType type); protected: std::shared_ptr processNode( @@ -71,10 +73,10 @@ class BiquadFilterNode : public AudioNode { int framesToProcess) override; private: - std::shared_ptr frequencyParam_; - std::shared_ptr detuneParam_; - std::shared_ptr QParam_; - std::shared_ptr gainParam_; + const std::shared_ptr frequencyParam_; + const std::shared_ptr detuneParam_; + const std::shared_ptr QParam_; + const std::shared_ptr gainParam_; BiquadFilterType type_; // delayed samples, one per channel @@ -83,23 +85,22 @@ class BiquadFilterNode : public AudioNode { AudioArray y1_; AudioArray y2_; - // coefficients - float b0_ = 1.0; - float b1_ = 0; - float b2_ = 0; - float a1_ = 0; - float a2_ = 0; + struct alignas(64) FilterCoefficients { + double b0, b1, b2, a1, a2; + }; - void setNormalizedCoefficients(float b0, float b1, float b2, float a0, float a1, float a2); - void setLowpassCoefficients(float frequency, float Q); - void setHighpassCoefficients(float frequency, float Q); - void setBandpassCoefficients(float frequency, float Q); - void setLowshelfCoefficients(float frequency, float gain); - void setHighshelfCoefficients(float frequency, float gain); - void setPeakingCoefficients(float frequency, float Q, float gain); - void setNotchCoefficients(float frequency, float Q); - void setAllpassCoefficients(float frequency, float Q); - void applyFilter(); + static FilterCoefficients getLowpassCoefficients(float frequency, float Q); + static FilterCoefficients getHighpassCoefficients(float frequency, float Q); + static FilterCoefficients getBandpassCoefficients(float frequency, float Q); + static FilterCoefficients getLowshelfCoefficients(float frequency, float gain); + static FilterCoefficients getHighshelfCoefficients(float frequency, float gain); + static FilterCoefficients getPeakingCoefficients(float frequency, float Q, float gain); + static FilterCoefficients getNotchCoefficients(float frequency, float Q); + static FilterCoefficients getAllpassCoefficients(float frequency, float Q); + static FilterCoefficients + getNormalizedCoefficients(float b0, float b1, float b2, float a0, float a1, float a2); + FilterCoefficients + applyFilter(float frequency, float Q, float gain, float detune, BiquadFilterType type); }; } // namespace audioapi 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 00788a888..5415eb6f3 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,12 +1,12 @@ #include #include +#include #include -#include -#include #include #include #include #include +#include #include namespace audioapi { @@ -17,59 +17,75 @@ ConvolverNode::ConvolverNode( gainCalibrationSampleRate_(context->getSampleRate()), remainingSegments_(0), internalBufferIndex_(0), - normalize_(!options.disableNormalization), signalledToStop_(false), scaleFactor_(1.0f), intermediateBuffer_(nullptr), buffer_(nullptr), internalBuffer_(nullptr) { - setBuffer(options.buffer); - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } -bool ConvolverNode::getNormalize_() const { - return normalize_; -} +void ConvolverNode::setBuffer( + const std::shared_ptr &buffer, + std::vector convolvers, + const std::shared_ptr &threadPool, + const std::shared_ptr &internalBuffer, + const std::shared_ptr &intermediateBuffer, + float scaleFactor) { + std::shared_ptr context = context_.lock(); + if (context == nullptr) { + return; + } -const std::shared_ptr &ConvolverNode::getBuffer() const { - return buffer_; -} + auto graphManager = context->getGraphManager(); -void ConvolverNode::setNormalize(bool normalize) { - if (normalize_ != normalize) { - normalize_ = normalize; - if (normalize_ && buffer_) - calculateNormalizationScale(); + if (buffer_) { + graphManager->addAudioBufferForDestruction(std::move(buffer_)); } - if (!normalize_) { - scaleFactor_ = 1.0f; + if (internalBuffer_) { + graphManager->addAudioBufferForDestruction(std::move(internalBuffer_)); } + if (intermediateBuffer_) { + graphManager->addAudioBufferForDestruction(std::move(intermediateBuffer_)); + } + + // TODO move convolvers and thread destruction to graph manager as well + + buffer_ = buffer; + convolvers_ = std::move(convolvers); + threadPool_ = threadPool; + internalBuffer_ = internalBuffer; + intermediateBuffer_ = intermediateBuffer; + scaleFactor_ = scaleFactor; + internalBufferIndex_ = 0; } -void ConvolverNode::setBuffer(const std::shared_ptr &buffer) { - if (buffer_ != buffer && buffer != nullptr) { - buffer_ = buffer; - if (normalize_) - calculateNormalizationScale(); - threadPool_ = std::make_shared(4); - convolvers_.clear(); - for (size_t i = 0; i < buffer->getNumberOfChannels(); ++i) { - convolvers_.emplace_back(); - AudioArray channelData(*buffer->getChannel(i)); - convolvers_.back().init(RENDER_QUANTUM_SIZE, channelData, buffer->getSize()); - } - if (buffer->getNumberOfChannels() == 1) { - // add one more convolver, because right now input is always stereo - convolvers_.emplace_back(); - AudioArray channelData(*buffer->getChannel(0)); - convolvers_.back().init(RENDER_QUANTUM_SIZE, channelData, buffer->getSize()); +float ConvolverNode::calculateNormalizationScale(const std::shared_ptr &buffer) { + int numberOfChannels = buffer->getNumberOfChannels(); + auto length = buffer->getSize(); + + float power = 0; + + for (size_t channel = 0; channel < numberOfChannels; ++channel) { + float channelPower = 0; + auto channelData = buffer->getChannel(channel)->span(); + for (int i = 0; i < length; ++i) { + float sample = channelData[i]; + channelPower += sample * sample; } - internalBuffer_ = std::make_shared( - RENDER_QUANTUM_SIZE * 2, channelCount_, buffer->getSampleRate()); - intermediateBuffer_ = std::make_shared( - RENDER_QUANTUM_SIZE, convolvers_.size(), buffer->getSampleRate()); - internalBufferIndex_ = 0; + power += channelPower; + } + + power = std::sqrt(power / (numberOfChannels * length)); + if (power < MIN_IR_POWER) { + power = MIN_IR_POWER; } + + auto scaleFactor = 1 / power; + scaleFactor *= std::pow(10, GAIN_CALIBRATION * 0.05f); + scaleFactor *= gainCalibrationSampleRate_ / buffer->getSampleRate(); + + return scaleFactor; } void ConvolverNode::onInputDisabled() { @@ -130,31 +146,6 @@ std::shared_ptr ConvolverNode::processNode( return audioBuffer_; } -void ConvolverNode::calculateNormalizationScale() { - int numberOfChannels = buffer_->getNumberOfChannels(); - auto length = buffer_->getSize(); - - float power = 0; - - for (size_t channel = 0; channel < numberOfChannels; ++channel) { - float channelPower = 0; - auto channelData = buffer_->getChannel(channel)->span(); - for (int i = 0; i < length; ++i) { - float sample = channelData[i]; - channelPower += sample * sample; - } - power += channelPower; - } - - power = std::sqrt(power / (numberOfChannels * length)); - if (power < MIN_IR_POWER) { - power = MIN_IR_POWER; - } - scaleFactor_ = 1 / power; - scaleFactor_ *= std::pow(10, GAIN_CALIBRATION * 0.05f); - scaleFactor_ *= gainCalibrationSampleRate_ / buffer_->getSampleRate(); -} - void ConvolverNode::performConvolution(const std::shared_ptr &processingBuffer) { if (processingBuffer->getNumberOfChannels() == 1) { for (int i = 0; i < convolvers_.size(); ++i) { 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 0f3fcfdbb..4041d3c5b 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 @@ -25,10 +25,16 @@ class ConvolverNode : public AudioNode { const std::shared_ptr &context, const ConvolverOptions &options); - [[nodiscard]] bool getNormalize_() const; - [[nodiscard]] const std::shared_ptr &getBuffer() const; - void setNormalize(bool normalize); - void setBuffer(const std::shared_ptr &buffer); + /// @note Audio Thread only + void setBuffer( + const std::shared_ptr &buffer, + std::vector convolvers, + const std::shared_ptr &threadPool, + const std::shared_ptr &internalBuffer, + const std::shared_ptr &intermediateBuffer, + float scaleFactor); + + float calculateNormalizationScale(const std::shared_ptr &buffer); protected: std::shared_ptr processNode( @@ -41,10 +47,9 @@ class ConvolverNode : public AudioNode { int framesToProcess, bool checkIsAlreadyProcessed) override; void onInputDisabled() override; - float gainCalibrationSampleRate_; + const float gainCalibrationSampleRate_; size_t remainingSegments_; size_t internalBufferIndex_; - bool normalize_; bool signalledToStop_; float scaleFactor_; std::shared_ptr intermediateBuffer_; @@ -57,7 +62,6 @@ class ConvolverNode : public AudioNode { std::vector convolvers_; std::shared_ptr threadPool_; - void calculateNormalizationScale(); void performConvolution(const std::shared_ptr &processingBuffer); }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp index 9fcb83753..2350315be 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp @@ -19,7 +19,7 @@ DelayNode::DelayNode(const std::shared_ptr &context, const Del 1), // +1 to enable delayTime equal to maxDelayTime channelCount_, context->getSampleRate())) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr DelayNode::getDelayTimeParam() const { @@ -30,11 +30,7 @@ void DelayNode::onInputDisabled() { numberOfEnabledInputNodes_ -= 1; if (isEnabled() && numberOfEnabledInputNodes_ == 0) { signalledToStop_ = true; - if (std::shared_ptr context = context_.lock()) { - remainingFrames_ = delayTimeParam_->getValue() * context->getSampleRate(); - } else { - remainingFrames_ = 0; - } + remainingFrames_ = delayTimeParam_->getValue() * getContextSampleRate(); } } @@ -98,8 +94,11 @@ std::shared_ptr DelayNode::processNode( // normal processing std::shared_ptr context = context_.lock(); - if (context == nullptr) + if (context == nullptr) { + processingBuffer->zero(); return processingBuffer; + } + auto delayTime = delayTimeParam_->processKRateParam(framesToProcess, context->getCurrentTime()); size_t writeIndex = static_cast(readIndex_ + delayTime * context->getSampleRate()) % delayBuffer_->getSize(); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h index 5c0e96545..d380952c1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.h @@ -29,7 +29,7 @@ class DelayNode : public AudioNode { int framesToProcess, size_t &operationStartingIndex, BufferAction action); - std::shared_ptr delayTimeParam_; + const std::shared_ptr delayTimeParam_; std::shared_ptr delayBuffer_; size_t readIndex_ = 0; bool signalledToStop_ = false; 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 a3ee5ee0f..e2afde588 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 @@ -16,7 +16,7 @@ GainNode::GainNode(const std::shared_ptr &context, const GainO MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context)) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr GainNode::getGainParam() const { 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 86b18cc77..192a69979 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 @@ -22,7 +22,7 @@ class GainNode : public AudioNode { int framesToProcess) override; private: - std::shared_ptr gainParam_; + const std::shared_ptr gainParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp index 52927dae4..3592fb022 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp @@ -39,26 +39,12 @@ IIRFilterNode::IIRFilterNode( const std::shared_ptr &context, const IIRFilterOptions &options) : AudioNode(context, options), - feedforward_(options.feedforward.data(), options.feedforward.size()), - feedback_(options.feedback.data(), options.feedback.size()), - bufferIndices_(bufferLength), + feedforward_(createNormalizedArray(options.feedforward, options.feedback[0])), + feedback_(createNormalizedArray(options.feedback, options.feedback[0])), xBuffers_(bufferLength, MAX_CHANNEL_COUNT, context->getSampleRate()), - yBuffers_(bufferLength, MAX_CHANNEL_COUNT, context->getSampleRate()) { - - size_t feedforwardLength = feedforward_.getSize(); - size_t feedbackLength = feedback_.getSize(); - - if (feedback_[0] != 1) { - float scale = feedback_[0]; - for (unsigned k = 1; k < feedbackLength; ++k) - feedback_[k] /= scale; - - for (unsigned k = 0; k < feedforwardLength; ++k) - feedforward_[k] /= scale; - - feedback_[0] = 1.0f; - } - isInitialized_ = true; + yBuffers_(bufferLength, MAX_CHANNEL_COUNT, context->getSampleRate()), + bufferIndices_(bufferLength) { + isInitialized_.store(true, std::memory_order_release); } // Compute Z-transform of the filter @@ -82,11 +68,8 @@ void IIRFilterNode::getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - size_t length) { - std::shared_ptr context = context_.lock(); - if (context == nullptr) - return; - float nyquist = context->getNyquistFrequency(); + size_t length) const { + float nyquist = getNyquistFrequency(); for (size_t k = 0; k < length; ++k) { float normalizedFreq = frequencyArray[k] / nyquist; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h index ea4178642..a5f6416cf 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h @@ -27,10 +27,11 @@ #include #include +#include +#include +#include #include -#include "audioapi/utils/AudioArray.h" -#include "audioapi/utils/AudioBuffer.h" namespace audioapi { @@ -43,11 +44,12 @@ class IIRFilterNode : public AudioNode { const std::shared_ptr &context, const IIRFilterOptions &options); + /// @note Audio Thread only void getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - size_t length); + size_t length) const; protected: std::shared_ptr processNode( @@ -57,8 +59,8 @@ class IIRFilterNode : public AudioNode { private: static constexpr size_t bufferLength = 32; - AudioArray feedforward_; - AudioArray feedback_; + const AudioArray feedforward_; + const AudioArray feedback_; AudioBuffer xBuffers_; AudioBuffer yBuffers_; @@ -72,5 +74,16 @@ class IIRFilterNode : public AudioNode { result = result * z + std::complex(coefficients[k]); return result; } + + static AudioArray createNormalizedArray( + const std::vector &inputVector, + float scaleFactor) { + AudioArray result(inputVector.data(), inputVector.size()); + if (scaleFactor != 1.0f && scaleFactor != 0.0f && result.getSize() > 0) { + result.scale(1.0f / scaleFactor); + } + + return result; + } }; } // namespace audioapi 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 acaaf4a40..c2e837518 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 @@ -15,7 +15,7 @@ StereoPannerNode::StereoPannerNode( const StereoPannerOptions &options) : AudioNode(context, options), panParam_(std::make_shared(options.pan, -1.0f, 1.0f, context)) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr StereoPannerNode::getPanParam() const { 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 2a5c862f8..f78134619 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 @@ -25,7 +25,7 @@ class StereoPannerNode : public AudioNode { int framesToProcess) override; private: - std::shared_ptr panParam_; + const std::shared_ptr panParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp index 3067cc34c..c4e33fd62 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -21,29 +22,18 @@ WaveShaperNode::WaveShaperNode( waveShapers_.emplace_back(std::make_unique(nullptr)); } setCurve(options.curve); - isInitialized_ = true; -} - -OverSampleType WaveShaperNode::getOversample() const { - return oversample_.load(std::memory_order_acquire); + isInitialized_.store(true, std::memory_order_release); } void WaveShaperNode::setOversample(OverSampleType type) { - std::scoped_lock lock(mutex_); - oversample_.store(type, std::memory_order_release); + oversample_ = type; for (int i = 0; i < waveShapers_.size(); i++) { waveShapers_[i]->setOversample(type); } } -std::shared_ptr WaveShaperNode::getCurve() const { - std::scoped_lock lock(mutex_); - return curve_; -} - -void WaveShaperNode::setCurve(const std::shared_ptr &curve) { - std::scoped_lock lock(mutex_); +void WaveShaperNode::setCurve(const std::shared_ptr &curve) { curve_ = curve; for (int i = 0; i < waveShapers_.size(); i++) { @@ -54,16 +44,6 @@ void WaveShaperNode::setCurve(const std::shared_ptr &curve) { std::shared_ptr WaveShaperNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (!isInitialized_) { - return processingBuffer; - } - - std::unique_lock lock(mutex_, std::try_to_lock); - - if (!lock.owns_lock()) { - return processingBuffer; - } - if (curve_ == nullptr) { return processingBuffer; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h index 18a3f96a2..66d949905 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h @@ -15,7 +15,7 @@ namespace audioapi { class AudioBuffer; -class AudioArrayBuffer; +class AudioArray; struct WaveShaperOptions; class WaveShaperNode : public AudioNode { @@ -24,11 +24,11 @@ class WaveShaperNode : public AudioNode { const std::shared_ptr &context, const WaveShaperOptions &options); - [[nodiscard]] OverSampleType getOversample() const; - [[nodiscard]] std::shared_ptr getCurve() const; - + /// @note Audio Thread only void setOversample(OverSampleType); - void setCurve(const std::shared_ptr &curve); + + /// @note Audio Thread only + void setCurve(const std::shared_ptr &curve); protected: std::shared_ptr processNode( @@ -36,9 +36,8 @@ class WaveShaperNode : public AudioNode { int framesToProcess) override; private: - std::atomic oversample_; - std::shared_ptr curve_; - mutable std::mutex mutex_; + OverSampleType oversample_; + std::shared_ptr curve_; std::vector> waveShapers_{}; }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp index 14420859f..d18f15dbd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp @@ -17,7 +17,7 @@ WorkletNode::WorkletNode( bufferLength_(bufferLength), inputChannelCount_(inputChannelCount), curBuffIndex_(0) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletNode::processNode( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp index f1bb4c642..683a34df4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp @@ -18,7 +18,8 @@ WorkletProcessingNode::WorkletProcessingNode( inputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); outputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); } - isInitialized_ = true; + + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletProcessingNode::processNode( 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 677e2353c..eedb0ea47 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 @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -16,18 +17,29 @@ AudioBufferBaseSourceNode::AudioBufferBaseSourceNode( const BaseAudioBufferSourceOptions &options) : AudioScheduledSourceNode(context, options), pitchCorrection_(options.pitchCorrection), - vReadIndex_(0.0) { - onPositionChangedInterval_ = static_cast(context->getSampleRate() * 0.1); - - detuneParam_ = std::make_shared( - options.detune, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); - playbackRateParam_ = std::make_shared( - options.playbackRate, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); - - playbackRateBuffer_ = std::make_shared( - RENDER_QUANTUM_SIZE * 3, channelCount_, context->getSampleRate()); - - stretch_ = std::make_shared>(); + playbackRateBuffer_( + std::make_shared( + RENDER_QUANTUM_SIZE * 3, + channelCount_, + context->getSampleRate())), + detuneParam_( + std::make_shared( + options.detune, + MOST_NEGATIVE_SINGLE_FLOAT, + MOST_POSITIVE_SINGLE_FLOAT, + context)), + playbackRateParam_( + std::make_shared( + options.playbackRate, + MOST_NEGATIVE_SINGLE_FLOAT, + MOST_POSITIVE_SINGLE_FLOAT, + context)), + vReadIndex_(0.0), + onPositionChangedInterval_(static_cast(context->getSampleRate() * 0.1)) {} + +void AudioBufferBaseSourceNode::initStretch( + const std::shared_ptr> &stretch) { + stretch_ = stretch; } std::shared_ptr AudioBufferBaseSourceNode::getDetuneParam() const { @@ -39,58 +51,28 @@ std::shared_ptr AudioBufferBaseSourceNode::getPlaybackRateParam() co } void AudioBufferBaseSourceNode::setOnPositionChangedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onPositionChangedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); - - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::POSITION_CHANGED, oldCallbackId); - } + onPositionChangedCallbackId_ = callbackId; } void AudioBufferBaseSourceNode::setOnPositionChangedInterval(int interval) { - if (std::shared_ptr context = context_.lock()) { - onPositionChangedInterval_ = - static_cast(context->getSampleRate() * static_cast(interval) / 1000); - } + onPositionChangedInterval_ = + static_cast(getContextSampleRate() * static_cast(interval) / 1000); } int AudioBufferBaseSourceNode::getOnPositionChangedInterval() const { return onPositionChangedInterval_; } -std::mutex &AudioBufferBaseSourceNode::getBufferLock() { - return bufferLock_; -} - -double AudioBufferBaseSourceNode::getInputLatency() const { - if (pitchCorrection_) { - if (std::shared_ptr context = context_.lock()) { - return static_cast(stretch_->inputLatency()) / context->getSampleRate(); - } else { - return 0; - } - } - return 0; -} - -double AudioBufferBaseSourceNode::getOutputLatency() const { - if (pitchCorrection_) { - if (std::shared_ptr context = context_.lock()) { - return static_cast(stretch_->outputLatency()) / context->getSampleRate(); - } else { - return 0; - } - } - return 0; +void AudioBufferBaseSourceNode::unregisterOnPositionChangedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::POSITION_CHANGED, callbackId); } void AudioBufferBaseSourceNode::sendOnPositionChangedEvent() { - auto onPositionChangedCallbackId = onPositionChangedCallbackId_.load(std::memory_order_acquire); - - if (onPositionChangedCallbackId != 0 && onPositionChangedTime_ > onPositionChangedInterval_) { + if (onPositionChangedCallbackId_ != 0 && onPositionChangedTime_ > onPositionChangedInterval_) { std::unordered_map body = {{"value", getCurrentPosition()}}; audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::POSITION_CHANGED, onPositionChangedCallbackId, body); + AudioEvent::POSITION_CHANGED, onPositionChangedCallbackId_, body); onPositionChangedTime_ = 0; } @@ -127,7 +109,7 @@ void AudioBufferBaseSourceNode::processWithPitchCorrection( context->getSampleRate(), context->getCurrentSampleFrame()); - if (playbackRate == 0.0f || (!isPlaying() && !isStopScheduled())) { + if (playbackRate == 0.0f || (!isPlaying() && !isStopScheduled()) || stretch_ == nullptr) { processingBuffer->zero(); return; } 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 139594fdb..f8330650c 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 @@ -19,37 +19,42 @@ class AudioBufferBaseSourceNode : public AudioScheduledSourceNode { const std::shared_ptr &context, const BaseAudioBufferSourceOptions &options); + /// @note JS Thread only + void initStretch(const std::shared_ptr> &stretch); + [[nodiscard]] std::shared_ptr getDetuneParam() const; [[nodiscard]] std::shared_ptr getPlaybackRateParam() const; + /// @note Audio Thread only void setOnPositionChangedCallbackId(uint64_t callbackId); + + /// @note Audio Thread only void setOnPositionChangedInterval(int interval); + + /// TODO remove and refactor [[nodiscard]] int getOnPositionChangedInterval() const; - [[nodiscard]] double getInputLatency() const; - [[nodiscard]] double getOutputLatency() const; + + void unregisterOnPositionChangedCallback(uint64_t callbackId); protected: // pitch correction - bool pitchCorrection_; - - std::mutex bufferLock_; + const bool pitchCorrection_; // pitch correction std::shared_ptr> stretch_; std::shared_ptr playbackRateBuffer_; // k-rate params - std::shared_ptr detuneParam_; - std::shared_ptr playbackRateParam_; + const std::shared_ptr detuneParam_; + const std::shared_ptr playbackRateParam_; // internal helper double vReadIndex_; - std::atomic onPositionChangedCallbackId_ = 0; // 0 means no callback + uint64_t onPositionChangedCallbackId_ = 0; // 0 means no callback int onPositionChangedInterval_; int onPositionChangedTime_ = 0; - std::mutex &getBufferLock(); virtual double getCurrentPosition() const = 0; void sendOnPositionChangedEvent(); 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 5206a0d2c..5a2d8383e 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 @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -11,7 +12,6 @@ #include #include -#include #include #include #include @@ -22,27 +22,13 @@ AudioBufferQueueSourceNode::AudioBufferQueueSourceNode( const std::shared_ptr &context, const BaseAudioBufferSourceOptions &options) : AudioBufferBaseSourceNode(context, options) { - buffers_ = {}; - stretch_->presetDefault(channelCount_, context->getSampleRate()); - if (options.pitchCorrection) { // If pitch correction is enabled, add extra frames at the end // to compensate for processing latency. addExtraTailFrames_ = true; - int extraTailFrames = static_cast(stretch_->inputLatency() + stretch_->outputLatency()); - tailBuffer_ = - std::make_shared(channelCount_, extraTailFrames, context->getSampleRate()); - - tailBuffer_->zero(); } - isInitialized_ = true; -} - -AudioBufferQueueSourceNode::~AudioBufferQueueSourceNode() { - Locker locker(getBufferLock()); - - buffers_ = {}; + isInitialized_.store(true, std::memory_order_release); } void AudioBufferQueueSourceNode::stop(double when) { @@ -59,7 +45,7 @@ void AudioBufferQueueSourceNode::start(double when) { void AudioBufferQueueSourceNode::start(double when, double offset) { start(when); - if (buffers_.empty()) { + if (buffers_.empty() || offset < 0) { return; } @@ -72,45 +58,57 @@ void AudioBufferQueueSourceNode::pause() { isPaused_ = true; } -std::string AudioBufferQueueSourceNode::enqueueBuffer(const std::shared_ptr &buffer) { - auto locker = Locker(getBufferLock()); - buffers_.emplace(bufferId_, buffer); +void AudioBufferQueueSourceNode::enqueueBuffer( + const std::shared_ptr &buffer, + size_t bufferId, + const std::shared_ptr &tailBuffer) { + buffers_.emplace_back(bufferId, buffer); + + if (tailBuffer != nullptr) { + tailBuffer_ = tailBuffer; + } if (tailBuffer_ != nullptr) { addExtraTailFrames_ = true; } - - return std::to_string(bufferId_++); } void AudioBufferQueueSourceNode::dequeueBuffer(const size_t bufferId) { - auto locker = Locker(getBufferLock()); - if (buffers_.empty()) { - return; - } + if (auto context = context_.lock()) { + if (buffers_.empty()) { + return; + } - if (buffers_.front().first == bufferId) { - buffers_.pop(); - vReadIndex_ = 0.0; - return; - } + auto graphManager = context->getGraphManager(); + + if (buffers_.front().first == bufferId) { + graphManager->addAudioBufferForDestruction(std::move(buffers_.front().second)); + buffers_.pop_front(); + vReadIndex_ = 0.0; + return; + } - // If the buffer is not at the front, we need to remove it from the queue. - // And keep vReadIndex_ at the same position. - std::queue>> newQueue; - while (!buffers_.empty()) { - if (buffers_.front().first != bufferId) { - newQueue.push(buffers_.front()); + // If the buffer is not at the front, we need to remove it from the linked list.. + // And keep vReadIndex_ at the same position. + for (auto it = std::next(buffers_.begin()); it != buffers_.end(); ++it) { + if (it->first == bufferId) { + graphManager->addAudioBufferForDestruction(std::move(it->second)); + buffers_.erase(it); + return; + } } - buffers_.pop(); } - std::swap(buffers_, newQueue); } void AudioBufferQueueSourceNode::clearBuffers() { - auto locker = Locker(getBufferLock()); - buffers_ = {}; - vReadIndex_ = 0.0; + if (auto context = context_.lock()) { + for (auto it = buffers_.begin(); it != buffers_.end(); ++it) { + context->getGraphManager()->addAudioBufferForDestruction(std::move(it->second)); + } + + buffers_.clear(); + vReadIndex_ = 0.0; + } } void AudioBufferQueueSourceNode::disable() { @@ -124,59 +122,49 @@ void AudioBufferQueueSourceNode::disable() { } AudioScheduledSourceNode::disable(); - buffers_ = {}; + clearBuffers(); } void AudioBufferQueueSourceNode::setOnBufferEndedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onBufferEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onBufferEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::BUFFER_ENDED, oldCallbackId); - } +void AudioBufferQueueSourceNode::unregisterOnBufferEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::BUFFER_ENDED, callbackId); } std::shared_ptr AudioBufferQueueSourceNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // no audio data to fill, zero the output and return. - if (buffers_.empty()) { - processingBuffer->zero(); - return processingBuffer; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBuffer, framesToProcess); - } else { - processWithPitchCorrection(processingBuffer, framesToProcess); - } + // no audio data to fill, zero the output and return. + if (buffers_.empty()) { + processingBuffer->zero(); + return processingBuffer; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBuffer, framesToProcess); } else { - processingBuffer->zero(); + processWithPitchCorrection(processingBuffer, framesToProcess); } + handleStopScheduled(); + return processingBuffer; } double AudioBufferQueueSourceNode::getCurrentPosition() const { - if (std::shared_ptr context = context_.lock()) { - return dsp::sampleFrameToTime(static_cast(vReadIndex_), context->getSampleRate()) + - playedBuffersDuration_; - } else { - return 0.0; - } + return dsp::sampleFrameToTime(static_cast(vReadIndex_), getContextSampleRate()) + + playedBuffersDuration_; } void AudioBufferQueueSourceNode::sendOnBufferEndedEvent(size_t bufferId, bool isLastBufferInQueue) { - auto onBufferEndedCallbackId = onBufferEndedCallbackId_.load(std::memory_order_acquire); - - if (onBufferEndedCallbackId != 0) { + if (onBufferEndedCallbackId_ != 0) { std::unordered_map body = { {"bufferId", std::to_string(bufferId)}, {"isLastBufferInQueue", isLastBufferInQueue}}; audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::BUFFER_ENDED, onBufferEndedCallbackId, body); + AudioEvent::BUFFER_ENDED, onBufferEndedCallbackId_, body); } } @@ -189,60 +177,64 @@ void AudioBufferQueueSourceNode::processWithoutInterpolation( size_t startOffset, size_t offsetLength, float playbackRate) { - auto readIndex = static_cast(vReadIndex_); - size_t writeIndex = startOffset; - - auto data = buffers_.front(); - auto bufferId = data.first; - auto buffer = data.second; + if (auto context = context_.lock()) { + auto readIndex = static_cast(vReadIndex_); + size_t writeIndex = startOffset; - size_t framesLeft = offsetLength; + auto data = buffers_.front(); + auto bufferId = data.first; + auto buffer = data.second; - while (framesLeft > 0) { - size_t framesToEnd = buffer->getSize() - readIndex; - size_t framesToCopy = std::min(framesToEnd, framesLeft); - framesToCopy = framesToCopy > 0 ? framesToCopy : 0; + size_t framesLeft = offsetLength; - assert(readIndex >= 0); - assert(writeIndex >= 0); - assert(readIndex + framesToCopy <= buffer->getSize()); - assert(writeIndex + framesToCopy <= processingBuffer->getSize()); + while (framesLeft > 0) { + size_t framesToEnd = buffer->getSize() - readIndex; + size_t framesToCopy = std::min(framesToEnd, framesLeft); + framesToCopy = framesToCopy > 0 ? framesToCopy : 0; - processingBuffer->copy(*buffer, readIndex, writeIndex, framesToCopy); + assert(readIndex >= 0); + assert(writeIndex >= 0); + assert(readIndex + framesToCopy <= buffer->getSize()); + assert(writeIndex + framesToCopy <= processingBuffer->getSize()); - writeIndex += framesToCopy; - readIndex += framesToCopy; - framesLeft -= framesToCopy; + processingBuffer->copy(*buffer, readIndex, writeIndex, framesToCopy); - if (readIndex >= buffer->getSize()) { - playedBuffersDuration_ += buffer->getDuration(); - buffers_.pop(); + writeIndex += framesToCopy; + readIndex += framesToCopy; + framesLeft -= framesToCopy; - if (!(buffers_.empty() && addExtraTailFrames_)) { - sendOnBufferEndedEvent(bufferId, buffers_.empty()); - } + if (readIndex >= buffer->getSize()) { + playedBuffersDuration_ += buffer->getDuration(); + buffers_.pop_front(); - if (buffers_.empty()) { - if (addExtraTailFrames_) { - buffers_.emplace(bufferId, tailBuffer_); - addExtraTailFrames_ = false; - } else { - processingBuffer->zero(writeIndex, framesLeft); - readIndex = 0; + if (!(buffers_.empty() && addExtraTailFrames_)) { + sendOnBufferEndedEvent(bufferId, buffers_.empty()); + } - break; + if (buffers_.empty()) { + if (addExtraTailFrames_) { + buffers_.emplace_back(bufferId, tailBuffer_); + addExtraTailFrames_ = false; + } else { + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + processingBuffer->zero(writeIndex, framesLeft); + readIndex = 0; + + break; + } } - } - data = buffers_.front(); - bufferId = data.first; - buffer = data.second; - readIndex = 0; + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + data = buffers_.front(); + bufferId = data.first; + buffer = data.second; + readIndex = 0; + } } - } - // update reading index for next render quantum - vReadIndex_ = static_cast(readIndex); + // update reading index for next render quantum + vReadIndex_ = static_cast(readIndex); + } } void AudioBufferQueueSourceNode::processWithInterpolation( @@ -250,69 +242,73 @@ void AudioBufferQueueSourceNode::processWithInterpolation( size_t startOffset, size_t offsetLength, float playbackRate) { - size_t writeIndex = startOffset; - size_t framesLeft = offsetLength; + if (auto context = context_.lock()) { + size_t writeIndex = startOffset; + size_t framesLeft = offsetLength; + + auto data = buffers_.front(); + auto bufferId = data.first; + auto buffer = data.second; + + while (framesLeft > 0) { + auto readIndex = static_cast(vReadIndex_); + size_t nextReadIndex = readIndex + 1; + auto factor = static_cast(vReadIndex_ - static_cast(readIndex)); + + bool crossBufferInterpolation = false; + std::shared_ptr nextBuffer = nullptr; + + if (nextReadIndex >= buffer->getSize()) { + if (buffers_.size() > 1) { + auto tempQueue = buffers_; + tempQueue.pop_front(); + nextBuffer = tempQueue.front().second; + nextReadIndex = 0; + crossBufferInterpolation = true; + } else { + nextReadIndex = readIndex; + } + } - auto data = buffers_.front(); - auto bufferId = data.first; - auto buffer = data.second; + for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i += 1) { + const auto destination = processingBuffer->getChannel(i)->span(); + const auto currentSource = buffer->getChannel(i)->span(); - while (framesLeft > 0) { - auto readIndex = static_cast(vReadIndex_); - size_t nextReadIndex = readIndex + 1; - auto factor = static_cast(vReadIndex_ - static_cast(readIndex)); - - bool crossBufferInterpolation = false; - std::shared_ptr nextBuffer = nullptr; - - if (nextReadIndex >= buffer->getSize()) { - if (buffers_.size() > 1) { - auto tempQueue = buffers_; - tempQueue.pop(); - nextBuffer = tempQueue.front().second; - nextReadIndex = 0; - crossBufferInterpolation = true; - } else { - nextReadIndex = readIndex; + if (crossBufferInterpolation) { + const auto nextSource = nextBuffer->getChannel(i)->span(); + float currentSample = currentSource[readIndex]; + float nextSample = nextSource[nextReadIndex]; + destination[writeIndex] = currentSample + factor * (nextSample - currentSample); + } else { + destination[writeIndex] = + dsp::linearInterpolate(currentSource, readIndex, nextReadIndex, factor); + } } - } - for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i += 1) { - const auto destination = processingBuffer->getChannel(i)->span(); - const auto currentSource = buffer->getChannel(i)->span(); - - if (crossBufferInterpolation) { - const auto nextSource = nextBuffer->getChannel(i)->span(); - float currentSample = currentSource[readIndex]; - float nextSample = nextSource[nextReadIndex]; - destination[writeIndex] = currentSample + factor * (nextSample - currentSample); - } else { - destination[writeIndex] = - dsp::linearInterpolate(currentSource, readIndex, nextReadIndex, factor); - } - } + writeIndex += 1; + // queue source node always use positive playbackRate + vReadIndex_ += std::abs(playbackRate); + framesLeft -= 1; - writeIndex += 1; - // queue source node always use positive playbackRate - vReadIndex_ += std::abs(playbackRate); - framesLeft -= 1; + if (vReadIndex_ >= static_cast(buffer->getSize())) { + playedBuffersDuration_ += buffer->getDuration(); + buffers_.pop_front(); - if (vReadIndex_ >= static_cast(buffer->getSize())) { - playedBuffersDuration_ += buffer->getDuration(); - buffers_.pop(); + sendOnBufferEndedEvent(bufferId, buffers_.empty()); - sendOnBufferEndedEvent(bufferId, buffers_.empty()); + if (buffers_.empty()) { + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + processingBuffer->zero(writeIndex, framesLeft); + vReadIndex_ = 0.0; + break; + } - if (buffers_.empty()) { - processingBuffer->zero(writeIndex, framesLeft); - vReadIndex_ = 0.0; - break; + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + vReadIndex_ = vReadIndex_ - buffer->getSize(); + data = buffers_.front(); + bufferId = data.first; + buffer = data.second; } - - vReadIndex_ = vReadIndex_ - buffer->getSize(); - data = buffers_.front(); - bufferId = data.first; - buffer = data.second; } } } 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 df923a51c..1d56886c9 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 @@ -5,8 +5,8 @@ #include #include +#include #include -#include #include namespace audioapi { @@ -20,21 +20,35 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { explicit AudioBufferQueueSourceNode( const std::shared_ptr &context, const BaseAudioBufferSourceOptions &options); - ~AudioBufferQueueSourceNode() override; + /// @note Audio Thread only void stop(double when) override; void start(double when) override; + /// @note Audio Thread only void start(double when, double offset); + /// @note Audio Thread only void pause(); - std::string enqueueBuffer(const std::shared_ptr &buffer); + /// @note Audio Thread only + void enqueueBuffer( + const std::shared_ptr &buffer, + size_t bufferId, + const std::shared_ptr &tailBuffer); + + /// @note Audio Thread only void dequeueBuffer(size_t bufferId); + /// @note Audio Thread only void clearBuffers(); + + /// @note Audio Thread only void disable() override; + /// @note Audio Thread only void setOnBufferEndedCallbackId(uint64_t callbackId); + void unregisterOnBufferEndedCallback(uint64_t callbackId); + protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -46,8 +60,7 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { private: // User provided buffers - std::queue>> buffers_; - size_t bufferId_ = 0; + std::list>> buffers_{}; bool isPaused_ = false; bool addExtraTailFrames_ = false; @@ -55,7 +68,7 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { double playedBuffersDuration_ = 0; - std::atomic onBufferEndedCallbackId_ = 0; // 0 means no callback + uint64_t onBufferEndedCallbackId_ = 0; // 0 means no callback void processWithoutInterpolation( const std::shared_ptr &processingBuffer, 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 3f76f0298..c4fddbe80 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,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include namespace audioapi { @@ -18,38 +20,10 @@ AudioBufferSourceNode::AudioBufferSourceNode( const AudioBufferSourceOptions &options) : AudioBufferBaseSourceNode(context, options), loop_(options.loop), - loopSkip_(false), + loopSkip_(options.loopSkip), loopStart_(options.loopStart), loopEnd_(options.loopEnd) { - setBuffer(options.buffer); - isInitialized_ = true; -} - -AudioBufferSourceNode::~AudioBufferSourceNode() { - Locker locker(getBufferLock()); - - buffer_.reset(); - alignedBuffer_.reset(); -} - -bool AudioBufferSourceNode::getLoop() const { - return loop_; -} - -bool AudioBufferSourceNode::getLoopSkip() const { - return loopSkip_; -} - -double AudioBufferSourceNode::getLoopStart() const { - return loopStart_; -} - -double AudioBufferSourceNode::getLoopEnd() const { - return loopEnd_; -} - -std::shared_ptr AudioBufferSourceNode::getBuffer() const { - return buffer_; + isInitialized_.store(true, std::memory_order_release); } void AudioBufferSourceNode::setLoop(bool loop) { @@ -62,9 +36,7 @@ void AudioBufferSourceNode::setLoopSkip(bool loopSkip) { void AudioBufferSourceNode::setLoopStart(double loopStart) { if (loopSkip_) { - if (std::shared_ptr context = context_.lock()) { - vReadIndex_ = loopStart * context->getSampleRate(); - } + vReadIndex_ = loopStart * getContextSampleRate(); } loopStart_ = loopStart; } @@ -73,40 +45,41 @@ void AudioBufferSourceNode::setLoopEnd(double loopEnd) { loopEnd_ = loopEnd; } -void AudioBufferSourceNode::setBuffer(const std::shared_ptr &buffer) { - Locker locker(getBufferLock()); +void AudioBufferSourceNode::setBuffer( + const std::shared_ptr &buffer, + const std::shared_ptr &playbackRateBuffer, + const std::shared_ptr &audioBuffer) { std::shared_ptr context = context_.lock(); - if (buffer == nullptr || context == nullptr) { - buffer_ = std::shared_ptr(nullptr); - alignedBuffer_ = std::shared_ptr(nullptr); - loopEnd_ = 0; + if (context == nullptr) { return; } - buffer_ = buffer; - channelCount_ = buffer_->getNumberOfChannels(); + auto graphManager = context->getGraphManager(); - stretch_->presetDefault(static_cast(channelCount_), buffer_->getSampleRate()); + if (buffer_ != nullptr) { + graphManager->addAudioBufferForDestruction(std::move(buffer_)); + } - if (pitchCorrection_) { - int extraTailFrames = - static_cast((getInputLatency() + getOutputLatency()) * context->getSampleRate()); - size_t totalSize = buffer_->getSize() + extraTailFrames; + if (playbackRateBuffer_ != nullptr) { + graphManager->addAudioBufferForDestruction(std::move(playbackRateBuffer_)); + } - alignedBuffer_ = - std::make_shared(totalSize, channelCount_, buffer_->getSampleRate()); - alignedBuffer_->copy(*buffer_, 0, 0, buffer_->getSize()); + graphManager->addAudioBufferForDestruction(std::move(audioBuffer_)); - alignedBuffer_->zero(buffer_->getSize(), extraTailFrames); - } else { - alignedBuffer_ = std::make_shared(*buffer_); + if (buffer == nullptr) { + loopEnd_ = 0; + channelCount_ = 1; + + buffer_ = nullptr; + playbackRateBuffer_ = nullptr; + return; } - audioBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - playbackRateBuffer_ = std::make_shared( - RENDER_QUANTUM_SIZE * 3, channelCount_, context->getSampleRate()); + buffer_ = buffer; + playbackRateBuffer_ = playbackRateBuffer; + audioBuffer_ = audioBuffer; + channelCount_ = buffer_->getNumberOfChannels(); loopEnd_ = buffer_->getDuration(); } @@ -117,54 +90,48 @@ void AudioBufferSourceNode::start(double when, double offset, double duration) { AudioScheduledSourceNode::stop(when + duration); } - if (!alignedBuffer_) { + if (buffer_ == nullptr) { return; } - offset = std::min( - offset, static_cast(alignedBuffer_->getSize()) / alignedBuffer_->getSampleRate()); + offset = std::min(offset, static_cast(buffer_->getSize()) / buffer_->getSampleRate()); if (loop_) { offset = std::min(offset, loopEnd_); } - vReadIndex_ = static_cast(alignedBuffer_->getSampleRate() * offset); + vReadIndex_ = static_cast(buffer_->getSampleRate() * offset); } void AudioBufferSourceNode::disable() { AudioScheduledSourceNode::disable(); - alignedBuffer_.reset(); } void AudioBufferSourceNode::setOnLoopEndedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onLoopEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onLoopEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::LOOP_ENDED, oldCallbackId); - } +void AudioBufferSourceNode::unregisterOnLoopEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::LOOP_ENDED, callbackId); } std::shared_ptr AudioBufferSourceNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // No audio data to fill, zero the output and return. - if (!alignedBuffer_) { - processingBuffer->zero(); - return processingBuffer; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBuffer, framesToProcess); - } else { - processWithPitchCorrection(processingBuffer, framesToProcess); - } + // No audio data to fill, zero the output and return. + if (buffer_ == nullptr) { + processingBuffer->zero(); + return processingBuffer; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBuffer, framesToProcess); } else { - processingBuffer->zero(); + processWithPitchCorrection(processingBuffer, framesToProcess); } + handleStopScheduled(); + return processingBuffer; } @@ -173,10 +140,9 @@ double AudioBufferSourceNode::getCurrentPosition() const { } void AudioBufferSourceNode::sendOnLoopEndedEvent() { - auto onLoopEndedCallbackId = onLoopEndedCallbackId_.load(std::memory_order_acquire); - if (onLoopEndedCallbackId != 0) { + if (onLoopEndedCallbackId_ != 0) { audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::LOOP_ENDED, onLoopEndedCallbackId, {}); + AudioEvent::LOOP_ENDED, onLoopEndedCallbackId_, {}); } } @@ -194,15 +160,8 @@ void AudioBufferSourceNode::processWithoutInterpolation( auto readIndex = static_cast(vReadIndex_); size_t writeIndex = startOffset; - size_t frameStart; - size_t frameEnd; - if (std::shared_ptr context = context_.lock()) { - frameStart = static_cast(getVirtualStartFrame(context->getSampleRate())); - frameEnd = static_cast(getVirtualEndFrame(context->getSampleRate())); - } else { - processingBuffer->zero(); - return; - } + auto frameStart = static_cast(getVirtualStartFrame(getContextSampleRate())); + auto frameEnd = static_cast(getVirtualEndFrame(getContextSampleRate())); size_t frameDelta = frameEnd - frameStart; size_t framesLeft = offsetLength; @@ -222,16 +181,16 @@ void AudioBufferSourceNode::processWithoutInterpolation( assert(readIndex >= 0); assert(writeIndex >= 0); - assert(readIndex + framesToCopy <= alignedBuffer_->getSize()); + assert(readIndex + framesToCopy <= buffer_->getSize()); assert(writeIndex + framesToCopy <= processingBuffer->getSize()); // Direction is forward, we can normally copy the data if (direction == 1) { - processingBuffer->copy(*alignedBuffer_, readIndex, writeIndex, framesToCopy); + processingBuffer->copy(*buffer_, readIndex, writeIndex, framesToCopy); } else { for (size_t ch = 0; ch < processingBuffer->getNumberOfChannels(); ch += 1) { processingBuffer->getChannel(ch)->copyReverse( - *alignedBuffer_->getChannel(ch), readIndex, writeIndex, framesToCopy); + *buffer_->getChannel(ch), readIndex, writeIndex, framesToCopy); } } @@ -267,15 +226,8 @@ void AudioBufferSourceNode::processWithInterpolation( size_t writeIndex = startOffset; - double vFrameStart; - double vFrameEnd; - if (std::shared_ptr context = context_.lock()) { - vFrameStart = getVirtualStartFrame(context->getSampleRate()); - vFrameEnd = getVirtualEndFrame(context->getSampleRate()); - } else { - processingBuffer->zero(); - return; - } + auto vFrameStart = getVirtualStartFrame(getContextSampleRate()); + auto vFrameEnd = getVirtualEndFrame(getContextSampleRate()); auto vFrameDelta = vFrameEnd - vFrameStart; auto frameStart = static_cast(vFrameStart); @@ -299,7 +251,7 @@ void AudioBufferSourceNode::processWithInterpolation( for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i++) { auto destination = processingBuffer->getChannel(i)->span(); - const auto source = alignedBuffer_->getChannel(i)->span(); + const auto source = buffer_->getChannel(i)->span(); destination[writeIndex] = dsp::linearInterpolate(source, readIndex, nextReadIndex, factor); } @@ -328,7 +280,7 @@ double AudioBufferSourceNode::getVirtualStartFrame(float sampleRate) const { } double AudioBufferSourceNode::getVirtualEndFrame(float sampleRate) { - auto inputBufferLength = static_cast(alignedBuffer_->getSize()); + auto inputBufferLength = static_cast(buffer_->getSize()); auto loopEndFrame = loopEnd_ * sampleRate; return loop_ && loopEndFrame > 0 && loopStart_ < loopEnd_ 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 bca9c3abb..a63d640f0 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 @@ -18,26 +18,37 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { explicit AudioBufferSourceNode( const std::shared_ptr &context, const AudioBufferSourceOptions &options); - ~AudioBufferSourceNode() override; - - [[nodiscard]] bool getLoop() const; - [[nodiscard]] bool getLoopSkip() const; - [[nodiscard]] double getLoopStart() const; - [[nodiscard]] double getLoopEnd() const; - [[nodiscard]] std::shared_ptr getBuffer() const; + /// @note Audio Thread only void setLoop(bool loop); + + /// @note Audio Thread only void setLoopSkip(bool loopSkip); + + /// @note Audio Thread only void setLoopStart(double loopStart); + + /// @note Audio Thread only void setLoopEnd(double loopEnd); - void setBuffer(const std::shared_ptr &buffer); + + /// @note Audio Thread only + void setBuffer( + const std::shared_ptr &buffer, + const std::shared_ptr &playbackRateBuffer, + const std::shared_ptr &audioBuffer); using AudioScheduledSourceNode::start; + /// @note Audio Thread only void start(double when, double offset, double duration = -1); + + /// @note Audio Thread only void disable() override; + /// @note Audio Thread only void setOnLoopEndedCallbackId(uint64_t callbackId); + void unregisterOnLoopEndedCallback(uint64_t callbackId); + protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -53,9 +64,8 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { // User provided buffer std::shared_ptr buffer_; - std::shared_ptr alignedBuffer_; - std::atomic onLoopEndedCallbackId_ = 0; // 0 means no callback + uint64_t onLoopEndedCallbackId_ = 0; // 0 means no callback void sendOnLoopEndedEvent(); void processWithoutInterpolation( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp index 3b51dad3c..551b93982 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp @@ -63,11 +63,11 @@ bool AudioScheduledSourceNode::isStopScheduled() { } void AudioScheduledSourceNode::setOnEndedCallbackId(const uint64_t callbackId) { - auto oldCallbackId = onEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::ENDED, oldCallbackId); - } +void AudioScheduledSourceNode::unregisterOnEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::ENDED, callbackId); } void AudioScheduledSourceNode::updatePlaybackInfo( @@ -77,12 +77,6 @@ void AudioScheduledSourceNode::updatePlaybackInfo( size_t &nonSilentFramesToProcess, float sampleRate, size_t currentSampleFrame) { - if (!isInitialized_) { - startOffset = 0; - nonSilentFramesToProcess = 0; - return; - } - auto firstFrame = currentSampleFrame; size_t lastFrame = firstFrame + framesToProcess - 1; @@ -165,10 +159,9 @@ void AudioScheduledSourceNode::updatePlaybackInfo( void AudioScheduledSourceNode::disable() { AudioNode::disable(); - auto onEndedCallbackId = onEndedCallbackId_.load(std::memory_order_acquire); - if (onEndedCallbackId != 0) { + if (onEndedCallbackId_ != 0) { audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::ENDED, onEndedCallbackId, {}); + AudioEvent::ENDED, onEndedCallbackId_, {}); } } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h index f496c3a71..d4129dcd7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -27,24 +26,36 @@ class AudioScheduledSourceNode : public AudioNode { virtual void start(double when); virtual void stop(double when); + /// @note Audio Thread only bool isUnscheduled(); + + /// @note Audio Thread only bool isScheduled(); + + /// @note Audio Thread only bool isPlaying(); + + /// @note Audio Thread only bool isFinished(); + + /// @note Audio Thread only bool isStopScheduled(); + /// @note Audio Thread only void setOnEndedCallbackId(uint64_t callbackId); void disable() override; + void unregisterOnEndedCallback(uint64_t callbackId); + protected: double startTime_; double stopTime_; PlaybackState playbackState_; - std::atomic onEndedCallbackId_ = 0; - std::shared_ptr audioEventHandlerRegistry_; + uint64_t onEndedCallbackId_ = 0; + const std::shared_ptr audioEventHandlerRegistry_; void updatePlaybackInfo( const std::shared_ptr &processingBuffer, 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 1e36ec1ea..8f863698c 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 @@ -10,10 +10,14 @@ namespace audioapi { ConstantSourceNode::ConstantSourceNode( const std::shared_ptr &context, const ConstantSourceOptions &options) - : AudioScheduledSourceNode(context) { - offsetParam_ = std::make_shared( - options.offset, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); - isInitialized_ = true; + : AudioScheduledSourceNode(context), + offsetParam_( + std::make_shared( + options.offset, + MOST_NEGATIVE_SINGLE_FLOAT, + MOST_POSITIVE_SINGLE_FLOAT, + context)) { + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr ConstantSourceNode::getOffsetParam() const { 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 edb536b27..5c030c59f 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 @@ -24,6 +24,6 @@ class ConstantSourceNode : public AudioScheduledSourceNode { int framesToProcess) override; private: - std::shared_ptr offsetParam_; + const std::shared_ptr offsetParam_; }; } // namespace audioapi 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 c001e2b77..2d32fb8dc 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 @@ -13,7 +13,7 @@ OscillatorNode::OscillatorNode( const OscillatorOptions &options) : AudioScheduledSourceNode(context, options) { frequencyParam_ = std::make_shared( - options.frequency, -context->getNyquistFrequency(), context->getNyquistFrequency(), context); + options.frequency, -getNyquistFrequency(), getNyquistFrequency(), context); detuneParam_ = std::make_shared( options.detune, -1200 * LOG2_MOST_POSITIVE_SINGLE_FLOAT, @@ -28,7 +28,7 @@ OscillatorNode::OscillatorNode( audioBuffer_ = std::make_shared(RENDER_QUANTUM_SIZE, 1, context->getSampleRate()); - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr OscillatorNode::getFrequencyParam() const { @@ -39,10 +39,6 @@ std::shared_ptr OscillatorNode::getDetuneParam() const { return detuneParam_; } -OscillatorType OscillatorNode::getType() { - return type_; -} - void OscillatorNode::setType(OscillatorType type) { if (std::shared_ptr context = context_.lock()) { type_ = type; 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 184643a06..3b6eb6bb4 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 @@ -20,8 +20,11 @@ class OscillatorNode : public AudioScheduledSourceNode { [[nodiscard]] std::shared_ptr getFrequencyParam() const; [[nodiscard]] std::shared_ptr getDetuneParam() const; - [[nodiscard]] OscillatorType getType(); + + /// @note Audio Thread only void setType(OscillatorType); + + /// @note Audio Thread only void setPeriodicWave(const std::shared_ptr &periodicWave); protected: diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp index a98201c6b..00d710192 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp @@ -12,12 +12,11 @@ RecorderAdapterNode::RecorderAdapterNode(const std::shared_ptr : AudioNode(context, AudioScheduledSourceNodeOptions()) { // It should be marked as initialized only after it is connected to the // recorder. Internal buffer size is based on the recorder's buffer length. - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); } void RecorderAdapterNode::init(size_t bufferSize, int channelCount) { - std::shared_ptr context = context_.lock(); - if (isInitialized_ || context == nullptr) { + if (isInitialized_.load(std::memory_order_acquire)) { return; } @@ -41,12 +40,12 @@ void RecorderAdapterNode::init(size_t bufferSize, int channelCount) { // context output and not enforcing anything on the system output/input configuration. // A lot of words for a couple of lines of implementation :shrug: adapterOutputBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - isInitialized_ = true; + std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, getContextSampleRate()); + isInitialized_.store(true, std::memory_order_release); } void RecorderAdapterNode::cleanup() { - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); buff_.clear(); adapterOutputBuffer_.reset(); } @@ -54,11 +53,6 @@ void RecorderAdapterNode::cleanup() { std::shared_ptr RecorderAdapterNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (!isInitialized_) { - processingBuffer->zero(); - return processingBuffer; - } - readFrames(framesToProcess); processingBuffer->sum(*adapterOutputBuffer_, ChannelInterpretation::SPEAKERS); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h index 0e17b264d..043fa8690 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h @@ -28,7 +28,6 @@ class RecorderAdapterNode : public AudioNode { void init(size_t bufferSize, int channelCount); void cleanup(); - int channelCount_{}; // TODO: CircularOverflowableAudioBuffer std::vector> buff_; 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 38b2a7c61..14b52fb01 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 @@ -37,7 +37,11 @@ StreamerNode::StreamerNode( bufferedAudioBufferSize_(0), audio_stream_index_(-1), maxResampledSamples_(0), - processedSamples_(0) {} + processedSamples_(0) { +#if !RN_AUDIO_API_FFMPEG_DISABLED + initialize(options.streamPath); +#endif // RN_AUDIO_API_FFMPEG_DISABLED +} #else StreamerNode::StreamerNode( const std::shared_ptr &context, @@ -51,60 +55,6 @@ StreamerNode::~StreamerNode() { #endif // RN_AUDIO_API_FFMPEG_DISABLED } -bool StreamerNode::initialize(const std::string &input_url) { -#if !RN_AUDIO_API_FFMPEG_DISABLED - streamPath_ = input_url; - std::shared_ptr context = context_.lock(); - if (context == nullptr) { - return false; - } - - if (isInitialized_) { - cleanup(); - } - - if (!openInput(input_url)) { - if (VERBOSE) - printf("Failed to open input\n"); - return false; - } - - if (!findAudioStream() || !setupDecoder() || !setupResampler(context->getSampleRate())) { - if (VERBOSE) - printf("Failed to find/setup audio stream\n"); - cleanup(); - return false; - } - - pkt_ = av_packet_alloc(); - frame_ = av_frame_alloc(); - - if (pkt_ == nullptr || frame_ == nullptr) { - if (VERBOSE) - printf("Failed to allocate packet or frame\n"); - cleanup(); - return false; - } - - channelCount_ = codecpar_->ch_layout.nb_channels; - audioBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - - auto [sender, receiver] = channels::spsc::channel< - StreamingData, - STREAMER_NODE_SPSC_OVERFLOW_STRATEGY, - STREAMER_NODE_SPSC_WAIT_STRATEGY>(CHANNEL_CAPACITY); - sender_ = std::move(sender); - receiver_ = std::move(receiver); - - streamingThread_ = std::thread(&StreamerNode::streamAudio, this); - isInitialized_ = true; - return true; -#else - return false; -#endif // RN_AUDIO_API_FFMPEG_DISABLED -} - std::shared_ptr StreamerNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { @@ -159,6 +109,56 @@ std::shared_ptr StreamerNode::processNode( } #if !RN_AUDIO_API_FFMPEG_DISABLED +bool StreamerNode::initialize(const std::string &input_url) { + streamPath_ = input_url; + std::shared_ptr context = context_.lock(); + if (context == nullptr) { + return false; + } + + if (isInitialized_.load(std::memory_order_acquire)) { + return false; + } + + if (!openInput(input_url)) { + if (VERBOSE) + printf("Failed to open input\n"); + return false; + } + + if (!findAudioStream() || !setupDecoder() || !setupResampler(context->getSampleRate())) { + if (VERBOSE) + printf("Failed to find/setup audio stream\n"); + cleanup(); + return false; + } + + pkt_ = av_packet_alloc(); + frame_ = av_frame_alloc(); + + if (pkt_ == nullptr || frame_ == nullptr) { + if (VERBOSE) + printf("Failed to allocate packet or frame\n"); + cleanup(); + return false; + } + + channelCount_ = codecpar_->ch_layout.nb_channels; + audioBuffer_ = + std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); + + auto [sender, receiver] = channels::spsc::channel< + StreamingData, + STREAMER_NODE_SPSC_OVERFLOW_STRATEGY, + STREAMER_NODE_SPSC_WAIT_STRATEGY>(CHANNEL_CAPACITY); + sender_ = std::move(sender); + receiver_ = std::move(receiver); + + streamingThread_ = std::thread(&StreamerNode::streamAudio, this); + isInitialized_.store(true, std::memory_order_release); + return true; +} + bool StreamerNode::setupResampler(float outSampleRate) { // Allocate resampler context swrCtx_ = swr_alloc(); @@ -346,10 +346,10 @@ void StreamerNode::cleanup() { } audio_stream_index_ = -1; - isInitialized_ = false; decoder_ = nullptr; codecpar_ = nullptr; maxResampledSamples_ = 0; + isInitialized_.store(false, std::memory_order_release); } #endif // RN_AUDIO_API_FFMPEG_DISABLED } // namespace audioapi 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 6a45c2eb1..41ff21a8a 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 @@ -67,15 +67,6 @@ class StreamerNode : public AudioScheduledSourceNode { const StreamerOptions &options); ~StreamerNode() override; - /** - * @brief Initialize all necessary ffmpeg components for streaming audio - */ - bool initialize(const std::string &inputUrl); - - std::string getStreamPath() const { - return streamPath_; - } - protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -112,6 +103,12 @@ class StreamerNode : public AudioScheduledSourceNode { STREAMER_NODE_SPSC_WAIT_STRATEGY> receiver_; + /// @brief Initialize the StreamerNode by opening the input stream, + /// finding the audio stream, setting up the decoder, and starting the streaming thread. + /// @param inputUrl The URL of the input stream + /// @return true if initialization was successful, false otherwise + bool initialize(const std::string &inputUrl); + /** * @brief Setting up the resampler * @param outSampleRate Sample rate for the output audio diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp index 09d9450a8..bfaf57555 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp @@ -9,14 +9,14 @@ WorkletSourceNode::WorkletSourceNode( const std::shared_ptr &context, WorkletsRunner &&workletRunner) : AudioScheduledSourceNode(context), workletRunner_(std::move(workletRunner)) { - isInitialized_ = true; - // Prepare buffers for audio processing size_t outputChannelCount = this->getChannelCount(); outputBuffsHandles_.resize(outputChannelCount); for (size_t i = 0; i < outputChannelCount; ++i) { outputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); } + + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletSourceNode::processNode( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDecoder.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDecoder.cpp index 7b130c9d9..c37448064 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDecoder.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDecoder.cpp @@ -169,4 +169,4 @@ AudioBufferResult AudioDecoder::decodeWithPCMInBase64( return Ok(std::move(audioBuffer)); } -} // namespace audioapi \ No newline at end of file +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp index f5bc89fcb..3755ec935 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp @@ -151,7 +151,7 @@ void AudioGraphManager::addAudioParam(const std::shared_ptr ¶m) sender_.send(std::move(event)); } -void AudioGraphManager::addAudioBuffeForDestruction(std::shared_ptr buffer) { +void AudioGraphManager::addAudioBufferForDestruction(std::shared_ptr buffer) { // direct access because this is called from the Audio thread audioBuffers_.emplace_back(std::move(buffer)); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h index 06c9ade3a..4a7bd2deb 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h @@ -101,7 +101,7 @@ class AudioGraphManager { /// @brief Adds an audio buffer to the manager for destruction. /// @note Called directly from the Audio thread (bypasses SPSC). - void addAudioBuffeForDestruction(std::shared_ptr buffer); + void addAudioBufferForDestruction(std::shared_ptr buffer); void cleanup(); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h index 5a2bbfc85..dd12c78cd 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h @@ -2,6 +2,7 @@ #include #include +#include #include // https://webaudio.github.io/web-audio-api/ @@ -11,6 +12,7 @@ namespace audioapi { static constexpr int RENDER_QUANTUM_SIZE = 128; static constexpr size_t MAX_FFT_SIZE = 32768; static constexpr int MAX_CHANNEL_COUNT = 32; +static constexpr float DEFAULT_SAMPLE_RATE = 44100.0f; static constexpr int OCTAVE_RANGE = 1200; static constexpr int BIQUAD_GAIN_DB_FACTOR = 40; @@ -31,4 +33,13 @@ static constexpr float PI = std::numbers::pi_v; static constexpr size_t PROMISE_VENDOR_THREAD_POOL_WORKER_COUNT = 4; static constexpr size_t PROMISE_VENDOR_THREAD_POOL_LOAD_BALANCER_QUEUE_SIZE = 32; static constexpr size_t PROMISE_VENDOR_THREAD_POOL_WORKER_QUEUE_SIZE = 32; + +// Cache line size +#ifdef __cpp_lib_hardware_interference_size +using std::hardware_constructive_interference_size; +using std::hardware_destructive_interference_size; +#else +constexpr std::size_t hardware_constructive_interference_size = 64; +constexpr std::size_t hardware_destructive_interference_size = 64; +#endif } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/dsp/Windows.hpp b/packages/react-native-audio-api/common/cpp/audioapi/dsp/Windows.hpp deleted file mode 100644 index 8a5db033d..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/dsp/Windows.hpp +++ /dev/null @@ -1,197 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace audioapi::dsp { - -// https://en.wikipedia.org/wiki/Window_function -// https://personalpages.hs-kempten.de/~vollratj/InEl/pdf/Window%20function%20-%20Wikipedia.pdf -class WindowFunction { - public: - explicit WindowFunction(float amplitude = 1.0f) : amplitude_(amplitude) {} - - virtual void apply(std::span data) const noexcept = 0; - // forces STFT perfect-reconstruction (WOLA) on an existing window, for a given STFT interval. - static void forcePerfectReconstruction(std::span data, int interval) { - int windowLength = static_cast(data.size()); - - for (int i = 0; i < interval; ++i) { - float sum2 = 0; - - for (int index = i; index < windowLength; index += interval) { - sum2 += data[index] * data[index]; - } - - float factor = 1 / std::sqrt(sum2); - - for (int index = i; index < windowLength; index += interval) { - data[index] *= factor; - } - } - } - - protected: - // 1/L = amplitude - float amplitude_; -}; - -//https://en.wikipedia.org/wiki/Hann_function -// https://www.sciencedirect.com/topics/engineering/hanning-window -// https://docs.scipy.org/doc//scipy-1.2.3/reference/generated/scipy.signal.windows.hann.html#scipy.signal.windows.hann -class Hann : public WindowFunction { - public: - explicit Hann(float amplitude = 1.0f) : WindowFunction(amplitude) {} - - void apply(std::span data) const noexcept override { - const size_t size = data.size(); - if (size < 2) { - return; - } - - const float invSizeMinusOne = 1.0f / static_cast(size - 1); - const float constantPart = 2.0f * std::numbers::pi_v * invSizeMinusOne; - - for (size_t i = 0; i < size; ++i) { - float window = 0.5f * (1.0f - std::cos(constantPart * i)); - data[i] = window * amplitude_; - } - } -}; - -// https://www.sciencedirect.com/topics/engineering/blackman-window -// https://docs.scipy.org/doc//scipy-1.2.3/reference/generated/scipy.signal.windows.blackman.html#scipy.signal.windows.blackman -class Blackman : public WindowFunction { - public: - explicit Blackman(float amplitude = 1.0f) : WindowFunction(amplitude) {} - - void apply(std::span data) const noexcept override { - const size_t size = data.size(); - if (size < 2) { - return; - } - - const float invSizeMinusOne = 1.0f / static_cast(size - 1); - const float alpha = 2.0f * std::numbers::pi_v * invSizeMinusOne; - - for (size_t i = 0; i < size; ++i) { - const float phase = alpha * i; - // 4*PI*x is just 2 * (2*PI*x) - const float window = 0.42f - 0.50f * std::cos(phase) + 0.08f * std::cos(2.0f * phase); - data[i] = window * amplitude_; - } - } -}; - -// https://en.wikipedia.org/wiki/Kaiser_window -class Kaiser : public WindowFunction { - public: - explicit Kaiser(float beta, float amplitude = 1.0f) - : WindowFunction(amplitude), beta_(beta), invB0_(1.0f / bessel0(beta)) {} - - static Kaiser - withBandwidth(float bandwidth, bool heuristicOptimal = false, float amplitude = 1.0f) { - return Kaiser(bandwidthToBeta(bandwidth, heuristicOptimal), amplitude); - } - - void apply(std::span data) const noexcept override { - const size_t size = data.size(); - if (size == 0) { - return; - } - - const float invSize = 1.0f / static_cast(size); - const float commonScale = invB0_ * amplitude_; - - for (size_t i = 0; i < size; ++i) { - // Optimized 'r' calculation: (2i+1)/size - 1 - const float r = (static_cast(2 * i + 1) * invSize) - 1.0f; - const float arg = std::sqrt(std::max(0.0f, 1.0f - r * r)); - - data[i] = bessel0(beta_ * arg) * commonScale; - } - } - - private: - // beta = pi * alpha - // invB0 = 1 / I0(beta) - float beta_; - float invB0_; - - // https://en.wikipedia.org/wiki/Bessel_function#Modified_Bessel_functions:_I%CE%B1,_K%CE%B1 - static inline float bessel0(float x) { - const double significanceLimit = 1e-4; - auto result = 0.0f; - auto term = 1.0f; - auto m = 1.0f; - while (term > significanceLimit) { - result += term; - ++m; - term *= (x * x) / (4 * m * m); - } - - return result; - } - inline static float bandwidthToBeta(float bandwidth, bool heuristicOptimal = false) { - if (heuristicOptimal) { // Heuristic based on numerical search - return bandwidth + 8.0f / (bandwidth + 3.0f) * (bandwidth + 3.0f) + - 0.25f * std::max(3.0f - bandwidth, 0.0f); - } - - bandwidth = std::max(bandwidth, 2.0f); - auto alpha = std::sqrt(bandwidth * bandwidth * 0.25f - 1.0f); - return alpha * std::numbers::pi_v; - } -}; - -// https://www.recordingblogs.com/wiki/gaussian-window -class ApproximateConfinedGaussian : public WindowFunction { - public: - explicit ApproximateConfinedGaussian(float sigma, float amplitude = 1.0f) - : WindowFunction(amplitude), gaussianFactor_(0.0625f / (sigma * sigma)) {} - - static ApproximateConfinedGaussian withBandwidth(float bandwidth, float amplitude = 1.0f) { - return ApproximateConfinedGaussian(bandwidthToSigma(bandwidth), amplitude); - } - - void apply(std::span data) const noexcept override { - const size_t size = data.size(); - if (size == 0) - return; - - const float g1 = getGaussian(1.0f); - const float g3 = getGaussian(3.0f); - const float g_1 = getGaussian(-1.0f); - const float g2 = getGaussian(2.0f); - - const float offsetScale = g1 / (g3 + g_1); - const float norm = 1.0f / (g1 - 2.0f * offsetScale * g2); - - const float invSize = 1.0f / static_cast(size); - const float totalAmplitude = norm * amplitude_; - - for (size_t i = 0; i < size; ++i) { - const float r = (static_cast(2 * i + 1) * invSize) - 1.0f; - - const float gR = getGaussian(r); - const float gRMinus2 = getGaussian(r - 2.0f); - const float gRPlus2 = getGaussian(r + 2.0f); - - data[i] = totalAmplitude * (gR - offsetScale * (gRMinus2 + gRPlus2)); - } - } - - private: - float gaussianFactor_; - - inline static float bandwidthToSigma(float bandwidth) noexcept { - return 0.3f / std::sqrt(bandwidth); - } - - [[nodiscard]] inline float getGaussian(float x) const noexcept { - return std::exp(-x * x * gaussianFactor_); - } -}; -} // namespace audioapi::dsp diff --git a/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp b/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp index 796d064b3..e1a055f00 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp @@ -21,8 +21,8 @@ uint64_t AudioEventHandlerRegistry::registerHandler( const std::shared_ptr &handler) { auto listenerId = listenerIdCounter_.fetch_add(1, std::memory_order_relaxed); - if (callInvoker_ == nullptr || runtime_ == nullptr) { - // If callInvoker or runtime is not valid, we cannot register the handler + if (runtime_ == nullptr) { + // If runtime is not valid, we cannot register the handler return 0; } @@ -39,8 +39,8 @@ uint64_t AudioEventHandlerRegistry::registerHandler( } void AudioEventHandlerRegistry::unregisterHandler(AudioEvent eventName, uint64_t listenerId) { - if (callInvoker_ == nullptr || runtime_ == nullptr) { - // If callInvoker or runtime is not valid, we cannot unregister the handler + if (runtime_ == nullptr) { + // If runtime is not valid, we cannot unregister the handler return; } @@ -68,9 +68,8 @@ void AudioEventHandlerRegistry::unregisterHandler(AudioEvent eventName, uint64_t void AudioEventHandlerRegistry::invokeHandlerWithEventBody( AudioEvent eventName, const std::unordered_map &body) { - // callInvoker_ and runtime_ must be valid to invoke handlers - // this might happen when react-native is reloaded or the app is closed - if (callInvoker_ == nullptr || runtime_ == nullptr) { + if (runtime_ == nullptr) { + // If runtime is not valid, we cannot unregister the handler return; } @@ -127,9 +126,8 @@ void AudioEventHandlerRegistry::invokeHandlerWithEventBody( AudioEvent eventName, uint64_t listenerId, const std::unordered_map &body) { - // callInvoker_ and runtime_ must be valid to invoke handlers - // this might happen when react-native is reloaded or the app is closed - if (callInvoker_ == nullptr || runtime_ == nullptr) { + if (runtime_ == nullptr) { + // If runtime is not valid, we cannot unregister the handler return; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.h b/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.h index cca44180f..0ddac36f8 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/events/AudioEventHandlerRegistry.h @@ -58,7 +58,7 @@ class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry, private: std::atomic listenerIdCounter_{1}; // Atomic counter for listener IDs - std::shared_ptr callInvoker_; + const std::shared_ptr callInvoker_; jsi::Runtime *runtime_; std::unordered_map>> eventHandlers_; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h index c3316aeba..e5846f1ec 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h @@ -104,6 +104,12 @@ struct AudioBufferSourceOptions : BaseAudioBufferSourceOptions { float loopStart = 0.0f; float loopEnd = 0.0f; bool loop = false; + bool loopSkip = false; + + explicit AudioBufferSourceOptions(BaseAudioBufferSourceOptions &&options) + : BaseAudioBufferSourceOptions(options) { + channelCount = 1; + } }; struct StreamerOptions : AudioScheduledSourceNodeOptions { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp index be7ddcbb4..aa691eac5 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include -#include #include namespace audioapi { @@ -23,11 +23,13 @@ using namespace channels::spsc; /// In this setup no locking happens and modifications can be seen by Audio thread. /// @note it is intended to be used for two threads one which schedules events and one which processes them /// @note it is not safe to be copied across two threads use std::shared_ptr if you need to share data -template +template class CrossThreadEventScheduler { + using EventType = FatFunction; + public: explicit CrossThreadEventScheduler(size_t capacity) { - auto [sender, receiver] = channel>(capacity); + auto [sender, receiver] = channel(capacity); eventSender_ = std::move(sender); eventReceiver_ = std::move(receiver); } @@ -35,26 +37,26 @@ class CrossThreadEventScheduler { CrossThreadEventScheduler &operator=(const CrossThreadEventScheduler &) = delete; /// @brief Schedules an event to be processed on the audio thread. - /// @param event The event to schedule. - /// @return True if the event was successfully scheduled, false if the queue is full. - bool scheduleEvent(std::function &&event) noexcept( + /// @param event The event to schedule. Implicitly converts from lambdas. + /// @return True if scheduled, false if the queue is full. + /// @note Requires that sizeof(lambda) <= FunctionSize. + bool scheduleEvent(EventType &&event) noexcept( noexcept(eventSender_.try_send(std::move(event)))) { return eventSender_.try_send(std::move(event)) == ResponseStatus::SUCCESS; } /// @brief Processes all scheduled events. /// @param data The data to pass to each event. - void processAllEvents(T &data) noexcept( - noexcept(eventReceiver_.try_receive(std::declval &>()))) { - std::function event; + void processAllEvents(T &data) noexcept { + EventType event; while (eventReceiver_.try_receive(event) == ResponseStatus::SUCCESS) { event(data); } } private: - Sender> eventSender_; - Receiver> eventReceiver_; + Sender eventSender_; + Receiver eventReceiver_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/FatFunction.hpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/FatFunction.hpp new file mode 100644 index 000000000..b32d154ee --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/FatFunction.hpp @@ -0,0 +1,150 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace audioapi { + +template +concept CallableConcept = requires(_Callable &&_c, _FpArgs &&..._args) { + sizeof(std::decay_t<_Callable>) <= N; + { _c(std::forward<_FpArgs>(_args)...) } -> std::convertible_to<_FpReturnType>; +}; + +template +class FatFunction; + +/// @brief FatFunction is a fixed-size function wrapper that can store callable objects +/// of a specific size N without dynamic memory allocation. +/// @tparam N Size in bytes to allocate for the callable object +/// @tparam _Fp The function signature (e.g., void(), int(int), etc.) +template +class FatFunction { + + private: + using _InvokerType = _FpReturnType (*)(const std::byte *storage, _FpArgs... args); + using _DeleterType = void (*)(std::byte *storage); + using _MoverType = void (*)(std::byte *dest, std::byte *src); + + public: + FatFunction() = default; + FatFunction(std::nullptr_t) : FatFunction() {} + + /// @brief Constructs a FatFunction from a callable object. + /// @tparam _Callable The type of the callable object + /// @tparam (enable_if) Ensures that the callable fits within the allocated size N + /// and is invocable with the specified signature. + /// @param callable The callable object to store + template + requires CallableConcept + FatFunction(_Callable &&callable) { + using DecayedCallable = std::decay_t<_Callable>; + new (storage_.data()) DecayedCallable(std::forward<_Callable>(callable)); + invoker_ = [](const std::byte *storage, _FpArgs... args) -> _FpReturnType { + const DecayedCallable *callablePtr = reinterpret_cast(storage); + return (*callablePtr)(std::forward<_FpArgs>(args)...); + }; + if constexpr (std::is_trivially_destructible_v) { + // No custom deleter needed for trivially destructible types + deleter_ = nullptr; + } else { + deleter_ = [](std::byte *storage) { + DecayedCallable *callablePtr = reinterpret_cast(storage); + callablePtr->~DecayedCallable(); + }; + } + if constexpr (std::is_trivially_move_constructible_v) { + // No custom mover needed for trivially moveable types as memcpy is a fallback + mover_ = nullptr; + } else { + mover_ = [](std::byte *dest, std::byte *src) { + DecayedCallable *srcPtr = reinterpret_cast(src); + new (dest) DecayedCallable(std::move(*srcPtr)); + }; + } + } + + /// @brief Move constructor + /// @param other + FatFunction(FatFunction &&other) noexcept { + if (other.invoker_) { + if (other.mover_) { + other.mover_(storage_.data(), other.storage_.data()); + } else { + std::memcpy(storage_.data(), other.storage_.data(), N); + } + invoker_ = other.invoker_; + deleter_ = other.deleter_; + mover_ = other.mover_; + other.reset(); + } + } + + /// @brief Move assignment operator + /// @param other + FatFunction &operator=(FatFunction &&other) noexcept { + if (this != &other) { + reset(); + if (other.invoker_) { + if (other.mover_) { + other.mover_(storage_.data(), other.storage_.data()); + } else { + std::memcpy(storage_.data(), other.storage_.data(), N); + } + invoker_ = other.invoker_; + deleter_ = other.deleter_; + mover_ = other.mover_; + other.reset(); + } + } + return *this; + } + + /// @brief Call operator to invoke the stored callable + /// @param ...args Arguments to pass to the callable + /// @return The result of the callable invocation + _FpReturnType operator()(_FpArgs &&...args) const { + if (!invoker_) { + throw std::bad_function_call(); + } + return invoker_(storage_.data(), std::forward<_FpArgs>(args)...); + } + + /// @brief Destructor + ~FatFunction() { + reset(); + } + + /// @brief Releases the stored callable and returns its storage and deleter. + /// @return A pair containing the storage array and the deleter function + /// @note To clear resources properly after release, the user must call the deleter on the storage. + std::pair, _DeleterType> release() { + std::array storageCopy; + std::memcpy(storageCopy.data(), storage_.data(), N); + _DeleterType deleterCopy = deleter_; + deleter_ = nullptr; + invoker_ = nullptr; + mover_ = nullptr; + return {std::move(storageCopy), deleterCopy}; + } + + private: + alignas(std::max_align_t) std::array storage_; + _InvokerType invoker_ = nullptr; // Function pointer to invoke the stored callable + _DeleterType deleter_ = nullptr; // Function pointer to delete the stored callable + _MoverType mover_ = nullptr; // Function pointer to move the stored callable + + void reset() { + if (deleter_) { + deleter_(storage_.data()); + } + deleter_ = nullptr; + invoker_ = nullptr; + mover_ = nullptr; + } +}; + +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/SpscChannel.hpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/SpscChannel.hpp index e4a0c4696..df9cf27d8 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/utils/SpscChannel.hpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/SpscChannel.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,14 +9,6 @@ #include #include -#ifdef __cpp_lib_hardware_interference_size -using std::hardware_constructive_interference_size; -using std::hardware_destructive_interference_size; -#else -constexpr std::size_t hardware_constructive_interference_size = 64; -constexpr std::size_t hardware_destructive_interference_size = 64; -#endif - namespace audioapi::channels::spsc { /// @brief Overflow strategy for sender when the channel is full diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/TripleBuffer.hpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/TripleBuffer.hpp new file mode 100644 index 000000000..88a358e37 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/TripleBuffer.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace audioapi { + +template +concept ConstructibleFromCopyable = std::constructible_from && + (std::copy_constructible> && ...); + +/// @brief A lock-free triple buffer for single producer and single consumer scenarios. +/// The producer can write to one buffer while the consumer reads from another buffer, and the third buffer is idle. +/// The producer can publish new data by swapping the back buffer with the idle buffer, +/// and the consumer can get the latest data by swapping the front buffer with the idle buffer if there is an update. +/// @tparam T The type of the buffer. +template +class TripleBuffer { + public: + template + requires ConstructibleFromCopyable + explicit TripleBuffer(Args &&...args) { + new (&buffers_[0]) T(args...); + new (&buffers_[1]) T(args...); + new (&buffers_[2]) T(args...); + } + + ~TripleBuffer() { + std::launder(reinterpret_cast(&buffers_[0]))->~T(); + std::launder(reinterpret_cast(&buffers_[1]))->~T(); + std::launder(reinterpret_cast(&buffers_[2]))->~T(); + } + + TripleBuffer(const TripleBuffer &) = delete; + TripleBuffer &operator=(const TripleBuffer &) = delete; + + TripleBuffer(TripleBuffer &&) = delete; + TripleBuffer &operator=(TripleBuffer &&) = delete; + + T *getForWriter() { + return std::launder(reinterpret_cast(&buffers_[backIndex_])); + } + + void publish() { + State newState{backIndex_, true}; + auto prevState = state_.exchange(newState, std::memory_order_acq_rel); + backIndex_ = prevState.index; + } + + T *getForReader() { + auto state = state_.load(std::memory_order_relaxed); + if (state.hasUpdate) { + State newState{frontIndex_, false}; + auto prevState = state_.exchange(newState, std::memory_order_acq_rel); + frontIndex_ = prevState.index; + } + + return std::launder(reinterpret_cast(&buffers_[frontIndex_])); + } + + private: + struct State { + uint32_t index : 2; + uint32_t hasUpdate : 1; + }; + + struct alignas(hardware_destructive_interference_size) AlignedBuffer { + alignas(T) std::byte data[sizeof(T)]; + }; + + AlignedBuffer buffers_[3]; + alignas(hardware_destructive_interference_size) uint32_t frontIndex_{0}; + alignas(hardware_destructive_interference_size) std::atomic state_{{1, false}}; + alignas(hardware_destructive_interference_size) uint32_t backIndex_{2}; +}; + +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp index 3382e74e1..6faa6bfe4 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -28,7 +28,7 @@ class TestableWaveShaperNode : public WaveShaperNode { public: explicit TestableWaveShaperNode(std::shared_ptr context) : WaveShaperNode(context, WaveShaperOptions()) { - testCurve_ = std::make_shared(3); + testCurve_ = std::make_shared(3); auto data = testCurve_->span(); data[0] = -2.0f; data[1] = 0.0f; @@ -41,7 +41,7 @@ class TestableWaveShaperNode : public WaveShaperNode { return WaveShaperNode::processNode(processingBuffer, framesToProcess); } - std::shared_ptr testCurve_; + std::shared_ptr testCurve_; }; TEST_F(WaveShaperNodeTest, WaveShaperNodeCanBeCreated) { @@ -52,7 +52,6 @@ TEST_F(WaveShaperNodeTest, WaveShaperNodeCanBeCreated) { TEST_F(WaveShaperNodeTest, NullCanBeAsignedToCurve) { auto waveShaper = context->createWaveShaper(WaveShaperOptions()); ASSERT_NO_THROW(waveShaper->setCurve(nullptr)); - ASSERT_EQ(waveShaper->getCurve(), nullptr); } TEST_F(WaveShaperNodeTest, NoneOverSamplingProcessesCorrectly) { diff --git a/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.cpp index fc8d006b1..e4953169d 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.cpp @@ -7,77 +7,61 @@ namespace audioapi { void BiquadFilterTest::expectCoefficientsNear( - const BiquadFilterNode &biquadNode, + const BiquadFilterNode::FilterCoefficients &actual, const BiquadCoefficients &expected) { - EXPECT_NEAR(biquadNode.b0_, expected.b0, tolerance); - EXPECT_NEAR(biquadNode.b1_, expected.b1, tolerance); - EXPECT_NEAR(biquadNode.b2_, expected.b2, tolerance); - EXPECT_NEAR(biquadNode.a1_, expected.a1, tolerance); - EXPECT_NEAR(biquadNode.a2_, expected.a2, tolerance); + EXPECT_NEAR(actual.b0, expected.b0, tolerance); + EXPECT_NEAR(actual.b1, expected.b1, tolerance); + EXPECT_NEAR(actual.b2, expected.b2, tolerance); + EXPECT_NEAR(actual.a1, expected.a1, tolerance); + EXPECT_NEAR(actual.a2, expected.a2, tolerance); } void BiquadFilterTest::testLowpass(float frequency, float Q) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setLowpassCoefficients(normalizedFrequency, Q); - expectCoefficientsNear(node, calculateLowpassCoefficients(normalizedFrequency, Q)); + auto coeffs = BiquadFilterNode::getLowpassCoefficients(normalizedFrequency, Q); + expectCoefficientsNear(coeffs, calculateLowpassCoefficients(normalizedFrequency, Q)); } void BiquadFilterTest::testHighpass(float frequency, float Q) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setHighpassCoefficients(normalizedFrequency, Q); - expectCoefficientsNear(node, calculateHighpassCoefficients(normalizedFrequency, Q)); + auto coeffs = BiquadFilterNode::getHighpassCoefficients(normalizedFrequency, Q); + expectCoefficientsNear(coeffs, calculateHighpassCoefficients(normalizedFrequency, Q)); } void BiquadFilterTest::testBandpass(float frequency, float Q) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setBandpassCoefficients(normalizedFrequency, Q); - expectCoefficientsNear(node, calculateBandpassCoefficients(normalizedFrequency, Q)); + auto coeffs = BiquadFilterNode::getBandpassCoefficients(normalizedFrequency, Q); + expectCoefficientsNear(coeffs, calculateBandpassCoefficients(normalizedFrequency, Q)); } void BiquadFilterTest::testNotch(float frequency, float Q) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setNotchCoefficients(normalizedFrequency, Q); - expectCoefficientsNear(node, calculateNotchCoefficients(normalizedFrequency, Q)); + auto coeffs = BiquadFilterNode::getNotchCoefficients(normalizedFrequency, Q); + expectCoefficientsNear(coeffs, calculateNotchCoefficients(normalizedFrequency, Q)); } void BiquadFilterTest::testAllpass(float frequency, float Q) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setAllpassCoefficients(normalizedFrequency, Q); - expectCoefficientsNear(node, calculateAllpassCoefficients(normalizedFrequency, Q)); + auto coeffs = BiquadFilterNode::getAllpassCoefficients(normalizedFrequency, Q); + expectCoefficientsNear(coeffs, calculateAllpassCoefficients(normalizedFrequency, Q)); } void BiquadFilterTest::testPeaking(float frequency, float Q, float gain) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setPeakingCoefficients(normalizedFrequency, Q, gain); - expectCoefficientsNear(node, calculatePeakingCoefficients(normalizedFrequency, Q, gain)); + auto coeffs = BiquadFilterNode::getPeakingCoefficients(normalizedFrequency, Q, gain); + expectCoefficientsNear(coeffs, calculatePeakingCoefficients(normalizedFrequency, Q, gain)); } void BiquadFilterTest::testLowshelf(float frequency, float gain) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setLowshelfCoefficients(normalizedFrequency, gain); - expectCoefficientsNear(node, calculateLowshelfCoefficients(normalizedFrequency, gain)); + auto coeffs = BiquadFilterNode::getLowshelfCoefficients(normalizedFrequency, gain); + expectCoefficientsNear(coeffs, calculateLowshelfCoefficients(normalizedFrequency, gain)); } void BiquadFilterTest::testHighshelf(float frequency, float gain) { - auto node = BiquadFilterNode(context, BiquadFilterOptions()); float normalizedFrequency = frequency / nyquistFrequency; - - node.setHighshelfCoefficients(normalizedFrequency, gain); - expectCoefficientsNear(node, calculateHighshelfCoefficients(normalizedFrequency, gain)); + auto coeffs = BiquadFilterNode::getHighshelfCoefficients(normalizedFrequency, gain); + expectCoefficientsNear(coeffs, calculateHighshelfCoefficients(normalizedFrequency, gain)); } INSTANTIATE_TEST_SUITE_P( @@ -113,106 +97,106 @@ INSTANTIATE_TEST_SUITE_P( 0.0f, // default 40.0f)); -TEST_P(BiquadFilterFrequencyTest, SetLowpassCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestLowpassCoefficients) { float frequency = GetParam(); float Q = 1.0f; testLowpass(frequency, Q); } -TEST_P(BiquadFilterFrequencyTest, SetHighpassCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestHighpassCoefficients) { float frequency = GetParam(); float Q = 1.0f; testHighpass(frequency, Q); } -TEST_P(BiquadFilterFrequencyTest, SetBandpassCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestBandpassCoefficients) { float frequency = GetParam(); float Q = 1.0f; testBandpass(frequency, Q); } -TEST_P(BiquadFilterFrequencyTest, SetNotchCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestNotchCoefficients) { float frequency = GetParam(); float Q = 1.0f; testNotch(frequency, Q); } -TEST_P(BiquadFilterFrequencyTest, SetAllpassCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestAllpassCoefficients) { float frequency = GetParam(); float Q = 1.0f; testAllpass(frequency, Q); } -TEST_P(BiquadFilterFrequencyTest, SetPeakingCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestPeakingCoefficients) { float frequency = GetParam(); float Q = 1.0f; float gain = 2.0f; testPeaking(frequency, Q, gain); } -TEST_P(BiquadFilterFrequencyTest, SetLowshelfCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestLowshelfCoefficients) { float frequency = GetParam(); float gain = 2.0f; testLowshelf(frequency, gain); } -TEST_P(BiquadFilterFrequencyTest, SetHighshelfCoefficients) { +TEST_P(BiquadFilterFrequencyTest, TestHighshelfCoefficients) { float frequency = GetParam(); float gain = 2.0f; testHighshelf(frequency, gain); } -TEST_P(BiquadFilterQTestLowpassHighpass, SetLowpassCoefficients) { +TEST_P(BiquadFilterQTestLowpassHighpass, TestLowpassCoefficients) { float frequency = 1000.0f; float Q = GetParam(); testLowpass(frequency, Q); } -TEST_P(BiquadFilterQTestLowpassHighpass, SetHighpassCoefficients) { +TEST_P(BiquadFilterQTestLowpassHighpass, TestHighpassCoefficients) { float frequency = 1000.0f; float Q = GetParam(); testHighpass(frequency, Q); } -TEST_P(BiquadFilterQTestRestTypes, SetBandpassCoefficients) { +TEST_P(BiquadFilterQTestRestTypes, TestBandpassCoefficients) { float frequency = 1000.0f; float Q = GetParam(); testBandpass(frequency, Q); } -TEST_P(BiquadFilterQTestRestTypes, SetNotchCoefficients) { +TEST_P(BiquadFilterQTestRestTypes, TestNotchCoefficients) { float frequency = 1000.0f; float Q = GetParam(); testNotch(frequency, Q); } -TEST_P(BiquadFilterQTestRestTypes, SetAllpassCoefficients) { +TEST_P(BiquadFilterQTestRestTypes, TestAllpassCoefficients) { float frequency = 1000.0f; float Q = GetParam(); testAllpass(frequency, Q); } -TEST_P(BiquadFilterQTestRestTypes, SetPeakingCoefficients) { +TEST_P(BiquadFilterQTestRestTypes, TestPeakingCoefficients) { float frequency = 1000.0f; float Q = GetParam(); float gain = 2.0f; testPeaking(frequency, Q, gain); } -TEST_P(BiquadFilterGainTest, SetPeakingCoefficients) { +TEST_P(BiquadFilterGainTest, TestPeakingCoefficients) { float frequency = 1000.0f; float Q = 1.0f; float gain = GetParam(); testPeaking(frequency, Q, gain); } -TEST_P(BiquadFilterGainTest, SetLowshelfCoefficients) { +TEST_P(BiquadFilterGainTest, TestLowshelfCoefficients) { float frequency = 1000.0f; float gain = GetParam(); testLowshelf(frequency, gain); } -TEST_P(BiquadFilterGainTest, SetHighshelfCoefficients) { +TEST_P(BiquadFilterGainTest, TestHighshelfCoefficients) { float frequency = 1000.0f; float gain = GetParam(); testHighshelf(frequency, gain); @@ -225,7 +209,8 @@ TEST_F(BiquadFilterTest, GetFrequencyResponse) { float Q = 1.0f; float normalizedFrequency = frequency / nyquistFrequency; - node.setLowpassCoefficients(normalizedFrequency, Q); + node.frequencyParam_->setValue(frequency); + node.QParam_->setValue(Q); auto coeffs = calculateLowpassCoefficients(normalizedFrequency, Q); std::vector TestFrequencies = { @@ -248,7 +233,8 @@ TEST_F(BiquadFilterTest, GetFrequencyResponse) { TestFrequencies.data(), magResponseNode.data(), phaseResponseNode.data(), - TestFrequencies.size()); + TestFrequencies.size(), + BiquadFilterType::LOWPASS); getFrequencyResponse( coeffs, TestFrequencies, magResponseExpected, phaseResponseExpected, nyquistFrequency); diff --git a/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.h b/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.h index b47d00974..17fe99703 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.h +++ b/packages/react-native-audio-api/common/cpp/test/src/core/effects/biquad/BiquadFilterTest.h @@ -23,7 +23,9 @@ class BiquadFilterTest : public ::testing::Test { 2, 5 * sampleRate, sampleRate, eventRegistry, RuntimeRegistry{}); } - void expectCoefficientsNear(const BiquadFilterNode &node, const BiquadCoefficients &expected); + void expectCoefficientsNear( + const BiquadFilterNode::FilterCoefficients &actual, + const BiquadCoefficients &expected); void testLowpass(float frequency, float Q); void testHighpass(float frequency, float Q); void testBandpass(float frequency, float Q); diff --git a/packages/react-native-audio-api/common/cpp/test/src/core/sources/AudioScheduledSourceTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/core/sources/AudioScheduledSourceTest.cpp index f8ce75501..ed0c9beb5 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/core/sources/AudioScheduledSourceTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/core/sources/AudioScheduledSourceTest.cpp @@ -29,7 +29,7 @@ class TestableAudioScheduledSourceNode : public AudioScheduledSourceNode { public: explicit TestableAudioScheduledSourceNode(std::shared_ptr context) : AudioScheduledSourceNode(context) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } void updatePlaybackInfo( diff --git a/packages/react-native-audio-api/common/cpp/test/src/utils/TripleBufferTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/utils/TripleBufferTest.cpp new file mode 100644 index 000000000..339fad378 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/test/src/utils/TripleBufferTest.cpp @@ -0,0 +1,267 @@ +#include +#include + +#include +#include +#include + +using namespace audioapi; + +struct IntVal { + int value{0}; + explicit IntVal(int v = 0) : value(v) {} +}; + +// --------------------------------------------------------------------------- +// Functional Tests +// --------------------------------------------------------------------------- + +class TripleBufferTest : public ::testing::Test {}; + +TEST_F(TripleBufferTest, GetForWriterReturnsNonNull) { + TripleBuffer buf; + EXPECT_NE(buf.getForWriter(), nullptr); +} + +TEST_F(TripleBufferTest, GetForReaderReturnsNonNull) { + TripleBuffer buf; + EXPECT_NE(buf.getForReader(), nullptr); +} + +TEST_F(TripleBufferTest, ConstructorForwardsArguments) { + TripleBuffer buf(42); + EXPECT_EQ(buf.getForWriter()->value, 42); + EXPECT_EQ(buf.getForReader()->value, 42); +} + +TEST_F(TripleBufferTest, ReaderAndWriterPointToDifferentBuffers) { + TripleBuffer buf; + EXPECT_NE(buf.getForWriter(), buf.getForReader()); +} + +TEST_F(TripleBufferTest, PublishMakesWrittenDataVisibleToReader) { + TripleBuffer buf; + + buf.getForWriter()->value = 99; + buf.publish(); + + EXPECT_EQ(buf.getForReader()->value, 99); +} + +TEST_F(TripleBufferTest, ReaderReturnsSameBufferWithoutPublish) { + TripleBuffer buf; + + auto *first = buf.getForReader(); + auto *second = buf.getForReader(); + EXPECT_EQ(first, second); +} + +TEST_F(TripleBufferTest, MultiplePublishesWithoutReadShowLatestValue) { + TripleBuffer buf; + + for (int i = 1; i <= 5; ++i) { + buf.getForWriter()->value = i; + buf.publish(); + } + + EXPECT_EQ(buf.getForReader()->value, 5); +} + +TEST_F(TripleBufferTest, ReadClearsUpdateFlag) { + TripleBuffer buf; + + buf.getForWriter()->value = 42; + buf.publish(); + + auto *first = buf.getForReader(); + auto *second = buf.getForReader(); + + EXPECT_EQ(first, second); +} + +TEST_F(TripleBufferTest, WriterPointerIsStableBeforePublish) { + TripleBuffer buf; + + auto *w1 = buf.getForWriter(); + auto *w2 = buf.getForWriter(); + EXPECT_EQ(w1, w2); +} + +TEST_F(TripleBufferTest, WriterBufferChangesAfterPublish) { + TripleBuffer buf; + + auto *before = buf.getForWriter(); + buf.publish(); + auto *after = buf.getForWriter(); + + EXPECT_NE(before, after); +} + +TEST_F(TripleBufferTest, AlternatingWriteReadPreservesData) { + TripleBuffer buf; + + for (int i = 0; i < 10; ++i) { + buf.getForWriter()->value = i; + buf.publish(); + EXPECT_EQ(buf.getForReader()->value, i); + } +} + +TEST_F(TripleBufferTest, ReaderWithNoUpdateSeesInitialValue) { + TripleBuffer buf(7); + EXPECT_EQ(buf.getForReader()->value, 7); +} + +TEST_F(TripleBufferTest, DestructorCalledExactlyThreeTimes) { + struct Tracker { + std::atomic *count; + explicit Tracker(std::atomic *c) : count(c) {} + ~Tracker() { + ++(*count); + } + }; + + std::atomic count{0}; + { TripleBuffer buf(&count); } + EXPECT_EQ(count.load(), 3); +} + +TEST_F(TripleBufferTest, WriteThenReadSequenceIsConsistent) { + TripleBuffer buf; + + buf.getForWriter()->value = 10; + buf.publish(); + EXPECT_EQ(buf.getForReader()->value, 10); + + buf.getForWriter()->value = 20; + buf.publish(); + EXPECT_EQ(buf.getForReader()->value, 20); + + buf.getForWriter()->value = 30; + buf.publish(); + EXPECT_EQ(buf.getForReader()->value, 30); +} + +TEST_F(TripleBufferTest, ReaderPointerAfterReadIsDistinctFromWriter) { + TripleBuffer buf; + + buf.getForWriter()->value = 1; + buf.publish(); + + auto *reader = buf.getForReader(); + auto *writer = buf.getForWriter(); + EXPECT_NE(reader, writer); +} + +TEST_F(TripleBufferTest, AllThreeBufferAddressesAreDistinct) { + TripleBuffer buf; + + // Initial state: front=0, idle=1, back=2 + auto *front = buf.getForReader(); // buffers_[0] + auto *back = buf.getForWriter(); // buffers_[2] + + // publish: back (2) goes to state, old idle (1) becomes new back + buf.getForWriter()->value = 1; + buf.publish(); + + // consume the update: reader gets old back (2) as new front + buf.getForReader(); + + // after consume: front=2, state=0, back=1 — writer is now on the idle buffer + auto *idle = buf.getForWriter(); // buffers_[1] + + EXPECT_NE(front, back); + EXPECT_NE(front, idle); + EXPECT_NE(back, idle); +} + +// --------------------------------------------------------------------------- +// Stress Tests +// --------------------------------------------------------------------------- + +TEST_F(TripleBufferTest, StressReaderValueNeverGoesBackward) { + // Writer publishes strictly increasing values; reader must never see a + // decrease (the triple buffer may skip values but never go backwards). + TripleBuffer buf; + std::atomic stop{false}; + std::atomic error{false}; + + std::thread writer([&] { + for (int i = 1; !stop.load(std::memory_order_relaxed); ++i) { + buf.getForWriter()->value = i; + buf.publish(); + } + }); + + std::thread reader([&] { + int lastSeen = 0; + while (!stop.load(std::memory_order_relaxed)) { + int val = buf.getForReader()->value; + if (val < lastSeen) { + error.store(true, std::memory_order_relaxed); + } + lastSeen = val; + } + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + stop.store(true); + writer.join(); + reader.join(); + + EXPECT_FALSE(error.load()); +} + +TEST_F(TripleBufferTest, StressWriterNeverBlocks) { + TripleBuffer buf; + + constexpr int N = 1'000'000; + for (int i = 0; i < N; ++i) { + buf.getForWriter()->value = i; + buf.publish(); + } + + SUCCEED(); +} + +TEST_F(TripleBufferTest, StressReaderNeverBlocks) { + TripleBuffer buf; + + constexpr int N = 1'000'000; + for (int i = 0; i < N; ++i) { + (void)buf.getForReader()->value; + } + + SUCCEED(); +} + +TEST_F(TripleBufferTest, StressBothSidesMakeProgress) { + // Verify that neither thread starves the other. + TripleBuffer buf; + std::atomic stop{false}; + std::atomic writeCount{0}; + std::atomic readCount{0}; + + std::thread writer([&] { + for (int i = 1; !stop.load(std::memory_order_relaxed); ++i) { + buf.getForWriter()->value = i; + buf.publish(); + writeCount.fetch_add(1, std::memory_order_relaxed); + } + }); + + std::thread reader([&] { + while (!stop.load(std::memory_order_relaxed)) { + (void)buf.getForReader()->value; + readCount.fetch_add(1, std::memory_order_relaxed); + } + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + stop.store(true); + writer.join(); + reader.join(); + + EXPECT_GT(writeCount.load(), 1'000); + EXPECT_GT(readCount.load(), 1'000); +} diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm index d6fb6e7a2..f23ec9d63 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm @@ -50,7 +50,7 @@ if (isConnected()) { if (auto lock = Locker::tryLock(adapterNodeMutex_)) { - for (size_t channel = 0; channel < adapterNode_->channelCount_; ++channel) { + for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) { auto data = (float *)inputBuffer->mBuffers[channel].mData; adapterNode_->buff_[channel]->write(data, numFrames); diff --git a/packages/react-native-audio-api/src/api.web.ts b/packages/react-native-audio-api/src/api.web.ts index 6452b4b58..a10fa3591 100644 --- a/packages/react-native-audio-api/src/api.web.ts +++ b/packages/react-native-audio-api/src/api.web.ts @@ -25,7 +25,6 @@ export { ChannelCountMode, ChannelInterpretation, ContextState, - WindowType, } from './types'; export { diff --git a/packages/react-native-audio-api/src/core/AnalyserNode.ts b/packages/react-native-audio-api/src/core/AnalyserNode.ts index f8f0558e9..9f9a2e728 100644 --- a/packages/react-native-audio-api/src/core/AnalyserNode.ts +++ b/packages/react-native-audio-api/src/core/AnalyserNode.ts @@ -1,7 +1,7 @@ import BaseAudioContext from './BaseAudioContext'; import { IndexSizeError } from '../errors'; import { IAnalyserNode } from '../interfaces'; -import { WindowType, AnalyserOptions } from '../types'; +import { AnalyserOptions } from '../types'; import AudioNode from './AudioNode'; import { AnalyserOptionsValidator } from '../options-validators'; @@ -74,16 +74,8 @@ export default class AnalyserNode extends AudioNode { (this.node as IAnalyserNode).smoothingTimeConstant = value; } - public get window(): WindowType { - return (this.node as IAnalyserNode).window; - } - - public set window(value: WindowType) { - (this.node as IAnalyserNode).window = value; - } - public get frequencyBinCount(): number { - return (this.node as IAnalyserNode).frequencyBinCount; + return Math.floor((this.node as IAnalyserNode).fftSize / 2); } public getFloatFrequencyData(array: Float32Array): void { diff --git a/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts index e81e4768c..ea01fc288 100644 --- a/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts +++ b/packages/react-native-audio-api/src/core/AudioBufferQueueSourceNode.ts @@ -39,7 +39,7 @@ export default class AudioBufferQueueSourceNode extends AudioBufferBaseSourceNod (this.node as IAudioBufferQueueSourceNode).clearBuffers(); } - public override start(when: number = 0, offset?: number): void { + public override start(when: number = 0, offset: number = -1): void { if (when < 0) { throw new RangeError( `when must be a finite non-negative number: ${when}` diff --git a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts index e00175ccd..f75549f5b 100644 --- a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts +++ b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts @@ -11,29 +11,42 @@ export default class AudioBufferSourceNode extends AudioBufferBaseSourceNode { private onLoopEndedSubscription?: AudioEventSubscription; private onLoopEndedCallback?: (event: EventEmptyType) => void; + private _buffer: AudioBuffer | null = null; + private bufferHasBeenSet: boolean = false; + constructor(context: BaseAudioContext, options?: AudioBufferSourceOptions) { - const node = context.context.createBufferSource({ - ...options, - ...(options?.buffer ? { buffer: options.buffer.buffer } : {}), - }); + const node = context.context.createBufferSource(options || {}); super(context, node); + + if (options?.buffer) { + this._buffer = options.buffer; + this.bufferHasBeenSet = true; + } } public get buffer(): AudioBuffer | null { - const buffer = (this.node as IAudioBufferSourceNode).buffer; - if (!buffer) { - return null; - } - return new AudioBuffer(buffer); + return this._buffer; } public set buffer(buffer: AudioBuffer | null) { - if (!buffer) { - (this.node as IAudioBufferSourceNode).setBuffer(null); + if (buffer === null) { + if (this.buffer !== null) { + (this.node as IAudioBufferSourceNode).setBuffer(null); + this._buffer = null; + } + return; } + if (this.bufferHasBeenSet) { + throw new InvalidStateError( + 'The buffer can only be set once and cannot be changed afterwards.' + ); + } + (this.node as IAudioBufferSourceNode).setBuffer(buffer.buffer); + this._buffer = buffer; + this.bufferHasBeenSet = true; } public get loopSkip(): boolean { diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 452dcbe85..55d354e4e 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -130,8 +130,8 @@ export default class BaseAudioContext { return new OscillatorNode(this); } - createStreamer(): StreamerNode { - return new StreamerNode(this); + createStreamer(streamPath: string): StreamerNode { + return new StreamerNode(this, { streamPath }); } createConstantSource(): ConstantSourceNode { diff --git a/packages/react-native-audio-api/src/core/ConvolverNode.ts b/packages/react-native-audio-api/src/core/ConvolverNode.ts index c79f7e57d..c1f03beb1 100644 --- a/packages/react-native-audio-api/src/core/ConvolverNode.ts +++ b/packages/react-native-audio-api/src/core/ConvolverNode.ts @@ -5,29 +5,32 @@ import AudioNode from './AudioNode'; import AudioBuffer from './AudioBuffer'; export default class ConvolverNode extends AudioNode { + private _buffer: AudioBuffer | null = null; + constructor(context: BaseAudioContext, options?: ConvolverOptions) { - const convolverNode: IConvolverNode = context.context.createConvolver({ - ...options, - ...(options?.buffer ? { buffer: options.buffer.buffer } : {}), - }); + const convolverNode: IConvolverNode = context.context.createConvolver( + options || {} + ); super(context, convolverNode); + + if (options?.buffer) { + this.buffer = options.buffer; + } this.normalize = convolverNode.normalize; } public get buffer(): AudioBuffer | null { - const buffer = (this.node as IConvolverNode).buffer; - if (!buffer) { - return null; - } - return new AudioBuffer(buffer); + return this._buffer; } public set buffer(buffer: AudioBuffer | null) { if (!buffer) { (this.node as IConvolverNode).setBuffer(null); + this._buffer = null; return; } (this.node as IConvolverNode).setBuffer(buffer.buffer); + this._buffer = buffer; } public get normalize(): boolean { diff --git a/packages/react-native-audio-api/src/core/StreamerNode.ts b/packages/react-native-audio-api/src/core/StreamerNode.ts index adf58c4ed..31246b642 100644 --- a/packages/react-native-audio-api/src/core/StreamerNode.ts +++ b/packages/react-native-audio-api/src/core/StreamerNode.ts @@ -1,36 +1,17 @@ -import { IStreamerNode } from '../interfaces'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import { StreamerOptions } from '../types'; -import { InvalidStateError, NotSupportedError } from '../errors'; +import { NotSupportedError } from '../errors'; import BaseAudioContext from './BaseAudioContext'; export default class StreamerNode extends AudioScheduledSourceNode { - private hasBeenSetup: boolean = false; - constructor(context: BaseAudioContext, options?: StreamerOptions) { - const node = context.context.createStreamer(options || {}); + readonly streamPath: string; + + constructor(context: BaseAudioContext, options: StreamerOptions) { + const node = context.context.createStreamer(options); if (!node) { throw new NotSupportedError('StreamerNode requires FFmpeg build'); } super(context, node); - if (options?.streamPath) { - if (this.initialize(options.streamPath)) { - this.hasBeenSetup = true; - } - } - } - - public initialize(streamPath: string): boolean { - 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; + this.streamPath = options.streamPath; } } diff --git a/packages/react-native-audio-api/src/core/WaveShaperNode.ts b/packages/react-native-audio-api/src/core/WaveShaperNode.ts index 3de2f5c41..79af8c4b7 100644 --- a/packages/react-native-audio-api/src/core/WaveShaperNode.ts +++ b/packages/react-native-audio-api/src/core/WaveShaperNode.ts @@ -6,21 +6,19 @@ import { WaveShaperOptions } from '../types'; export default class WaveShaperNode extends AudioNode { private isCurveSet: boolean = false; + private _curve: Float32Array | null = null; constructor(context: BaseAudioContext, options?: WaveShaperOptions) { const node = context.context.createWaveShaper(options || {}); super(context, node); if (options?.curve) { + this._curve = options.curve; this.isCurveSet = true; } } get curve(): Float32Array | null { - if (!this.isCurveSet) { - return null; - } - - return (this.node as IWaveShaperNode).curve; + return this._curve; } get oversample(): OverSampleType { diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index b4f1b431e..bc3ff0481 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -23,7 +23,6 @@ import type { StereoPannerOptions, StreamerOptions, WaveShaperOptions, - WindowType, } from './types'; // IMPORTANT: use only IClass, because it is a part of contract between cpp host object and js layer @@ -101,7 +100,7 @@ export interface IBaseAudioContext { ) => IPeriodicWave; createAnalyser: (analyserOptions: AnalyserOptions) => IAnalyserNode; createConvolver: (convolverOptions?: IConvolverOptions) => IConvolverNode; - createStreamer: (streamerOptions?: StreamerOptions) => IStreamerNode | null; // null when FFmpeg is not enabled + createStreamer: (streamerOptions: StreamerOptions) => IStreamerNode | null; // null when FFmpeg is not enabled createWaveShaper: (waveShaperOptions?: WaveShaperOptions) => IWaveShaperNode; } @@ -195,10 +194,7 @@ export interface IOscillatorNode extends IAudioScheduledSourceNode { setPeriodicWave(periodicWave: IPeriodicWave): void; } -export interface IStreamerNode extends IAudioNode { - readonly streamPath: string; - initialize(streamPath: string): boolean; -} +export interface IStreamerNode extends IAudioNode {} export interface IConstantSourceNode extends IAudioScheduledSourceNode { readonly offset: IAudioParam; @@ -289,7 +285,6 @@ export interface IAnalyserNode extends IAudioNode { minDecibels: number; maxDecibels: number; smoothingTimeConstant: number; - window: WindowType; getFloatFrequencyData: (array: Float32Array) => void; getByteFrequencyData: (array: Uint8Array) => void; diff --git a/packages/react-native-audio-api/src/mock/index.ts b/packages/react-native-audio-api/src/mock/index.ts index dcfcfa344..c3d6d09d5 100644 --- a/packages/react-native-audio-api/src/mock/index.ts +++ b/packages/react-native-audio-api/src/mock/index.ts @@ -458,27 +458,11 @@ class AudioBufferQueueSourceNodeMock extends AudioScheduledSourceNodeMock { } class StreamerNodeMock extends AudioScheduledSourceNodeMock { - private hasBeenSetup: boolean = false; - private _streamPath: string = ''; + readonly streamPath: string = ''; - constructor(context: BaseAudioContextMock, options?: StreamerOptions) { - super(context, {}); - if (options?.streamPath) { - this.initialize(options.streamPath); - } - } - - initialize(streamPath: string): boolean { - if (this.hasBeenSetup) { - throw new Error('Node is already setup'); - } - this._streamPath = streamPath; - this.hasBeenSetup = true; - return true; - } - - get streamPath(): string { - return this._streamPath; + constructor(context: BaseAudioContextMock, options: StreamerOptions) { + super(context, options); + this.streamPath = options.streamPath; } pause(): void {} @@ -653,7 +637,7 @@ class BaseAudioContextMock { return new AudioBufferQueueSourceNodeMock(this, options); } - createStreamer(options?: StreamerOptions): StreamerNodeMock { + createStreamer(options: StreamerOptions): StreamerNodeMock { return new StreamerNodeMock(this, options); } diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 5e933f2be..67450bf0b 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -106,8 +106,6 @@ export interface FileInfo { duration: number; } -export type WindowType = 'blackman' | 'hann'; - export type ProcessorMode = 'processInPlace' | 'processThrough'; export interface AudioNodeOptions { @@ -188,7 +186,7 @@ export interface ConstantSourceOptions { } export interface StreamerOptions { - streamPath?: string; + streamPath: string; } export interface PeriodicWaveConstraints { 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 44da78800..3cdbf1325 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, AnalyserOptions } from '../types'; +import { AnalyserOptions } from '../types'; import BaseAudioContext from './BaseAudioContext'; export default class AnalyserNode extends AudioNode { @@ -20,16 +20,6 @@ export default class AnalyserNode extends AudioNode { this.smoothingTimeConstant = node.smoothingTimeConstant; } - public get window(): WindowType { - return 'blackman'; - } - - public set window(value: WindowType) { - console.log( - 'React Native Audio API: setting window is not supported on web' - ); - } - public getByteFrequencyData(array: Uint8Array): void { (this.node as globalThis.AnalyserNode).getByteFrequencyData( array as Uint8Array diff --git a/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx b/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx index b921c211c..83057ca18 100644 --- a/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx @@ -212,6 +212,7 @@ class AudioBufferSourceNodeStretcher implements IAudioAPIBufferSourceNodeWeb { private _loopEnd: number = -1; private _buffer: AudioBuffer | null = null; + private bufferHasBeenSet: boolean = false; constructor(context: BaseAudioContext) { const promise = async () => { @@ -421,7 +422,16 @@ class AudioBufferSourceNodeStretcher implements IAudioAPIBufferSourceNodeWeb { } set buffer(buffer: AudioBuffer | null) { + if (buffer !== null && this.bufferHasBeenSet) { + throw new InvalidStateError( + 'The buffer can only be set once and cannot be changed afterwards.' + ); + } + this._buffer = buffer; + if (buffer !== null) { + this.bufferHasBeenSet = true; + } const action = (node: IStretcherNode) => { node.dropBuffers();