diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp index 60475b89b..bf630f895 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp @@ -55,7 +55,7 @@ AndroidAudioRecorder::~AndroidAudioRecorder() { } } - if (mStream_) { + if (mStream_ != nullptr) { mStream_->requestStop(); mStream_->close(); mStream_.reset(); @@ -68,7 +68,7 @@ AndroidAudioRecorder::~AndroidAudioRecorder() { /// Callable from the JS thread only. /// @returns Success status or Error status with message. Result AndroidAudioRecorder::openAudioStream() { - if (mStream_) { + if (mStream_ != nullptr) { return Result::Ok(None); } @@ -404,7 +404,7 @@ bool AndroidAudioRecorder::isIdle() const { void AndroidAudioRecorder::cleanup() { state_.store(RecorderState::Idle, std::memory_order_release); - if (mStream_) { + if (mStream_ != nullptr) { mStream_->close(); mStream_.reset(); } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp index c09c17f81..9e9f8638b 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp @@ -15,7 +15,10 @@ AudioPlayer::AudioPlayer( const std::function, int)> &renderAudio, float sampleRate, int channelCount) - : renderAudio_(renderAudio), sampleRate_(sampleRate), channelCount_(channelCount) { + : renderAudio_(renderAudio), + sampleRate_(sampleRate), + channelCount_(channelCount), + isRunning_(false) { isInitialized_ = openAudioStream(); } @@ -39,36 +42,44 @@ bool AudioPlayer::openAudioStream() { return false; } - mBus_ = std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, sampleRate_); + audioBus_ = std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, sampleRate_); return true; } bool AudioPlayer::start() { - if (mStream_) { - auto result = mStream_->requestStart(); - return result == oboe::Result::OK; + if (mStream_ != nullptr) { + auto result = mStream_->requestStart() == oboe::Result::OK; + isRunning_.store(result, std::memory_order_release); + return result; } return false; } void AudioPlayer::stop() { - if (mStream_) { + if (mStream_ != nullptr) { + isRunning_.store(false, std::memory_order_release); mStream_->requestStop(); } } bool AudioPlayer::resume() { - if (mStream_) { - auto result = mStream_->requestStart(); - return result == oboe::Result::OK; + if (isRunning()) { + return true; + } + + if (mStream_ != nullptr) { + auto result = mStream_->requestStart() == oboe::Result::OK; + isRunning_.store(result, std::memory_order_release); + return result; } return false; } void AudioPlayer::suspend() { - if (mStream_) { + if (mStream_ != nullptr) { + isRunning_.store(false, std::memory_order_release); mStream_->requestPause(); } } @@ -76,14 +87,15 @@ void AudioPlayer::suspend() { void AudioPlayer::cleanup() { isInitialized_ = false; - if (mStream_) { + if (mStream_ != nullptr) { mStream_->close(); mStream_.reset(); } } bool AudioPlayer::isRunning() const { - return mStream_ && mStream_->getState() == oboe::StreamState::Started; + return mStream_ && mStream_->getState() == oboe::StreamState::Started && + isRunning_.load(std::memory_order_acquire); } DataCallbackResult @@ -95,17 +107,19 @@ AudioPlayer::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numF auto buffer = static_cast(audioData); int processedFrames = 0; - assert(buffer != nullptr); - while (processedFrames < numFrames) { int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE); - renderAudio_(mBus_, framesToProcess); - // TODO: optimize this with SIMD? + if (isRunning_.load(std::memory_order_acquire)) { + renderAudio_(audioBus_, framesToProcess); + } else { + audioBus_->zero(); + } + for (int i = 0; i < framesToProcess; i++) { - for (int channel = 0; channel < channelCount_; channel += 1) { + for (int channel = 0; channel < channelCount_; channel++) { buffer[(processedFrames + i) * channelCount_ + channel] = - mBus_->getChannel(channel)->getData()[i]; + audioBus_->getChannel(channel)->getData()[i]; } } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h index 5283a4316..c93c7ebe7 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -45,10 +44,11 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { private: std::function, int)> renderAudio_; std::shared_ptr mStream_; - std::shared_ptr mBus_; + std::shared_ptr audioBus_; bool isInitialized_ = false; float sampleRate_; int channelCount_; + std::atomic isRunning_; bool openAudioStream(); 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 08a3babda..121c74048 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 @@ -1,12 +1,19 @@ #include - #include #include +#include + #include namespace audioapi { -AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr &node) : node_(node) { +AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr &node) : + node_(node), + numberOfInputs_(node->getNumberOfInputs()), + numberOfOutputs_(node->getNumberOfOutputs()), + channelCount_(node->getChannelCount()), + channelCountMode_(node->getChannelCountMode()), + channelInterpretation_(node->getChannelInterpretation()) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfInputs), JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfOutputs), @@ -26,23 +33,23 @@ AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr &node) 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 {node_->getChannelCount()}; + return channelCount_; } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCountMode) { - return jsi::String::createFromUtf8(runtime, node_->getChannelCountMode()); + return jsi::String::createFromUtf8(runtime, js_enum_parser::channelCountModeToString(channelCountMode_)); } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelInterpretation) { - return jsi::String::createFromUtf8(runtime, node_->getChannelInterpretation()); + return jsi::String::createFromUtf8(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 dfea4ab46..bef509158 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 @@ -27,5 +29,11 @@ class AudioNodeHostObject : public JsiHostObject { protected: std::shared_ptr node_; + + int numberOfInputs_; + int numberOfOutputs_; + int channelCount_; + ChannelCountMode channelCountMode_; + 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 45508e702..39bd8d01f 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 @@ -7,7 +7,7 @@ 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), @@ -31,47 +31,58 @@ 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(); } @@ -80,24 +91,32 @@ 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, rawValues + length); + auto values = std::make_shared>(rawValues, 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); - return jsi::Value::undefined(); + auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) { + param->cancelAndHoldAtTime(cancelTime); + }; + + param_->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } } // namespace audioapi 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 8afbfaa10..363ec7bcc 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 @@ -36,5 +36,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/BaseAudioContextHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp index 3f226b550..e1c2d83c3 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/BaseAudioContextHostObject.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include #include #include 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 9035caca3..9001eea81 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 @@ -24,9 +24,7 @@ size_t DelayNodeHostObject::getSizeInBytes() const { } JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { - auto delayNode = std::static_pointer_cast(node_); - auto delayTimeParam = std::make_shared(delayNode->getDelayTimeParam()); - return jsi::Object::createFromHostObject(runtime, delayTimeParam); + return jsi::Object::createFromHostObject(runtime, delayTimeParam_); } } // 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 903e08803..b06806c4c 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 @@ -10,6 +10,7 @@ using namespace facebook; struct DelayOptions; class BaseAudioContext; +class AudioParamHostObject; class DelayNodeHostObject : public AudioNodeHostObject { public: @@ -20,5 +21,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 98c89de37..e4d553f74 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)) { + 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 577a84e2e..9503e292f 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 @@ -10,6 +10,7 @@ using namespace facebook; struct GainOptions; class BaseAudioContext; +class AudioParamHostObject; class GainNodeHostObject : public AudioNodeHostObject { public: @@ -18,5 +19,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/WorkletNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WorkletNodeHostObject.h similarity index 100% rename from packages/react-native-audio-api/common/cpp/audioapi/HostObjects/WorkletNodeHostObject.h rename to packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WorkletNodeHostObject.h diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/WorkletProcessingNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WorkletProcessingNodeHostObject.h similarity index 100% rename from packages/react-native-audio-api/common/cpp/audioapi/HostObjects/WorkletProcessingNodeHostObject.h rename to packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WorkletProcessingNodeHostObject.h 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 d7152ffa8..7f7558a0f 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,14 +1,20 @@ #include - #include #include + #include +#include namespace audioapi { AudioBufferBaseSourceNodeHostObject::AudioBufferBaseSourceNodeHostObject( const std::shared_ptr &node) - : AudioScheduledSourceNodeHostObject(node) { + : AudioScheduledSourceNodeHostObject(node), + detuneParam_(std::make_shared(node->getDetuneParam())), + playbackRateParam_(std::make_shared(node->getPlaybackRateParam())), + onPositionChangedInterval_(node->getOnPositionChangedInterval()), + inputLatency_(node->getInputLatency()), + outputLatency_(node->getOutputLatency()) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, detune), JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, playbackRate), @@ -24,55 +30,59 @@ 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_; +} + +void AudioBufferBaseSourceNodeHostObject::setOnPositionChangedCallbackId(uint64_t callbackId) { + auto sourceNode = std::static_pointer_cast(node_); + + auto event = [sourceNode, callbackId](BaseAudioContext &) { + sourceNode->setOnPositionChangedCallbackId(callbackId); + }; - return audioBufferBaseSourceNode->getOutputLatency(); + sourceNode->unregisterOnPositionChangedCallback(onPositionChangedCallbackId_); + sourceNode->scheduleAudioEvent(std::move(event)); + onPositionChangedCallbackId_ = callbackId; } } // 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 60ee74d90..a5c297620 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 @@ namespace audioapi { using namespace facebook; class AudioBufferBaseSourceNode; +class AudioParamHostObject; class AudioBufferBaseSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -26,6 +27,19 @@ 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; + + bool pitchCorrection_ = false; + double inputLatency_; + double outputLatency_; + + void setOnPositionChangedCallbackId(uint64_t callbackId); }; } // namespace audioapi 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 3705cb43b..0f70e9843 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 @@ -1,10 +1,12 @@ #include - #include #include #include #include + #include +#include +#include namespace audioapi { @@ -41,24 +43,33 @@ JSI_PROPERTY_SETTER_IMPL(AudioBufferQueueSourceNodeHostObject, onBufferEnded) { } 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(); - audioBufferQueueSourceNode->start(when, offset); - } - - return jsi::Value::undefined(); + auto event = [ + audioBufferQueueSourceNode, + when = args[0].getNumber(), + offset = args[1].isNumber() ? args[1].getNumber() : -1 + ](BaseAudioContext &) { + if (offset >= 0) { + audioBufferQueueSourceNode->start(when, offset); + } else { + audioBufferQueueSourceNode->start(when); + } + }; + + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); + + return jsi::Value::undefined(); } 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(); } @@ -66,20 +77,26 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, pause) { JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, enqueueBuffer) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - auto audioBufferHostObject = - args[0].getObject(runtime).asHostObject(runtime); + auto audioBufferHostObject = + args[0].getObject(runtime).asHostObject(runtime); - auto bufferId = audioBufferQueueSourceNode->enqueueBuffer(audioBufferHostObject->audioBuffer_); + auto event = [audioBufferQueueSourceNode, buffer = audioBufferHostObject->audioBuffer_] (BaseAudioContext &) { + audioBufferQueueSourceNode->enqueueBuffer(buffer); + }; - return jsi::String::createFromUtf8(runtime, bufferId); + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); + + 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()); + auto event = [audioBufferQueueSourceNode, bufferId = static_cast(args[0].getNumber())] (BaseAudioContext &) { + audioBufferQueueSourceNode->dequeueBuffer(bufferId); + }; - audioBufferQueueSourceNode->dequeueBuffer(bufferId); + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -87,7 +104,11 @@ 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(); } 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 6485a7c3a..e60ce496b 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 @@ -26,6 +26,9 @@ class AudioBufferQueueSourceNodeHostObject : public AudioBufferBaseSourceNodeHos JSI_HOST_FUNCTION_DECL(enqueueBuffer); JSI_HOST_FUNCTION_DECL(dequeueBuffer); JSI_HOST_FUNCTION_DECL(clearBuffers); + + private: + size_t bufferId_ = 0; }; } // 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 faadb9da8..8292788f1 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,17 +1,25 @@ #include - #include #include #include #include +#include + #include +#include namespace audioapi { AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject( const std::shared_ptr &context, const AudioBufferSourceOptions &options) - : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options)) { + : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options)), + loop_(options.loop), + loopStart_(options.loopStart), + loopEnd_(options.loopEnd), + buffer_(std::make_shared(options.buffer)) { + pitchCorrection_ = options.pitchCorrection; + addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loop), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopSkip), @@ -35,93 +43,104 @@ 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}; + return {loopSkip_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, buffer) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto buffer = audioBufferSourceNode->getBuffer(); - - if (!buffer) { + 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); + auto jsiObject = jsi::Object::createFromHostObject(runtime, buffer_); + jsiObject.setExternalMemoryPressure(runtime, buffer_->getSizeInBytes() + 16); return jsiObject; } 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 event = [ + audioBufferSourceNode, + when = args[0].getNumber(), + offset = args[1].getNumber(), + duration = args[2].isUndefined() ? -1 : args[2].getNumber() + ](BaseAudioContext &) { + audioBufferSourceNode->start(when, offset, duration); + }; - auto duration = args[2].getNumber(); - audioBufferSourceNode->start(when, offset, duration); + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -129,16 +148,63 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, start) { 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(); + auto bufferHostObject = args[0].isNull() ? std::shared_ptr(nullptr) : + args[0].getObject(runtime).asHostObject(runtime); + + + std::shared_ptr alignedBus = nullptr; + std::shared_ptr audioBus = nullptr; + std::shared_ptr playbackRateBus = nullptr; + + if (bufferHostObject != nullptr) { + thisValue.asObject(runtime).setExternalMemoryPressure( + runtime, bufferHostObject->getSizeInBytes() + 16); + + auto buffer = bufferHostObject->audioBuffer_; + + channelCount_ = buffer->getNumberOfChannels(); + audioBus = std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, 48000.0f); + playbackRateBus = std::make_shared(RENDER_QUANTUM_SIZE * 3, channelCount_, 48000.0f); + + if (pitchCorrection_) { + int extraTailFrames = + static_cast((inputLatency_ + outputLatency_) * buffer->getSampleRate()); + size_t totalSize = buffer->getLength() + extraTailFrames; + + alignedBus = std::make_shared(totalSize, channelCount_, buffer->getSampleRate()); + alignedBus->copy(buffer->bus_.get(), 0, 0, buffer->getLength()); + alignedBus->zero(buffer->getLength(), extraTailFrames); + } else { + alignedBus = std::make_shared(*buffer->bus_); + } } - auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - thisValue.asObject(runtime).setExternalMemoryPressure( - runtime, bufferHostObject->getSizeInBytes() + 16); - audioBufferSourceNode->setBuffer(bufferHostObject->audioBuffer_); + auto event = [ + audioBufferSourceNode, + alignedBus, + audioBus, + playbackRateBus + ](BaseAudioContext &) { + audioBufferSourceNode->setBuffer(alignedBus, audioBus, playbackRateBus); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + buffer_ = bufferHostObject; + loopEnd_ = bufferHostObject ? bufferHostObject->audioBuffer_->getDuration() : 0.0; + 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; +} + } // 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 ad3359962..54b16b3e3 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 @@ -10,6 +10,7 @@ using namespace facebook; struct AudioBufferSourceOptions; class BaseAudioContext; +class AudioBufferHostObject; class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObject { public: @@ -33,6 +34,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; + std::shared_ptr buffer_; + + void setOnLoopEndedCallbackId(uint64_t callbackId); }; } // 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 dc3542710..b1a2d7aa7 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 @@ -1,7 +1,8 @@ #include - #include + #include +#include namespace audioapi { @@ -16,33 +17,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 8b9c3c101..4e4605e8e 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 @@ -21,5 +21,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 d8d13cd4a..6c9e8d4e0 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 @@ -15,8 +15,6 @@ ConstantSourceNodeHostObject::ConstantSourceNodeHostObject( } 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 8cb158d66..61c5c0e7f 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 @@ -19,5 +19,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/utils/JsEnumParser.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp index 174799295..875a16264 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 @@ -187,4 +187,28 @@ namespace audioapi::js_enum_parser { throw std::invalid_argument("Unknown context state"); } } + + std::string channelCountModeToString(ChannelCountMode mode) { + switch (mode) { + case ChannelCountMode::MAX: + return "max"; + case ChannelCountMode::CLAMPED_MAX: + return "clamped-max"; + case ChannelCountMode::EXPLICIT: + return "explicit"; + default: + throw std::invalid_argument("Unknown channel count mode"); + } + } + + std::string channelInterpretationToString(ChannelInterpretation interpretation) { + switch (interpretation) { + case ChannelInterpretation::SPEAKERS: + return "speakers"; + case ChannelInterpretation::DISCRETE: + return "discrete"; + default: + throw std::invalid_argument("Unknown channel interpretation"); + } + } } // namespace audioapi::js_enum_parser 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 18a49a506..fadf44720 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 @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -19,4 +21,6 @@ std::string filterTypeToString(BiquadFilterType type); BiquadFilterType filterTypeFromString(const std::string &type); AudioEvent audioEventFromString(const std::string &event); std::string contextStateToString(ContextState state); +std::string channelCountModeToString(ChannelCountMode mode); +std::string channelInterpretationToString(ChannelInterpretation interpretation); } // namespace audioapi::js_enum_parser 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 96a5bdc10..c07b03d62 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 @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -16,7 +16,7 @@ AudioContext::AudioContext( const std::shared_ptr &audioEventHandlerRegistry, const RuntimeRegistry &runtimeRegistry) : BaseAudioContext(sampleRate, audioEventHandlerRegistry, runtimeRegistry), - isInitialized_(false) {} + isInitialized_(false) {} AudioContext::~AudioContext() { if (!isClosed()) { @@ -28,19 +28,19 @@ void AudioContext::initialize() { BaseAudioContext::initialize(); #ifdef ANDROID audioPlayer_ = std::make_shared( - this->renderAudio(), sampleRate_, destination_->getChannelCount()); + this->renderAudio(), getSampleRate(), destination_->getChannelCount()); #else audioPlayer_ = std::make_shared( - this->renderAudio(), sampleRate_, destination_->getChannelCount()); + this->renderAudio(), getSampleRate(), destination_->getChannelCount()); #endif } void AudioContext::close() { - state_ = ContextState::CLOSED; + state_.store(ContextState::CLOSED, std::memory_order_release); audioPlayer_->stop(); audioPlayer_->cleanup(); - nodeManager_->cleanup(); + getAudioGraphManager()->cleanup(); } bool AudioContext::resume() { @@ -53,7 +53,7 @@ bool AudioContext::resume() { } if (isInitialized_ && audioPlayer_->resume()) { - state_ = ContextState::RUNNING; + state_.store(ContextState::RUNNING, std::memory_order_release); return true; } @@ -71,7 +71,7 @@ bool AudioContext::suspend() { audioPlayer_->suspend(); - state_ = ContextState::SUSPENDED; + state_.store(ContextState::SUSPENDED, std::memory_order_release); return true; } @@ -82,8 +82,7 @@ bool AudioContext::start() { if (!isInitialized_ && audioPlayer_->start()) { isInitialized_ = true; - state_ = ContextState::RUNNING; - + state_.store(ContextState::RUNNING, std::memory_order_release); 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 c1eced267..49529d1fd 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 @@ -21,10 +21,15 @@ class AudioContext : public BaseAudioContext { const RuntimeRegistry &runtimeRegistry); ~AudioContext() override; + /// @note JS Thread only void close(); + /// @note JS Thread only bool resume(); + /// @note JS Thread only bool suspend(); + /// @note JS Thread only bool start(); + /// @note JS Thread only void initialize() override; private: 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 9daa1df94..d3045a23e 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 @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include @@ -17,8 +17,8 @@ AudioNode::AudioNode(const std::shared_ptr& context) : context } AudioNode::AudioNode(const std::shared_ptr& context, const AudioNodeOptions &options) - : channelCount_(options.channelCount), - context_(context), + : context_(context), + channelCount_(options.channelCount), channelCountMode_(options.channelCountMode), channelInterpretation_(options.channelInterpretation) { audioBus_ = @@ -43,46 +43,46 @@ int AudioNode::getChannelCount() const { return channelCount_; } -std::string AudioNode::getChannelCountMode() const { - return AudioNode::toString(channelCountMode_); +ChannelCountMode AudioNode::getChannelCountMode() const { + return channelCountMode_; } -std::string AudioNode::getChannelInterpretation() const { - return AudioNode::toString(channelInterpretation_); +ChannelInterpretation AudioNode::getChannelInterpretation() const { + return channelInterpretation_; } void AudioNode::connect(const std::shared_ptr &node) { if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->addPendingNodeConnection( - shared_from_this(), node, AudioNodeManager::ConnectionType::CONNECT); + context->getAudioGraphManager()->addPendingNodeConnection( + shared_from_this(), node, AudioGraphManager::ConnectionType::CONNECT); } } void AudioNode::connect(const std::shared_ptr ¶m) { if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->addPendingParamConnection( - shared_from_this(), param, AudioNodeManager::ConnectionType::CONNECT); + context->getAudioGraphManager()->addPendingParamConnection( + shared_from_this(), param, AudioGraphManager::ConnectionType::CONNECT); } } void AudioNode::disconnect() { if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->addPendingNodeConnection( - shared_from_this(), nullptr, AudioNodeManager::ConnectionType::DISCONNECT_ALL); + context->getAudioGraphManager()->addPendingNodeConnection( + shared_from_this(), nullptr, AudioGraphManager::ConnectionType::DISCONNECT_ALL); } } void AudioNode::disconnect(const std::shared_ptr &node) { if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->addPendingNodeConnection( - shared_from_this(), node, AudioNodeManager::ConnectionType::DISCONNECT); + context->getAudioGraphManager()->addPendingNodeConnection( + shared_from_this(), node, AudioGraphManager::ConnectionType::DISCONNECT); } } void AudioNode::disconnect(const std::shared_ptr ¶m) { if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->addPendingParamConnection( - shared_from_this(), param, AudioNodeManager::ConnectionType::DISCONNECT); + context->getAudioGraphManager()->addPendingParamConnection( + shared_from_this(), param, AudioGraphManager::ConnectionType::DISCONNECT); } } @@ -118,30 +118,6 @@ void AudioNode::disable() { } } -std::string AudioNode::toString(ChannelCountMode mode) { - switch (mode) { - case ChannelCountMode::MAX: - return "max"; - case ChannelCountMode::CLAMPED_MAX: - return "clamped-max"; - case ChannelCountMode::EXPLICIT: - return "explicit"; - default: - throw std::invalid_argument("Unknown channel count mode"); - } -} - -std::string AudioNode::toString(ChannelInterpretation interpretation) { - switch (interpretation) { - case ChannelInterpretation::SPEAKERS: - return "speakers"; - case ChannelInterpretation::DISCRETE: - return "discrete"; - default: - throw std::invalid_argument("Unknown channel interpretation"); - } -} - std::shared_ptr AudioNode::processAudio( const std::shared_ptr &outputBus, int framesToProcess, 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 aea5845e4..353662815 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,20 +1,22 @@ #pragma once +#include #include #include #include #include #include +#include #include #include #include +#include #include namespace audioapi { class AudioBus; -class BaseAudioContext; class AudioParam; struct AudioNodeOptions; @@ -29,8 +31,8 @@ class AudioNode : public std::enable_shared_from_this { int getNumberOfInputs() const; int getNumberOfOutputs() const; int getChannelCount() const; - std::string getChannelCountMode() const; - std::string getChannelInterpretation() const; + ChannelCountMode getChannelCountMode() const; + ChannelInterpretation getChannelInterpretation() const; void connect(const std::shared_ptr &node); void connect(const std::shared_ptr ¶m); void disconnect(); @@ -41,23 +43,32 @@ class AudioNode : public std::enable_shared_from_this { int framesToProcess, bool checkIsAlreadyProcessed); + /// @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 AudioNodeManager; + friend class AudioGraphManager; friend class AudioDestinationNode; friend class ConvolverNode; friend class DelayNodeHostObject; - int channelCount_ = 2; std::weak_ptr context_; std::shared_ptr audioBus_; int numberOfInputs_ = 1; int numberOfOutputs_ = 1; + int channelCount_ = 2; ChannelCountMode channelCountMode_ = ChannelCountMode::MAX; ChannelInterpretation channelInterpretation_ = ChannelInterpretation::SPEAKERS; @@ -67,17 +78,17 @@ class AudioNode : public std::enable_shared_from_this { int numberOfEnabledInputNodes_ = 0; bool isInitialized_ = false; - bool isEnabled_ = true; bool requiresTailProcessing_ = false; std::size_t lastRenderedFrame_{SIZE_MAX}; + void enable(); + virtual void disable(); + private: + bool isEnabled_ = true; std::vector> inputBuses_ = {}; - static std::string toString(ChannelCountMode mode); - static std::string toString(ChannelInterpretation interpretation); - virtual std::shared_ptr processInputs( const std::shared_ptr &outputBus, int framesToProcess, 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 f1847550c..6deb3542f 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), @@ -29,7 +28,7 @@ AudioParam::AudioParam( inputNodes_.reserve(4); // Default calculation function just returns the static value calculateValue_ = [this](double, double, float, float, double) { - return value_; + return value_.load(std::memory_order_acquire); }; } @@ -43,18 +42,18 @@ float AudioParam::getValueAtTime(double time) { endTime_ = event.getEndTime(); startValue_ = event.getStartValue(); endValue_ = event.getEndValue(); - calculateValue_ = std::move(event.getCalculateValue()); + calculateValue_ = event.getCalculateValue(); } // 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()) { + if (startTime < this->getQueueEndTime()) { return; } @@ -68,21 +67,18 @@ void AudioParam::setValueAtTime(float value, double startTime) { return endValue; }; - param.updateQueue(ParamChangeEvent( + this->updateQueue(ParamChangeEvent( startTime, startTime, - param.getQueueEndValue(), + this->getQueueEndValue(), value, std::move(calculateValue), ParamChangeEventType::SET_VALUE)); - }; - eventScheduler_.scheduleEvent(std::move(event)); } 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()) { + if (endTime < this->getQueueEndTime()) { return; } @@ -101,20 +97,17 @@ void AudioParam::linearRampToValueAtTime(float value, double endTime) { return endValue; }; - param.updateQueue(ParamChangeEvent( - param.getQueueEndTime(), + this->updateQueue(ParamChangeEvent( + this->getQueueEndTime(), endTime, - param.getQueueEndValue(), + this->getQueueEndValue(), value, std::move(calculateValue), ParamChangeEventType::LINEAR_RAMP)); - }; - eventScheduler_.scheduleEvent(std::move(event)); } void AudioParam::exponentialRampToValueAtTime(float value, double endTime) { - auto event = [value, endTime](AudioParam ¶m) { - if (endTime <= param.getQueueEndTime()) { + if (endTime <= this->getQueueEndTime()) { return; } @@ -134,20 +127,17 @@ void AudioParam::exponentialRampToValueAtTime(float value, double endTime) { return endValue; }; - param.updateQueue(ParamChangeEvent( - param.getQueueEndTime(), + this->updateQueue(ParamChangeEvent( + this->getQueueEndTime(), endTime, - param.getQueueEndValue(), + this->getQueueEndValue(), value, std::move(calculateValue), ParamChangeEventType::EXPONENTIAL_RAMP)); - }; - eventScheduler_.scheduleEvent(std::move(event)); } void AudioParam::setTargetAtTime(float target, double startTime, double timeConstant) { - auto event = [target, startTime, timeConstant](AudioParam ¶m) { - if (startTime <= param.getQueueEndTime()) { + if (startTime <= this->getQueueEndTime()) { return; } // Exponential decay function towards target value @@ -160,17 +150,14 @@ void AudioParam::setTargetAtTime(float target, double startTime, double timeCons return static_cast( target + (startValue - target) * exp(-(time - startTime) / timeConstant)); }; - param.updateQueue(ParamChangeEvent( + this->updateQueue(ParamChangeEvent( startTime, startTime, // SetTarget events have infinite duration conceptually - param.getQueueEndValue(), - param.getQueueEndValue(), // End value is not meaningful for + this->getQueueEndValue(), + this->getQueueEndValue(), // End value is not meaningful for // infinite events std::move(calculateValue), ParamChangeEventType::SET_TARGET)); - }; - - eventScheduler_.scheduleEvent(std::move(event)); } void AudioParam::setValueCurveAtTime( @@ -178,8 +165,7 @@ void AudioParam::setValueCurveAtTime( size_t length, double startTime, double duration) { - auto event = [values, length, startTime, duration](AudioParam ¶m) { - if (startTime <= param.getQueueEndTime()) { + if (startTime <= this->getQueueEndTime()) { return; } @@ -203,29 +189,21 @@ void AudioParam::setValueCurveAtTime( return endValue; }; - param.updateQueue(ParamChangeEvent( + this->updateQueue(ParamChangeEvent( startTime, startTime + duration, - param.getQueueEndValue(), + this->getQueueEndValue(), values->at(length - 1), std::move(calculateValue), ParamChangeEventType::SET_VALUE_CURVE)); - }; - - /// Schedules an event that modifies this param - /// It will be executed on next audio render cycle - eventScheduler_.scheduleEvent(std::move(event)); } 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) { @@ -255,7 +233,6 @@ std::shared_ptr AudioParam::calculateInputs( } std::shared_ptr AudioParam::processARateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBus = calculateInputs(audioBus_, framesToProcess); std::shared_ptr context = context_.lock(); @@ -277,7 +254,6 @@ std::shared_ptr AudioParam::processARateParam(int framesToProcess, dou } float AudioParam::processKRateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBus = calculateInputs(audioBus_, 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 af1d7fcb6..370f4d424 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 @@ -23,84 +23,63 @@ class AudioParam { float maxValue, 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_; + return value_.load(std::memory_order_acquire); } - // 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_ = std::clamp(value, minValue_, maxValue_); + value_.store(std::clamp(value, minValue_, maxValue_), std::memory_order_release); } - // JS-Thread only void setValueAtTime(float value, double startTime); - - // JS-Thread only void linearRampToValueAtTime(float value, double endTime); - - // JS-Thread only void exponentialRampToValueAtTime(float value, double endTime); - - // JS-Thread only void setTargetAtTime(float target, double startTime, double timeConstant); - - // JS-Thread only void setValueCurveAtTime( std::shared_ptr> values, size_t length, double startTime, double duration); - - // JS-Thread only void cancelScheduledValues(double cancelTime); - - // JS-Thread only void cancelAndHoldAtTime(double cancelTime); - /// Audio-Thread only methods - /// These methods are called only from the Audio rendering thread. - - // Audio-Thread only (indirectly through AudioNode::connectParam by AudioNodeManager) void addInputNode(AudioNode *node); - - // Audio-Thread only (indirectly through AudioNode::disconnectParam by AudioNodeManager) void removeInputNode(AudioNode *node); - - // Audio-Thread only std::shared_ptr processARateParam(int framesToProcess, double time); - - // Audio-Thread only float processKRateParam(int framesToProcess, double time); + 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; + } + private: // Core parameter state std::weak_ptr context_; - float value_; + std::atomic value_; float defaultValue_; float minValue_; float maxValue_; AudioParamEventQueue eventsQueue_; - CrossThreadEventScheduler eventScheduler_; // Current automation state (cached for performance) double startTime_; @@ -116,7 +95,7 @@ class AudioParam { /// @brief Get the end time of the parameter queue. /// @return The end time of the parameter queue or last endTime_ if queue is empty. - inline double getQueueEndTime() const noexcept { + [[nodiscard]] inline double getQueueEndTime() const noexcept { if (eventsQueue_.isEmpty()) { return endTime_; } @@ -125,18 +104,13 @@ class AudioParam { /// @brief Get the end value of the parameter queue. /// @return The end value of the parameter queue or last endValue_ if queue is empty. - inline float getQueueEndValue() const noexcept { + [[nodiscard]] inline float getQueueEndValue() const noexcept { if (eventsQueue_.isEmpty()) { return endValue_; } 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 fc329e06c..925a61ff7 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 @@ -22,7 +22,7 @@ #endif // RN_AUDIO_API_FFMPEG_DISABLED #include #include -#include +#include #include #include #include @@ -39,25 +39,30 @@ BaseAudioContext::BaseAudioContext( float sampleRate, const std::shared_ptr &audioEventHandlerRegistry, const RuntimeRegistry &runtimeRegistry) - : sampleRate_(sampleRate), - nodeManager_(std::make_shared()), - audioEventHandlerRegistry_(audioEventHandlerRegistry), - runtimeRegistry_(runtimeRegistry) {} + : runtimeRegistry_(runtimeRegistry), + sampleRate_{sampleRate}, + state_(ContextState::SUSPENDED), + graphManager_(std::make_shared()), + audioEventScheduler_( + std::make_unique>(AUDIO_SCHEDULER_CAPACITY)), + audioEventHandlerRegistry_(audioEventHandlerRegistry) {} void BaseAudioContext::initialize() { destination_ = std::make_shared(shared_from_this()); } ContextState BaseAudioContext::getState() { - if (isDriverRunning() || state_ == ContextState::CLOSED) { - return state_; + auto state = state_.load(std::memory_order_acquire); + + if (state == ContextState::CLOSED || isDriverRunning()) { + return state; } return ContextState::SUSPENDED; } float BaseAudioContext::getSampleRate() const { - return sampleRate_; + return sampleRate_.load(std::memory_order_acquire); } std::size_t BaseAudioContext::getCurrentSampleFrame() const { @@ -81,7 +86,7 @@ std::shared_ptr BaseAudioContext::createWorkletSourceNode( WorkletsRunner workletRunner(runtime, shareableWorklet, shouldLockRuntime); auto workletSourceNode = std::make_shared(shared_from_this(), std::move(workletRunner)); - nodeManager_->addSourceNode(workletSourceNode); + graphManager_->addSourceNode(workletSourceNode); return workletSourceNode; } @@ -94,7 +99,7 @@ std::shared_ptr BaseAudioContext::createWorkletNode( WorkletsRunner workletRunner(runtime, shareableWorklet, shouldLockRuntime); auto workletNode = std::make_shared( shared_from_this(), bufferLength, inputChannelCount, std::move(workletRunner)); - nodeManager_->addProcessingNode(workletNode); + graphManager_->addProcessingNode(workletNode); return workletNode; } @@ -105,33 +110,34 @@ std::shared_ptr BaseAudioContext::createWorkletProcessing WorkletsRunner workletRunner(runtime, shareableWorklet, shouldLockRuntime); auto workletProcessingNode = std::make_shared(shared_from_this(), std::move(workletRunner)); - nodeManager_->addProcessingNode(workletProcessingNode); + graphManager_->addProcessingNode(workletProcessingNode); return workletProcessingNode; } std::shared_ptr BaseAudioContext::createRecorderAdapter() { auto recorderAdapter = std::make_shared(shared_from_this()); - nodeManager_->addProcessingNode(recorderAdapter); + graphManager_->addProcessingNode(recorderAdapter); return recorderAdapter; } -std::shared_ptr BaseAudioContext::createOscillator(const OscillatorOptions &options) { +std::shared_ptr BaseAudioContext::createOscillator( + const OscillatorOptions &options) { auto oscillator = std::make_shared(shared_from_this(), options); - nodeManager_->addSourceNode(oscillator); + graphManager_->addSourceNode(oscillator); return oscillator; } std::shared_ptr BaseAudioContext::createConstantSource( const ConstantSourceOptions &options) { auto constantSource = std::make_shared(shared_from_this(), options); - nodeManager_->addSourceNode(constantSource); + graphManager_->addSourceNode(constantSource); return constantSource; } std::shared_ptr BaseAudioContext::createStreamer(const StreamerOptions &options) { #if !RN_AUDIO_API_FFMPEG_DISABLED auto streamer = std::make_shared(shared_from_this(), options); - nodeManager_->addSourceNode(streamer); + graphManager_->addSourceNode(streamer); return streamer; #else return nullptr; @@ -140,47 +146,47 @@ std::shared_ptr BaseAudioContext::createStreamer(const StreamerOpt std::shared_ptr BaseAudioContext::createGain(const GainOptions &options) { auto gain = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(gain); + graphManager_->addProcessingNode(gain); return gain; } std::shared_ptr BaseAudioContext::createStereoPanner( const StereoPannerOptions &options) { auto stereoPanner = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(stereoPanner); + graphManager_->addProcessingNode(stereoPanner); return stereoPanner; } std::shared_ptr BaseAudioContext::createDelay(const DelayOptions &options) { auto delay = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(delay); + graphManager_->addProcessingNode(delay); return delay; } std::shared_ptr BaseAudioContext::createBiquadFilter( const BiquadFilterOptions &options) { auto biquadFilter = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(biquadFilter); + graphManager_->addProcessingNode(biquadFilter); return biquadFilter; } std::shared_ptr BaseAudioContext::createBufferSource( const AudioBufferSourceOptions &options) { auto bufferSource = std::make_shared(shared_from_this(), options); - nodeManager_->addSourceNode(bufferSource); + graphManager_->addSourceNode(bufferSource); return bufferSource; } std::shared_ptr BaseAudioContext::createIIRFilter(const IIRFilterOptions &options) { auto iirFilter = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(iirFilter); + graphManager_->addProcessingNode(iirFilter); return iirFilter; } std::shared_ptr BaseAudioContext::createBufferQueueSource( const BaseAudioBufferSourceOptions &options) { auto bufferSource = std::make_shared(shared_from_this(), options); - nodeManager_->addSourceNode(bufferSource); + graphManager_->addSourceNode(bufferSource); return bufferSource; } @@ -191,68 +197,69 @@ std::shared_ptr BaseAudioContext::createBuffer(const AudioBufferOpt std::shared_ptr BaseAudioContext::createPeriodicWave( const std::vector> &complexData, bool disableNormalization, - int length) { - return std::make_shared(sampleRate_, complexData, length, disableNormalization); + int length) const { + return std::make_shared(getSampleRate(), complexData, length, disableNormalization); } std::shared_ptr BaseAudioContext::createAnalyser(const AnalyserOptions &options) { auto analyser = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(analyser); + graphManager_->addProcessingNode(analyser); return analyser; } std::shared_ptr BaseAudioContext::createConvolver(const ConvolverOptions &options) { auto convolver = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(convolver); + graphManager_->addProcessingNode(convolver); return convolver; } -std::shared_ptr BaseAudioContext::createWaveShaper(const WaveShaperOptions &options) { +std::shared_ptr BaseAudioContext::createWaveShaper( + const WaveShaperOptions &options) { auto waveShaper = std::make_shared(shared_from_this(), options); - nodeManager_->addProcessingNode(waveShaper); + graphManager_->addProcessingNode(waveShaper); return waveShaper; } -AudioNodeManager *BaseAudioContext::getNodeManager() { - return nodeManager_.get(); -} - bool BaseAudioContext::isRunning() const { - return state_ == ContextState::RUNNING && isDriverRunning(); + return state_.load(std::memory_order_acquire) == ContextState::RUNNING && isDriverRunning(); } bool BaseAudioContext::isSuspended() const { - return state_ == ContextState::SUSPENDED || !isDriverRunning(); + ContextState s = state_.load(std::memory_order_acquire); + if (s == ContextState::CLOSED) + return false; + + return s == ContextState::SUSPENDED || !isDriverRunning(); } bool BaseAudioContext::isClosed() const { - return state_ == ContextState::CLOSED; + return state_.load(std::memory_order_acquire) == ContextState::CLOSED; } float BaseAudioContext::getNyquistFrequency() const { - return sampleRate_ / 2.0f; + return getSampleRate() / 2.0f; } std::shared_ptr BaseAudioContext::getBasicWaveForm(OscillatorType type) { switch (type) { case OscillatorType::SINE: if (cachedSineWave_ == nullptr) { - cachedSineWave_ = std::make_shared(sampleRate_, type, false); + cachedSineWave_ = std::make_shared(getSampleRate(), type, false); } return cachedSineWave_; case OscillatorType::SQUARE: if (cachedSquareWave_ == nullptr) { - cachedSquareWave_ = std::make_shared(sampleRate_, type, false); + cachedSquareWave_ = std::make_shared(getSampleRate(), type, false); } return cachedSquareWave_; case OscillatorType::SAWTOOTH: if (cachedSawtoothWave_ == nullptr) { - cachedSawtoothWave_ = std::make_shared(sampleRate_, type, false); + cachedSawtoothWave_ = std::make_shared(getSampleRate(), type, false); } return cachedSawtoothWave_; case OscillatorType::TRIANGLE: if (cachedTriangleWave_ == nullptr) { - cachedTriangleWave_ = std::make_shared(sampleRate_, type, false); + cachedTriangleWave_ = std::make_shared(getSampleRate(), type, false); } return cachedTriangleWave_; case OscillatorType::CUSTOM: @@ -261,4 +268,13 @@ std::shared_ptr BaseAudioContext::getBasicWaveForm(OscillatorType } } +std::shared_ptr BaseAudioContext::getAudioGraphManager() const { + return graphManager_; +} + +std::shared_ptr + BaseAudioContext::getAudioEventHandlerRegistry() const { + return audioEventHandlerRegistry_; +} + } // namespace audioapi 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 ade5beac3..1718312b5 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,15 @@ #include #include #include +#include + #include #include #include #include #include #include +#include #include #include @@ -22,7 +25,7 @@ class PeriodicWave; class OscillatorNode; class ConstantSourceNode; class StereoPannerNode; -class AudioNodeManager; +class AudioGraphManager; class BiquadFilterNode; class IIRFilterNode; class AudioDestinationNode; @@ -98,38 +101,58 @@ class BaseAudioContext : public std::enable_shared_from_this { std::shared_ptr createPeriodicWave( const std::vector> &complexData, bool disableNormalization, - int length); + int length) const; std::shared_ptr createAnalyser(const AnalyserOptions &options); std::shared_ptr createConvolver(const ConvolverOptions &options); std::shared_ptr createWaveShaper(const WaveShaperOptions &options); std::shared_ptr getBasicWaveForm(OscillatorType type); [[nodiscard]] float getNyquistFrequency() const; - AudioNodeManager *getNodeManager(); + std::shared_ptr getAudioGraphManager() const; + std::shared_ptr getAudioEventHandlerRegistry() const; [[nodiscard]] bool isRunning() const; [[nodiscard]] bool isSuspended() const; [[nodiscard]] bool isClosed() const; + void inline processAudioEvents() { + audioEventScheduler_->processAllEvents(*this); + } + + template + bool inline scheduleAudioEvent(F &&event) noexcept { + if (!isRunning()) { + processAudioEvents(); + event(*this); + return true; + } + + return audioEventScheduler_->scheduleEvent(std::forward(event)); + } + virtual void initialize(); + RuntimeRegistry runtimeRegistry_; + protected: std::shared_ptr destination_; - float sampleRate_; - ContextState state_ = ContextState::SUSPENDED; - std::shared_ptr nodeManager_; + std::atomic sampleRate_; + std::atomic state_; + std::shared_ptr graphManager_; private: + static constexpr int AUDIO_SCHEDULER_CAPACITY = 1024; + std::shared_ptr cachedSineWave_ = nullptr; std::shared_ptr cachedSquareWave_ = nullptr; std::shared_ptr cachedSawtoothWave_ = nullptr; std::shared_ptr cachedTriangleWave_ = nullptr; - [[nodiscard]] virtual bool isDriverRunning() const = 0; + std::unique_ptr> audioEventScheduler_; - public: std::shared_ptr audioEventHandlerRegistry_; - RuntimeRegistry runtimeRegistry_; + + [[nodiscard]] virtual bool isDriverRunning() const = 0; }; } // namespace audioapi 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 49ce36959..1c8d9d3b0 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 @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -31,13 +31,13 @@ OfflineAudioContext::OfflineAudioContext( resultBus_(std::make_shared(length, numberOfChannels, sampleRate)) {} OfflineAudioContext::~OfflineAudioContext() { - nodeManager_->cleanup(); + getAudioGraphManager()->cleanup(); } void OfflineAudioContext::resume() { Locker locker(mutex_); - if (state_ == ContextState::RUNNING) { + if (isRunning()) { return; } @@ -62,7 +62,7 @@ void OfflineAudioContext::suspend(double when, const std::function &call } void OfflineAudioContext::renderAudio() { - state_ = ContextState::RUNNING; + state_.store(ContextState::RUNNING, std::memory_order_release); std::thread([this]() { auto audioBus = std::make_shared(RENDER_QUANTUM_SIZE, numberOfChannels_, sampleRate_); @@ -88,7 +88,7 @@ void OfflineAudioContext::renderAudio() { assert(currentSampleFrame_ < length_); auto callback = suspend->second; scheduledSuspends_.erase(currentSampleFrame_); - state_ = ContextState::SUSPENDED; + state_.store(ContextState::SUSPENDED, std::memory_order_release); callback(); return; } 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 a33fd384a..86a207a7f 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 @@ -23,9 +23,11 @@ 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/destinations/AudioDestinationNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp index b46386315..a71118a1a 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 @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include @@ -16,12 +16,12 @@ AudioDestinationNode::AudioDestinationNode(const std::shared_ptr context = context_.lock()) { - return static_cast(currentSampleFrame_) / context->getSampleRate(); + return static_cast(getCurrentSampleFrame()) / context->getSampleRate(); } else { return 0.0; } @@ -35,7 +35,8 @@ void AudioDestinationNode::renderAudio( } if (std::shared_ptr context = context_.lock()) { - context->getNodeManager()->preProcessGraph(); + context->processAudioEvents(); + context->getAudioGraphManager()->preProcessGraph(); } destinationBus->zero(); @@ -48,7 +49,7 @@ void AudioDestinationNode::renderAudio( destinationBus->normalize(); - currentSampleFrame_ += numFrames; + currentSampleFrame_.fetch_add(numFrames, std::memory_order_acq_rel); } } // namespace audioapi 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 e971c41e4..923cf8bfe 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 @@ -29,7 +29,7 @@ class AudioDestinationNode : public AudioNode { }; private: - std::size_t currentSampleFrame_; + std::atomic currentSampleFrame_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.cpp index 528ca7703..d617f176d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.cpp @@ -16,6 +16,26 @@ AudioBuffer::AudioBuffer(std::shared_ptr bus) { bus_ = std::move(bus); } +AudioBuffer::AudioBuffer(const audioapi::AudioBuffer &other): bus_{std::make_shared(*other.bus_)} {} + +AudioBuffer &AudioBuffer::operator=(const audioapi::AudioBuffer &other) { + if (this != &other) { + bus_ = std::make_shared(*other.bus_); + } + + return *this; +} + +AudioBuffer::AudioBuffer(audioapi::AudioBuffer &&other) noexcept: bus_{std::move(other.bus_)} {} + +AudioBuffer &AudioBuffer::operator=(audioapi::AudioBuffer &&other) noexcept { + if (this != &other) { + bus_ = std::move(other.bus_); + } + + return *this; +} + size_t AudioBuffer::getLength() const { return bus_->getSize(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.h index 3952d65d0..5f814c348 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBuffer.h @@ -12,11 +12,19 @@ namespace audioapi { class AudioBus; struct AudioBufferOptions; +/// AudioBuffer is not thread-safe. +/// Due to that fact it should be copied when passing between threads. class AudioBuffer { public: explicit AudioBuffer(const AudioBufferOptions &options); explicit AudioBuffer(std::shared_ptr bus); + AudioBuffer(const AudioBuffer &other); + AudioBuffer &operator=(const AudioBuffer &other); + + AudioBuffer(AudioBuffer &&other) noexcept; + AudioBuffer &operator=(AudioBuffer &&other) noexcept; + [[nodiscard]] size_t getLength() const; [[nodiscard]] float getSampleRate() const; [[nodiscard]] double getDuration() const; @@ -34,6 +42,7 @@ class AudioBuffer { private: friend class AudioBufferSourceNode; + friend class AudioBufferSourceNodeHostObject; friend class AudioBufferQueueSourceNode; friend class AudioBufferHostObject; 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 f73a4b3c7..e63b31d34 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 @@ -39,11 +39,7 @@ 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) { @@ -57,10 +53,6 @@ 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()) { @@ -83,14 +75,16 @@ double AudioBufferBaseSourceNode::getOutputLatency() const { return 0; } -void AudioBufferBaseSourceNode::sendOnPositionChangedEvent() { - auto onPositionChangedCallbackId = onPositionChangedCallbackId_.load(std::memory_order_acquire); +void AudioBufferBaseSourceNode::unregisterOnPositionChangedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::POSITION_CHANGED, callbackId); +} - if (onPositionChangedCallbackId != 0 && onPositionChangedTime_ > onPositionChangedInterval_) { +void AudioBufferBaseSourceNode::sendOnPositionChangedEvent() { + 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; } 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 d44a4c702..ec9d3fd94 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 @@ -28,12 +28,14 @@ class AudioBufferBaseSourceNode : public AudioScheduledSourceNode { [[nodiscard]] double getInputLatency() const; [[nodiscard]] double getOutputLatency() const; + /// @note JS Thread only. + /// Thread safe, because does not access state of the node. + void unregisterOnPositionChangedCallback(uint64_t callbackId); + protected: // pitch correction bool pitchCorrection_; - std::mutex bufferLock_; - // pitch correction std::shared_ptr> stretch_; std::shared_ptr playbackRateBus_; @@ -45,11 +47,10 @@ class AudioBufferBaseSourceNode : public AudioScheduledSourceNode { // 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 c845dd40e..21c012388 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 @@ -42,8 +42,6 @@ AudioBufferQueueSourceNode::AudioBufferQueueSourceNode( } AudioBufferQueueSourceNode::~AudioBufferQueueSourceNode() { - Locker locker(getBufferLock()); - buffers_ = {}; } @@ -75,7 +73,6 @@ void AudioBufferQueueSourceNode::pause() { } std::string AudioBufferQueueSourceNode::enqueueBuffer(const std::shared_ptr &buffer) { - auto locker = Locker(getBufferLock()); buffers_.emplace(bufferId_, buffer); if (tailBuffer_ != nullptr) { @@ -86,7 +83,6 @@ std::string AudioBufferQueueSourceNode::enqueueBuffer(const std::shared_ptr AudioBufferQueueSourceNode::processNode( const std::shared_ptr &processingBus, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // no audio data to fill, zero the output and return. - if (buffers_.empty()) { - processingBus->zero(); - return processingBus; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBus, framesToProcess); - } else { - processWithPitchCorrection(processingBus, framesToProcess); - } + // no audio data to fill, zero the output and return. + if (buffers_.empty()) { + processingBus->zero(); + return processingBus; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBus, framesToProcess); } else { - processingBus->zero(); + processWithPitchCorrection(processingBus, framesToProcess); } + handleStopScheduled(); + return processingBus; } 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 06764ece4..71ef532de 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 @@ -8,8 +8,11 @@ #include #include #include +#include + #include #include +#include namespace audioapi { @@ -21,39 +24,15 @@ AudioBufferSourceNode::AudioBufferSourceNode( loopSkip_(false), loopStart_(options.loopStart), loopEnd_(options.loopEnd) { - buffer_ = std::shared_ptr(options.buffer); - alignedBus_ = std::shared_ptr(nullptr); +// setBuffer(options.buffer); isInitialized_ = true; } AudioBufferSourceNode::~AudioBufferSourceNode() { - Locker locker(getBufferLock()); - - buffer_.reset(); alignedBus_.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_; -} - void AudioBufferSourceNode::setLoop(bool loop) { loop_ = loop; } @@ -75,40 +54,56 @@ 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 &alignedBus, const std::shared_ptr &audioBus, const std::shared_ptr &playbackRateBus) { std::shared_ptr context = context_.lock(); - if (buffer == nullptr || context == nullptr) { - buffer_ = std::shared_ptr(nullptr); - alignedBus_ = std::shared_ptr(nullptr); - loopEnd_ = 0; - return; + if (context == nullptr) { + return; } - buffer_ = buffer; - channelCount_ = buffer_->getNumberOfChannels(); + if (alignedBus_ != nullptr) { + context->getAudioGraphManager()->addAudioBusForDestruction(std::move(alignedBus_)); + } - stretch_->presetDefault(channelCount_, buffer_->getSampleRate()); + if (alignedBus == nullptr) { + alignedBus_ = nullptr; + loopEnd_ = 0; + return; + } - if (pitchCorrection_) { - int extraTailFrames = - static_cast((getInputLatency() + getOutputLatency()) * context->getSampleRate()); - size_t totalSize = buffer_->getLength() + extraTailFrames; + channelCount_ = alignedBus->getNumberOfChannels(); + stretch_->presetDefault(channelCount_, alignedBus->getSampleRate()); - alignedBus_ = std::make_shared(totalSize, channelCount_, buffer_->getSampleRate()); - alignedBus_->copy(buffer_->bus_.get(), 0, 0, buffer_->getLength()); + if (audioBus_ != nullptr) { + context->getAudioGraphManager()->addAudioBusForDestruction(std::move(audioBus_)); + } - alignedBus_->zero(buffer_->getLength(), extraTailFrames); - } else { - alignedBus_ = std::make_shared(*buffer_->bus_); + if (playbackRateBus_ != nullptr) { + context->getAudioGraphManager()->addAudioBusForDestruction(std::move(playbackRateBus_)); } - audioBus_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - playbackRateBus_ = - std::make_shared(RENDER_QUANTUM_SIZE * 3, channelCount_, context->getSampleRate()); - loopEnd_ = buffer_->getDuration(); + alignedBus_ = alignedBus; + audioBus_ = audioBus; + playbackRateBus_ = playbackRateBus; + +// if (pitchCorrection_) { +// int extraTailFrames = +// static_cast((getInputLatency() + getOutputLatency()) * buffer->getSampleRate()); +// size_t totalSize = buffer->getLength() + extraTailFrames; +// +// alignedBus_ = std::make_shared(totalSize, channelCount_, buffer->getSampleRate()); +// alignedBus_->copy(buffer->bus_.get(), 0, 0, buffer->getLength()); +// +// alignedBus_->zero(buffer->getLength(), extraTailFrames); +// } else { +// alignedBus_ = std::make_shared(*buffer->bus_); +// } +// audioBus_ = +// std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); +// playbackRateBus_ = +// std::make_shared(RENDER_QUANTUM_SIZE * 3, channelCount_, context->getSampleRate()); + + loopEnd_ = alignedBus->getDuration(); } void AudioBufferSourceNode::start(double when, double offset, double duration) { @@ -138,46 +133,41 @@ void AudioBufferSourceNode::disable() { } 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 &processingBus, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // No audio data to fill, zero the output and return. - if (!alignedBus_) { - processingBus->zero(); - return processingBus; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBus, framesToProcess); - } else { - processWithPitchCorrection(processingBus, framesToProcess); - } + // No audio data to fill, zero the output and return. + if (!alignedBus_) { + processingBus->zero(); + return processingBus; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBus, framesToProcess); } else { - processingBus->zero(); + processWithPitchCorrection(processingBus, framesToProcess); } + handleStopScheduled(); + return processingBus; } double AudioBufferSourceNode::getCurrentPosition() const { - return dsp::sampleFrameToTime(static_cast(vReadIndex_), buffer_->getSampleRate()); + return dsp::sampleFrameToTime(static_cast(vReadIndex_), alignedBus_->getSampleRate()); } 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_, {}); } } 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 806d513c9..abd07f16d 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 @@ -22,17 +22,14 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { 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; - void setLoop(bool loop); void setLoopSkip(bool loopSkip); void setLoopStart(double loopStart); void setLoopEnd(double loopEnd); - void setBuffer(const std::shared_ptr &buffer); + void setBuffer( + const std::shared_ptr &alignedBus, + const std::shared_ptr &audioBus, + const std::shared_ptr &playbackRateBus); using AudioScheduledSourceNode::start; void start(double when, double offset, double duration = -1); @@ -40,6 +37,10 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { void setOnLoopEndedCallbackId(uint64_t callbackId); + /// @note JS Thread only. + /// Thread safe, because does not access state of the node. + void unregisterOnLoopEndedCallback(uint64_t callbackId); + protected: std::shared_ptr processNode( const std::shared_ptr &processingBus, @@ -54,10 +55,9 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { double loopEnd_; // User provided buffer - std::shared_ptr buffer_; std::shared_ptr alignedBus_; - 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 88ead6d9f..cb4e9528d 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 @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -16,12 +16,12 @@ namespace audioapi { -AudioScheduledSourceNode::AudioScheduledSourceNode(std::shared_ptr context) +AudioScheduledSourceNode::AudioScheduledSourceNode(const std::shared_ptr &context) : AudioNode(context), startTime_(-1.0), stopTime_(-1.0), playbackState_(PlaybackState::UNSCHEDULED), - audioEventHandlerRegistry_(context->audioEventHandlerRegistry_) { + audioEventHandlerRegistry_(context->getAudioEventHandlerRegistry()) { numberOfInputs_ = 0; } @@ -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( @@ -165,9 +165,9 @@ void AudioScheduledSourceNode::updatePlaybackInfo( void AudioScheduledSourceNode::disable() { AudioNode::disable(); - auto onEndedCallbackId = onEndedCallbackId_.load(std::memory_order_acquire); - if (onEndedCallbackId != 0) { - audioEventHandlerRegistry_->invokeHandlerWithEventBody(AudioEvent::ENDED, onEndedCallbackId, {}); + if (onEndedCallbackId_ != 0) { + audioEventHandlerRegistry_->invokeHandlerWithEventBody( + 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 8717c2825..3c05af811 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 @@ -26,7 +26,7 @@ class AudioScheduledSourceNode : public AudioNode { // STOP_SCHEDULED: The node is scheduled to stop at a specific time, but is still playing. // FINISHED: The node has finished playing. enum class PlaybackState { UNSCHEDULED, SCHEDULED, PLAYING, STOP_SCHEDULED, FINISHED }; - explicit AudioScheduledSourceNode(std::shared_ptr context); + explicit AudioScheduledSourceNode(const std::shared_ptr &context); virtual void start(double when); virtual void stop(double when); @@ -41,13 +41,17 @@ class AudioScheduledSourceNode : public AudioNode { void disable() override; + /// @note JS Thread only. + /// Thread safe, because does not access state of the node. + void unregisterOnEndedCallback(uint64_t callbackId); + protected: double startTime_; double stopTime_; PlaybackState playbackState_; - std::atomic onEndedCallbackId_ = 0; + uint64_t onEndedCallbackId_ = 0; std::shared_ptr audioEventHandlerRegistry_; void updatePlaybackInfo( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDestructor.hpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDestructor.hpp new file mode 100644 index 000000000..95e183a65 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioDestructor.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace audioapi { + +class AudioNode; + +#define AUDIO_NODE_DESTRUCTOR_SPSC_OPTIONS \ + std::shared_ptr, channels::spsc::OverflowStrategy::WAIT_ON_FULL, \ + channels::spsc::WaitStrategy::ATOMIC_WAIT + +/// @brief A generic class to offload object destruction to a separate thread. +/// @tparam T The type of object to be destroyed. +template +class AudioDestructor { + public: + AudioDestructor() : isExiting_(false) { + auto [sender, receiver] = channels::spsc::channel< + std::shared_ptr, + channels::spsc::OverflowStrategy::WAIT_ON_FULL, + channels::spsc::WaitStrategy::ATOMIC_WAIT>(kChannelCapacity); + sender_ = std::move(sender); + workerHandle_ = std::thread(&AudioDestructor::process, this, std::move(receiver)); + } + ~AudioDestructor() { + isExiting_.store(true, std::memory_order_release); + + // We need to send a nullptr to unblock the receiver + sender_.send(nullptr); + if (workerHandle_.joinable()) { + workerHandle_.join(); + } + } + + /// @brief Adds an audio object to the deconstruction queue. + /// @param object The audio object to be deconstructed. + /// @return True if the node was successfully added, false otherwise. + /// @note audio object does NOT get moved out if it is not successfully added. + bool tryAddForDeconstruction(std::shared_ptr &&object) { + return sender_.try_send(std::move(object)) == channels::spsc::ResponseStatus::SUCCESS; + } + + private: + static constexpr size_t kChannelCapacity = 1024; + + std::thread workerHandle_; + std::atomic isExiting_; + + using SenderType = channels::spsc::Sender< + std::shared_ptr, + channels::spsc::OverflowStrategy::WAIT_ON_FULL, + channels::spsc::WaitStrategy::ATOMIC_WAIT>; + + using ReceiverType = channels::spsc::Receiver< + std::shared_ptr, + channels::spsc::OverflowStrategy::WAIT_ON_FULL, + channels::spsc::WaitStrategy::ATOMIC_WAIT>; + + SenderType sender_; + + /// @brief Processes audio objects for deconstruction. + /// @param receiver The receiver channel for incoming audio objects. + void process(ReceiverType &&receiver) { + auto rcv = std::move(receiver); + while (!isExiting_.load(std::memory_order_acquire)) { + rcv.receive(); + } + } +}; + +#undef AUDIO_NODE_DESTRUCTOR_SPSC_OPTIONS + +} // 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 new file mode 100644 index 000000000..1a00893e4 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace audioapi { + +AudioGraphManager::Event::Event(Event &&other) { *this = std::move(other); } + +AudioGraphManager::Event &AudioGraphManager::Event::operator=(Event &&other) { + if (this != &other) { + this->~Event(); + type = other.type; + payloadType = other.payloadType; + switch (payloadType) { + case EventPayloadType::NODES: payload.nodes = std::move(other.payload.nodes); break; + case EventPayloadType::PARAMS: payload.params = std::move(other.payload.params); break; + case EventPayloadType::SOURCE_NODE: payload.sourceNode = std::move(other.payload.sourceNode); break; + case EventPayloadType::AUDIO_PARAM: payload.audioParam = std::move(other.payload.audioParam); break; + case EventPayloadType::NODE: payload.node = std::move(other.payload.node); break; + default: break; + } + } + return *this; +} + +AudioGraphManager::Event::~Event() { + switch (payloadType) { + case EventPayloadType::NODES: payload.nodes.from.reset(); payload.nodes.to.reset(); break; + case EventPayloadType::PARAMS: payload.params.from.reset(); payload.params.to.reset(); break; + case EventPayloadType::SOURCE_NODE: payload.sourceNode.reset(); break; + case EventPayloadType::AUDIO_PARAM: payload.audioParam.reset(); break; + case EventPayloadType::NODE: payload.node.reset(); break; + } +} + +AudioGraphManager::AudioGraphManager() { + sourceNodes_.reserve(kInitialCapacity); + processingNodes_.reserve(kInitialCapacity); + audioParams_.reserve(kInitialCapacity); + audioBuses_.reserve(kInitialCapacity); + + auto channel_pair = channels::spsc::channel< + std::unique_ptr, + channels::spsc::OverflowStrategy::WAIT_ON_FULL, + channels::spsc::WaitStrategy::BUSY_LOOP>(kChannelCapacity); + + sender_ = std::move(channel_pair.first); + receiver_ = std::move(channel_pair.second); +} + +AudioGraphManager::~AudioGraphManager() { + cleanup(); +} + + +void AudioGraphManager::addPendingNodeConnection(const std::shared_ptr &from, const std::shared_ptr &to, ConnectionType type) { + auto event = std::make_unique(); + event->type = type; + event->payloadType = EventPayloadType::NODES; + event->payload.nodes.from = from; + event->payload.nodes.to = to; + sender_.send(std::move(event)); +} + +void AudioGraphManager::addPendingParamConnection(const std::shared_ptr &from, const std::shared_ptr &to, ConnectionType type) { + auto event = std::make_unique(); + event->type = type; + event->payloadType = EventPayloadType::PARAMS; + event->payload.params.from = from; + event->payload.params.to = to; + sender_.send(std::move(event)); +} + +void AudioGraphManager::addProcessingNode(const std::shared_ptr &node) { + auto event = std::make_unique(); + event->type = ConnectionType::ADD; + event->payloadType = EventPayloadType::NODE; + event->payload.node = node; + sender_.send(std::move(event)); +} + +void AudioGraphManager::addSourceNode(const std::shared_ptr &node) { + auto event = std::make_unique(); + event->type = ConnectionType::ADD; + event->payloadType = EventPayloadType::SOURCE_NODE; + event->payload.sourceNode = node; + sender_.send(std::move(event)); +} + +void AudioGraphManager::addAudioParam(const std::shared_ptr ¶m) { + auto event = std::make_unique(); + event->type = ConnectionType::ADD; + event->payloadType = EventPayloadType::AUDIO_PARAM; + event->payload.audioParam = param; + sender_.send(std::move(event)); +} + +void AudioGraphManager::addAudioBusForDestruction(std::shared_ptr bus) { + // direct access because this is called from the Audio thread + audioBuses_.push_back(std::move(bus)); +} + +void AudioGraphManager::preProcessGraph() { + settlePendingConnections(); + prepareForDestruction(sourceNodes_, nodeDestructor_); + prepareForDestruction(processingNodes_, nodeDestructor_); + prepareForDestruction(audioBuses_, audioBusDestructor_); +} + +void AudioGraphManager::settlePendingConnections() { + std::unique_ptr value; + while (receiver_.try_receive(value) != channels::spsc::ResponseStatus::CHANNEL_EMPTY) { + switch (value->type) { + case ConnectionType::CONNECT: handleConnectEvent(std::move(value)); break; + case ConnectionType::DISCONNECT: handleDisconnectEvent(std::move(value)); break; + case ConnectionType::DISCONNECT_ALL: handleDisconnectAllEvent(std::move(value)); break; + case ConnectionType::ADD: handleAddToDeconstructionEvent(std::move(value)); break; + } + } +} + +void AudioGraphManager::handleConnectEvent(std::unique_ptr event) { + if (event->payloadType == EventPayloadType::NODES) event->payload.nodes.from->connectNode(event->payload.nodes.to); + else if (event->payloadType == EventPayloadType::PARAMS) event->payload.params.from->connectParam(event->payload.params.to); +} + +void AudioGraphManager::handleDisconnectEvent(std::unique_ptr event) { + if (event->payloadType == EventPayloadType::NODES) event->payload.nodes.from->disconnectNode(event->payload.nodes.to); + else if (event->payloadType == EventPayloadType::PARAMS) event->payload.params.from->disconnectParam(event->payload.params.to); +} + +void AudioGraphManager::handleDisconnectAllEvent(std::unique_ptr event) { + for (auto it = event->payload.nodes.from->outputNodes_.begin(); it != event->payload.nodes.from->outputNodes_.end();) { + auto next = std::next(it); + event->payload.nodes.from->disconnectNode(*it); + it = next; + } +} + +void AudioGraphManager::handleAddToDeconstructionEvent(std::unique_ptr event) { + switch (event->payloadType) { + case EventPayloadType::NODE: processingNodes_.push_back(event->payload.node); break; + case EventPayloadType::SOURCE_NODE: sourceNodes_.push_back(event->payload.sourceNode); break; + case EventPayloadType::AUDIO_PARAM: audioParams_.push_back(event->payload.audioParam); break; + default: break; + } +} + +void AudioGraphManager::cleanup() { + for (auto &node : sourceNodes_) node->cleanup(); + for (auto &node : processingNodes_) node->cleanup(); + sourceNodes_.clear(); + processingNodes_.clear(); + audioParams_.clear(); + audioBuses_.clear(); +} +} // namespace audioapi 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 new file mode 100644 index 000000000..d9de2ccae --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h @@ -0,0 +1,181 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace audioapi { + +class AudioNode; +class AudioScheduledSourceNode; +class AudioParam; +class AudioBus; + +#define AUDIO_GRAPH_MANAGER_SPSC_OPTIONS \ + std::unique_ptr, channels::spsc::OverflowStrategy::WAIT_ON_FULL, \ + channels::spsc::WaitStrategy::BUSY_LOOP + +class AudioGraphManager { + public: + enum class ConnectionType { CONNECT, DISCONNECT, DISCONNECT_ALL, ADD }; + typedef ConnectionType EventType; + enum class EventPayloadType { NODES, PARAMS, SOURCE_NODE, AUDIO_PARAM, NODE }; + + union EventPayload { + struct { + std::shared_ptr from; + std::shared_ptr to; + } nodes; + struct { + std::shared_ptr from; + std::shared_ptr to; + } params; + std::shared_ptr sourceNode; + std::shared_ptr audioParam; + std::shared_ptr node; + + EventPayload() : nodes{} {} + ~EventPayload() {} + }; + + struct Event { + EventType type; + EventPayloadType payloadType; + EventPayload payload; + + Event(Event &&other); + Event &operator=(Event &&other); + Event() : type(ConnectionType::CONNECT), payloadType(EventPayloadType::NODES), payload() {} + ~Event(); + }; + + AudioGraphManager(); + ~AudioGraphManager(); + + void preProcessGraph(); + + void addPendingNodeConnection( + const std::shared_ptr &from, + const std::shared_ptr &to, + ConnectionType type); + void addPendingParamConnection( + const std::shared_ptr &from, + const std::shared_ptr &to, + ConnectionType type); + void addProcessingNode(const std::shared_ptr &node); + void addSourceNode(const std::shared_ptr &node); + void addAudioParam(const std::shared_ptr ¶m); + + /// @brief Adds an audio buffer to the manager for destruction. + /// @note Called directly from the Audio thread (bypasses SPSC). + void addAudioBusForDestruction(std::shared_ptr bus); + + void cleanup(); + + private: + AudioDestructor nodeDestructor_; + AudioDestructor audioBusDestructor_; + + static constexpr size_t kInitialCapacity = 32; + static constexpr size_t kChannelCapacity = 1024; + + std::vector> sourceNodes_; + std::vector> processingNodes_; + std::vector> audioParams_; + std::vector> audioBuses_; + + channels::spsc::Receiver receiver_; + channels::spsc::Sender sender_; + + void settlePendingConnections(); + void handleConnectEvent(std::unique_ptr event); + void handleDisconnectEvent(std::unique_ptr event); + void handleDisconnectAllEvent(std::unique_ptr event); + void handleAddToDeconstructionEvent(std::unique_ptr event); + + inline static bool canBeDestructed(const std::shared_ptr &bus) { + return bus.use_count() == 1; + } + + template + requires std::derived_from + inline static bool canBeDestructed(std::shared_ptr const &node) { + // If the node is an AudioScheduledSourceNode, we need to check if it is + // playing + if constexpr (std::is_base_of_v) { + return node.use_count() == 1 && (node->isUnscheduled() || node->isFinished()); + } else if (node->requiresTailProcessing()) { + // if the node requires tail processing, its own implementation handles disabling it at the right time + return node.use_count() == 1 && !node->isEnabled(); + } + return node.use_count() == 1; + } + + template + requires std::convertible_to + void prepareForDestruction( + std::vector> &vec, + AudioDestructor &audioDestructor) { + if (vec.empty()) { + return; + } + + /// An example of input-output + /// for simplicity we will be considering vector where each value represents + /// use_count() of an element vec = [1, 2, 1, 3, 1] our end result will be vec + /// = [2, 3, 1, 1, 1] After this operation all nodes with use_count() == 1 + /// will be at the end and we will try to send them After sending, we will + /// only keep nodes with use_count() > 1 or which failed vec = [2, 3, failed, + /// sent, sent] // failed will be always before sents vec = [2, 3, failed] and + /// we resize + /// @note if there are no nodes with use_count() == 1 `begin` will be equal to + /// vec.size() + /// @note if all nodes have use_count() == 1 `begin` will be 0 + + int begin = 0; + int end = vec.size() - 1; // can be -1 (edge case) + + while (begin <= end) { + while (begin < end && AudioGraphManager::canBeDestructed(vec[end])) { + end--; + } + if (AudioGraphManager::canBeDestructed(vec[begin])) { + std::swap(vec[begin], vec[end]); + end--; + } + begin++; + } + + for (int i = begin; i < vec.size(); i++) { + if constexpr (std::derived_from) { + if (vec[i]) + vec[i]->cleanup(); + } + + /// If we fail to add we can't safely remove the node from the vector + /// so we swap it and advance begin cursor + /// @note vec[i] does NOT get moved out if it is not successfully added. + if (!audioDestructor.tryAddForDeconstruction(std::move(vec[i]))) { + std::swap(vec[i], vec[begin]); + begin++; + } + } + + if (begin < vec.size()) { + // it does not reallocate if newer size is < current size + vec.resize(begin); + } + } +}; + +#undef AUDIO_GRAPH_MANAGER_SPSC_OPTIONS + +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp deleted file mode 100644 index 1243da3b7..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include - -namespace audioapi { - -AudioNodeDestructor::AudioNodeDestructor() { - isExiting_.store(false, std::memory_order_release); - auto [sender, receiver] = channels::spsc::channel< - std::shared_ptr, - channels::spsc::OverflowStrategy::WAIT_ON_FULL, - channels::spsc::WaitStrategy::ATOMIC_WAIT>(kChannelCapacity); - sender_ = std::move(sender); - workerHandle_ = std::thread(&AudioNodeDestructor::process, this, std::move(receiver)); -} - -AudioNodeDestructor::~AudioNodeDestructor() { - isExiting_.store(true, std::memory_order_release); - - // We need to send a nullptr to unblock the receiver - sender_.send(nullptr); - if (workerHandle_.joinable()) { - workerHandle_.join(); - } -} - -bool AudioNodeDestructor::tryAddNodeForDeconstruction(std::shared_ptr &&node) { - return sender_.try_send(std::move(node)) == channels::spsc::ResponseStatus::SUCCESS; -} - -void AudioNodeDestructor::process( - channels::spsc::Receiver< - std::shared_ptr, - channels::spsc::OverflowStrategy::WAIT_ON_FULL, - channels::spsc::WaitStrategy::ATOMIC_WAIT> &&receiver) { - auto rcv = std::move(receiver); - while (!isExiting_.load(std::memory_order_acquire)) { - rcv.receive(); - } -} - -} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.h deleted file mode 100644 index 95e8a7a3f..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeDestructor.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace audioapi { - -class AudioNode; - -#define AUDIO_NODE_DESTRUCTOR_SPSC_OPTIONS \ - std::shared_ptr, channels::spsc::OverflowStrategy::WAIT_ON_FULL, \ - channels::spsc::WaitStrategy::ATOMIC_WAIT - -class AudioNodeDestructor { - public: - AudioNodeDestructor(); - ~AudioNodeDestructor(); - - /// @brief Adds a node to the deconstruction queue. - /// @param node The audio node to be deconstructed. - /// @return True if the node was successfully added, false otherwise. - /// @note node does NOT get moved out if it is not successfully added. - bool tryAddNodeForDeconstruction(std::shared_ptr &&node); - - private: - static constexpr size_t kChannelCapacity = 1024; - - std::thread workerHandle_; - std::atomic isExiting_; - - channels::spsc::Sender sender_; - - /// @brief Processes audio nodes for deconstruction. - /// @param receiver The receiver channel for incoming audio nodes. - void process(channels::spsc::Receiver &&receiver); -}; - -#undef AUDIO_NODE_DESTRUCTOR_SPSC_OPTIONS - -} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp deleted file mode 100644 index ca4a182de..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.cpp +++ /dev/null @@ -1,296 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace audioapi { - -AudioNodeManager::Event::Event(Event &&other) { - *this = std::move(other); -} - -AudioNodeManager::Event &AudioNodeManager::Event::operator=(Event &&other) { - if (this != &other) { - // Clean up current resources - this->~Event(); - - // Move resources from the other event - type = other.type; - payloadType = other.payloadType; - switch (payloadType) { - case EventPayloadType::NODES: - payload.nodes.from = std::move(other.payload.nodes.from); - payload.nodes.to = std::move(other.payload.nodes.to); - break; - case EventPayloadType::PARAMS: - payload.params.from = std::move(other.payload.params.from); - payload.params.to = std::move(other.payload.params.to); - break; - case EventPayloadType::SOURCE_NODE: - payload.sourceNode = std::move(other.payload.sourceNode); - break; - case EventPayloadType::AUDIO_PARAM: - payload.audioParam = std::move(other.payload.audioParam); - break; - case EventPayloadType::NODE: - payload.node = std::move(other.payload.node); - break; - - default: - break; - } - } - return *this; -} - -AudioNodeManager::Event::~Event() { - switch (payloadType) { - case EventPayloadType::NODES: - payload.nodes.from.~shared_ptr(); - payload.nodes.to.~shared_ptr(); - break; - case EventPayloadType::PARAMS: - payload.params.from.~shared_ptr(); - payload.params.to.~shared_ptr(); - break; - case EventPayloadType::SOURCE_NODE: - payload.sourceNode.~shared_ptr(); - break; - case EventPayloadType::AUDIO_PARAM: - payload.audioParam.~shared_ptr(); - break; - case EventPayloadType::NODE: - payload.node.~shared_ptr(); - break; - } -} - -AudioNodeManager::AudioNodeManager() { - sourceNodes_.reserve(kInitialCapacity); - processingNodes_.reserve(kInitialCapacity); - audioParams_.reserve(kInitialCapacity); - - auto channel_pair = channels::spsc::channel< - std::unique_ptr, - channels::spsc::OverflowStrategy::WAIT_ON_FULL, - channels::spsc::WaitStrategy::BUSY_LOOP>(kChannelCapacity); - - sender_ = std::move(channel_pair.first); - receiver_ = std::move(channel_pair.second); -} - -AudioNodeManager::~AudioNodeManager() { - cleanup(); -} - -void AudioNodeManager::addPendingNodeConnection( - const std::shared_ptr &from, - const std::shared_ptr &to, - ConnectionType type) { - auto event = std::make_unique(); - event->type = type; - event->payloadType = EventPayloadType::NODES; - event->payload.nodes.from = from; - event->payload.nodes.to = to; - - sender_.send(std::move(event)); -} - -void AudioNodeManager::addPendingParamConnection( - const std::shared_ptr &from, - const std::shared_ptr &to, - ConnectionType type) { - auto event = std::make_unique(); - event->type = type; - event->payloadType = EventPayloadType::PARAMS; - event->payload.params.from = from; - event->payload.params.to = to; - - sender_.send(std::move(event)); -} - -void AudioNodeManager::preProcessGraph() { - settlePendingConnections(); - prepareNodesForDestruction(sourceNodes_); - prepareNodesForDestruction(processingNodes_); -} - -void AudioNodeManager::addProcessingNode(const std::shared_ptr &node) { - auto event = std::make_unique(); - event->type = ConnectionType::ADD; - event->payloadType = EventPayloadType::NODE; - event->payload.node = node; - - sender_.send(std::move(event)); -} - -void AudioNodeManager::addSourceNode(const std::shared_ptr &node) { - auto event = std::make_unique(); - event->type = ConnectionType::ADD; - event->payloadType = EventPayloadType::SOURCE_NODE; - event->payload.sourceNode = node; - - sender_.send(std::move(event)); -} - -void AudioNodeManager::addAudioParam(const std::shared_ptr ¶m) { - auto event = std::make_unique(); - event->type = ConnectionType::ADD; - event->payloadType = EventPayloadType::AUDIO_PARAM; - event->payload.audioParam = param; - - sender_.send(std::move(event)); -} - -void AudioNodeManager::settlePendingConnections() { - std::unique_ptr value; - while (receiver_.try_receive(value) != channels::spsc::ResponseStatus::CHANNEL_EMPTY) { - switch (value->type) { - case ConnectionType::CONNECT: - handleConnectEvent(std::move(value)); - break; - case ConnectionType::DISCONNECT: - handleDisconnectEvent(std::move(value)); - break; - case ConnectionType::DISCONNECT_ALL: - handleDisconnectAllEvent(std::move(value)); - break; - case ConnectionType::ADD: - handleAddToDeconstructionEvent(std::move(value)); - break; - } - } -} - -void AudioNodeManager::handleConnectEvent(std::unique_ptr event) { - if (event->payloadType == EventPayloadType::NODES) { - event->payload.nodes.from->connectNode(event->payload.nodes.to); - } else if (event->payloadType == EventPayloadType::PARAMS) { - event->payload.params.from->connectParam(event->payload.params.to); - } else { - assert(false && "Invalid payload type for connect event"); - } -} - -void AudioNodeManager::handleDisconnectEvent(std::unique_ptr event) { - if (event->payloadType == EventPayloadType::NODES) { - event->payload.nodes.from->disconnectNode(event->payload.nodes.to); - } else if (event->payloadType == EventPayloadType::PARAMS) { - event->payload.params.from->disconnectParam(event->payload.params.to); - } else { - assert(false && "Invalid payload type for disconnect event"); - } -} - -void AudioNodeManager::handleDisconnectAllEvent(std::unique_ptr event) { - assert(event->payloadType == EventPayloadType::NODES); - for (auto it = event->payload.nodes.from->outputNodes_.begin(); - it != event->payload.nodes.from->outputNodes_.end();) { - auto next = std::next(it); - event->payload.nodes.from->disconnectNode(*it); - it = next; - } -} - -void AudioNodeManager::handleAddToDeconstructionEvent(std::unique_ptr event) { - switch (event->payloadType) { - case EventPayloadType::NODE: - processingNodes_.push_back(event->payload.node); - break; - case EventPayloadType::SOURCE_NODE: - sourceNodes_.push_back(event->payload.sourceNode); - break; - case EventPayloadType::AUDIO_PARAM: - audioParams_.push_back(event->payload.audioParam); - break; - default: - assert(false && "Unknown event payload type"); - } -} - -template -inline bool AudioNodeManager::nodeCanBeDestructed(std::shared_ptr const &node) { - // If the node is an AudioScheduledSourceNode, we need to check if it is - // playing - if constexpr (std::is_base_of_v) { - return node.use_count() == 1 && (node->isUnscheduled() || node->isFinished()); - } else if (node->requiresTailProcessing()) { - // if the node requires tail processing, its own implementation handles disabling it at the right time - return node.use_count() == 1 && !node->isEnabled(); - } - return node.use_count() == 1; -} - -template -void AudioNodeManager::prepareNodesForDestruction(std::vector> &vec) { - if (vec.empty()) { - return; - } - /// An example of input-output - /// for simplicity we will be considering vector where each value represents - /// use_count() of an element vec = [1, 2, 1, 3, 1] our end result will be vec - /// = [2, 3, 1, 1, 1] After this operation all nodes with use_count() == 1 - /// will be at the end and we will try to send them After sending, we will - /// only keep nodes with use_count() > 1 or which failed vec = [2, 3, failed, - /// sent, sent] // failed will be always before sents vec = [2, 3, failed] and - /// we resize - /// @note if there are no nodes with use_count() == 1 `begin` will be equal to - /// vec.size() - /// @note if all nodes have use_count() == 1 `begin` will be 0 - - int begin = 0; - int end = vec.size() - 1; // can be -1 (edge case) - - // Moves all nodes with use_count() == 1 to the end - // nodes in range [begin, vec.size()) should be deleted - // so new size of the vector will be `begin` - while (begin <= end) { - while (begin < end && AudioNodeManager::nodeCanBeDestructed(vec[end])) { - end--; - } - if (AudioNodeManager::nodeCanBeDestructed(vec[begin])) { - std::swap(vec[begin], vec[end]); - end--; - } - begin++; - } - - for (int i = begin; i < vec.size(); i++) { - if (vec[i]) - vec[i]->cleanup(); - - /// If we fail to add we can't safely remove the node from the vector - /// so we swap it and advance begin cursor - /// @note vec[i] does NOT get moved out if it is not successfully added. - if (!nodeDeconstructor_.tryAddNodeForDeconstruction(std::move(vec[i]))) { - std::swap(vec[i], vec[begin]); - begin++; - } - } - if (begin < vec.size()) { - // it does not realocate if newer size is < current size - vec.resize(begin); - } -} - -void AudioNodeManager::cleanup() { - for (auto it = sourceNodes_.begin(), end = sourceNodes_.end(); it != end; ++it) { - it->get()->cleanup(); - } - - for (auto it = processingNodes_.begin(), end = processingNodes_.end(); it != end; ++it) { - it->get()->cleanup(); - } - - sourceNodes_.clear(); - processingNodes_.clear(); - audioParams_.clear(); -} - -} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h deleted file mode 100644 index 4a9a0c652..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioNodeManager.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -#include - -namespace audioapi { - -class AudioNode; -class AudioScheduledSourceNode; -class AudioParam; - -#define AUDIO_NODE_MANAGER_SPSC_OPTIONS \ - std::unique_ptr, channels::spsc::OverflowStrategy::WAIT_ON_FULL, \ - channels::spsc::WaitStrategy::BUSY_LOOP - -class AudioNodeManager { - public: - enum class ConnectionType { CONNECT, DISCONNECT, DISCONNECT_ALL, ADD }; - typedef ConnectionType EventType; // for backwards compatibility - enum class EventPayloadType { NODES, PARAMS, SOURCE_NODE, AUDIO_PARAM, NODE }; - union EventPayload { - struct { - std::shared_ptr from; - std::shared_ptr to; - } nodes; - struct { - std::shared_ptr from; - std::shared_ptr to; - } params; - std::shared_ptr sourceNode; - std::shared_ptr audioParam; - std::shared_ptr node; - - // Default constructor that initializes the first member - EventPayload() : nodes{} {} - - // Destructor - we'll handle cleanup explicitly in Event destructor - ~EventPayload() {} - }; - struct Event { - EventType type; - EventPayloadType payloadType; - EventPayload payload; - - Event(Event &&other); - Event &operator=(Event &&other); - Event() : type(ConnectionType::CONNECT), payloadType(EventPayloadType::NODES), payload() {} - ~Event(); - }; - - AudioNodeManager(); - ~AudioNodeManager(); - - void preProcessGraph(); - - /// @brief Adds a pending connection between two audio nodes. - /// @param from The source audio node. - /// @param to The destination audio node. - /// @param type The type of connection (connect/disconnect). - /// @note Should be only used from JavaScript/HostObjects thread - void addPendingNodeConnection( - const std::shared_ptr &from, - const std::shared_ptr &to, - ConnectionType type); - - /// @brief Adds a pending connection between an audio node and an audio parameter. - /// @param from The source audio node. - /// @param to The destination audio parameter. - /// @param type The type of connection (connect/disconnect). - /// @note Should be only used from JavaScript/HostObjects thread - void addPendingParamConnection( - const std::shared_ptr &from, - const std::shared_ptr &to, - ConnectionType type); - - /// @brief Adds a processing node to the manager. - /// @param node The processing node to add. - /// @note Should be only used from JavaScript/HostObjects thread - void addProcessingNode(const std::shared_ptr &node); - - /// @brief Adds a source node to the manager. - /// @param node The source node to add. - /// @note Should be only used from JavaScript/HostObjects thread - void addSourceNode(const std::shared_ptr &node); - - /// @brief Adds an audio parameter to the manager. - /// @param param The audio parameter to add. - /// @note Should be only used from JavaScript/HostObjects thread - void addAudioParam(const std::shared_ptr ¶m); - - void cleanup(); - - private: - AudioNodeDestructor nodeDeconstructor_; - - /// @brief Initial capacity for various node types for deletion - /// @note Higher capacity decreases number of reallocations at runtime (can be easily adjusted to 128 if needed) - static constexpr size_t kInitialCapacity = 32; - - /// @brief Initial capacity for event passing channel - /// @note High value reduces wait time for sender (JavaScript/HostObjects thread here) - static constexpr size_t kChannelCapacity = 1024; - - std::vector> sourceNodes_; - std::vector> processingNodes_; - std::vector> audioParams_; - - channels::spsc::Receiver receiver_; - - channels::spsc::Sender sender_; - - void settlePendingConnections(); - void handleConnectEvent(std::unique_ptr event); - void handleDisconnectEvent(std::unique_ptr event); - void handleDisconnectAllEvent(std::unique_ptr event); - void handleAddToDeconstructionEvent(std::unique_ptr event); - - template - void prepareNodesForDestruction(std::vector> &vec); - - template - inline static bool nodeCanBeDestructed(std::shared_ptr const &node); -}; - -#undef AUDIO_NODE_MANAGER_SPSC_OPTIONS - -} // namespace audioapi 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 b600c2dc0..cb2de2596 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 @@ -18,7 +18,7 @@ AudioEventHandlerRegistry::~AudioEventHandlerRegistry() { uint64_t AudioEventHandlerRegistry::registerHandler( AudioEvent eventName, const std::shared_ptr &handler) { - uint64_t listenerId = listenerIdCounter_++; + uint64_t 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 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 9849f1c1c..436ac2c77 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 @@ -17,6 +17,10 @@ using namespace facebook; using EventValue = std::variant>; +/// @brief A registry for audio event handlers. +/// It allows registering, unregistering, and invoking event handlers for audio events. +/// State changes are performed only on the JS thread. +/// State access is thread-safe via the RN CallInvoker. class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry { public: explicit AudioEventHandlerRegistry( @@ -28,9 +32,14 @@ class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry { override; void unregisterHandler(AudioEvent eventName, uint64_t listenerId) override; + /// @brief Invokes the event handler(s) for a specific event with the provided event body. + /// @note Can be used from any thread. void invokeHandlerWithEventBody( AudioEvent eventName, const std::unordered_map &body) override; + + /// @brief Invokes a specific event handler by listener ID with the provided event body. + /// @note Can be used from any thread. void invokeHandlerWithEventBody( AudioEvent eventName, uint64_t listenerId, diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.cpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.cpp index 5276b434b..d226002d9 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.cpp @@ -84,6 +84,10 @@ size_t AudioBus::getSize() const { return size_; } +double AudioBus::getDuration() const { + return static_cast(size_) / sampleRate_; +} + AudioArray *AudioBus::getChannel(int index) const { return channels_[index].get(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.h b/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.h index ebcf34cf7..a71592d67 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/utils/AudioBus.h @@ -35,6 +35,7 @@ class AudioBus { [[nodiscard]] int getNumberOfChannels() const; [[nodiscard]] float getSampleRate() const; [[nodiscard]] size_t getSize() const; + [[nodiscard]] double getDuration() const; [[nodiscard]] AudioArray *getChannel(int index) const; [[nodiscard]] AudioArray *getChannelByType(int channelType) const; [[nodiscard]] std::shared_ptr getSharedChannel(int index) const; 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 0d47e5d8a..ed30bccb8 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,5 +1,6 @@ #pragma once +#include #include #include @@ -24,11 +25,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); } @@ -36,26 +39,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/test/src/OscillatorTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/OscillatorTest.cpp index 04151fc68..71de3fe2e 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/OscillatorTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/OscillatorTest.cpp @@ -18,6 +18,7 @@ class OscillatorTest : public ::testing::Test { eventRegistry = std::make_shared(); context = std::make_shared( 2, 5 * sampleRate, sampleRate, eventRegistry, RuntimeRegistry{}); + context->initialize(); } };