diff --git a/apps/common-app/src/examples/Streaming/Streaming.tsx b/apps/common-app/src/examples/Streaming/Streaming.tsx index b83009139..ab0393004 100644 --- a/apps/common-app/src/examples/Streaming/Streaming.tsx +++ b/apps/common-app/src/examples/Streaming/Streaming.tsx @@ -30,11 +30,8 @@ const Streaming: FC = () => { console.error('StreamerNode is already initialized'); return; } - streamerRef.current = aCtxRef.current.createStreamer(); + streamerRef.current = aCtxRef.current.createStreamer('https://liveradio.timesa.pl/2980-1.aac/playlist.m3u8'); - streamerRef.current.initialize( - 'https://liveradio.timesa.pl/2980-1.aac/playlist.m3u8' - ); streamerRef.current.connect(gainRef.current); gainRef.current.connect(aCtxRef.current.destination); streamerRef.current.start(aCtxRef.current.currentTime); diff --git a/packages/audiodocs/docs/guides/create-your-own-effect.mdx b/packages/audiodocs/docs/guides/create-your-own-effect.mdx index fbdcbe9c1..f882ccc27 100644 --- a/packages/audiodocs/docs/guides/create-your-own-effect.mdx +++ b/packages/audiodocs/docs/guides/create-your-own-effect.mdx @@ -77,7 +77,7 @@ namespace audioapi { MyProcessorNode::MyProcessorNode(const std::shared_ptr &context) //highlight-next-line : AudioNode(context), gain(0.5) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr MyProcessorNode::processNode(const std::shared_ptr &buffer, diff --git a/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp b/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp index b1ffb9581..033597f6c 100644 --- a/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp +++ b/packages/custom-node-generator/templates/basic/shared/MyProcessorNode.cpp @@ -5,7 +5,7 @@ namespace audioapi { MyProcessorNode::MyProcessorNode( const std::shared_ptr &context, ) : AudioNode(context) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioNodeHostObject.cpp index ae841ed4f..a33dd12b2 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 @@ -8,7 +8,13 @@ namespace audioapi { AudioNodeHostObject::AudioNodeHostObject(const std::shared_ptr &node, - const AudioNodeOptions &options) : node_(node) { + const AudioNodeOptions &options) + : node_(node), + numberOfInputs_(options.numberOfInputs), + numberOfOutputs_(options.numberOfOutputs), + channelCount_(options.channelCount), + channelCountMode_(options.channelCountMode), + channelInterpretation_(options.channelInterpretation) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfInputs), JSI_EXPORT_PROPERTY_GETTER(AudioNodeHostObject, numberOfOutputs), @@ -28,23 +34,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 {static_cast(node_->getChannelCount())}; + return {static_cast(channelCount_)}; } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelCountMode) { - return jsi::String::createFromUtf8(runtime, js_enum_parser::channelCountModeToString(node_->getChannelCountMode())); + return jsi::String::createFromUtf8(runtime, js_enum_parser::channelCountModeToString(channelCountMode_)); } JSI_PROPERTY_GETTER_IMPL(AudioNodeHostObject, channelInterpretation) { - return jsi::String::createFromUtf8(runtime, js_enum_parser::channelInterpretationToString(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 423f84b6d..25211cca0 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 @@ -30,5 +32,11 @@ class AudioNodeHostObject : public JsiHostObject { protected: std::shared_ptr node_; + + const int numberOfInputs_; + const int numberOfOutputs_; + size_t channelCount_; + const ChannelCountMode channelCountMode_; + const ChannelInterpretation channelInterpretation_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp index 6d501e25d..d3ed6c423 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.cpp @@ -9,7 +9,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), @@ -33,47 +33,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(); } @@ -82,23 +93,31 @@ JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, setValueCurveAtTime) { args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); auto rawValues = reinterpret_cast(arrayBuffer.data(runtime)); auto length = static_cast(arrayBuffer.size(runtime)); - auto values = std::make_unique(rawValues, length); + auto values = std::make_shared(rawValues, length); + + auto event = [param = param_, values, length, startTime = args[1].getNumber(), duration = args[2].getNumber()](BaseAudioContext &) { + param->setValueCurveAtTime(values, length, startTime, duration); + }; - double startTime = args[1].getNumber(); - double duration = args[2].getNumber(); - param_->setValueCurveAtTime(std::move(values), length, startTime, duration); + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelScheduledValues) { - double cancelTime = args[0].getNumber(); - param_->cancelScheduledValues(cancelTime); + auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) { + param->cancelScheduledValues(cancelTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioParamHostObject, cancelAndHoldAtTime) { - double cancelTime = args[0].getNumber(); - param_->cancelAndHoldAtTime(cancelTime); + auto event = [param = param_, cancelTime = args[0].getNumber()](BaseAudioContext &) { + param->cancelAndHoldAtTime(cancelTime); + }; + + param_->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioParamHostObject.h index 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/analysis/AnalyserNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp index 3edfd86ce..b621f829d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.cpp @@ -5,14 +5,19 @@ #include #include +#include namespace audioapi { AnalyserNodeHostObject::AnalyserNodeHostObject(const std::shared_ptr& context, const AnalyserOptions &options) - : AudioNodeHostObject(context->createAnalyser(options), options) { + : AudioNodeHostObject(context->createAnalyser(options), options), + fftSize_(options.fftSize), + minDecibels_(options.minDecibels), + maxDecibels_(options.maxDecibels), + smoothingTimeConstant_(options.smoothingTimeConstant), + windowType_(options.windowType) { addGetters( JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, fftSize), - JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, frequencyBinCount), JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, minDecibels), JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, maxDecibels), JSI_EXPORT_PROPERTY_GETTER(AnalyserNodeHostObject, smoothingTimeConstant), @@ -33,64 +38,74 @@ AnalyserNodeHostObject::AnalyserNodeHostObject(const std::shared_ptr(node_); - return {static_cast(analyserNode->getFftSize())}; -} - -JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, frequencyBinCount) { - auto analyserNode = std::static_pointer_cast(node_); - return {static_cast(analyserNode->getFrequencyBinCount())}; + return {fftSize_}; } JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, minDecibels) { - auto analyserNode = std::static_pointer_cast(node_); - return {analyserNode->getMinDecibels()}; + return {minDecibels_}; } JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, maxDecibels) { - auto analyserNode = std::static_pointer_cast(node_); - return {analyserNode->getMaxDecibels()}; + return {maxDecibels_}; } JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, smoothingTimeConstant) { - auto analyserNode = std::static_pointer_cast(node_); - return {analyserNode->getSmoothingTimeConstant()}; + return {smoothingTimeConstant_}; } JSI_PROPERTY_GETTER_IMPL(AnalyserNodeHostObject, window) { - auto analyserNode = std::static_pointer_cast(node_); - auto windowType = analyserNode->getWindowType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::windowTypeToString(windowType)); + return jsi::String::createFromUtf8(runtime, js_enum_parser::windowTypeToString(windowType_)); } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, fftSize) { auto analyserNode = std::static_pointer_cast(node_); + auto fftSize = static_cast(value.getNumber()); - analyserNode->setFftSize(fftSize); + auto event = [analyserNode, fftSize](BaseAudioContext&) { + analyserNode->setFftSize(fftSize); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + fftSize_ = fftSize; } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, minDecibels) { auto analyserNode = std::static_pointer_cast(node_); auto minDecibels = static_cast(value.getNumber()); - analyserNode->setMinDecibels(minDecibels); + auto event = [analyserNode, minDecibels](BaseAudioContext&) { + analyserNode->setMinDecibels(minDecibels); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + minDecibels_ = minDecibels; } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, maxDecibels) { auto analyserNode = std::static_pointer_cast(node_); auto maxDecibels = static_cast(value.getNumber()); - analyserNode->setMaxDecibels(maxDecibels); + auto event = [analyserNode, maxDecibels](BaseAudioContext&) { + analyserNode->setMaxDecibels(maxDecibels); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + maxDecibels_ = maxDecibels; } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, smoothingTimeConstant) { auto analyserNode = std::static_pointer_cast(node_); auto smoothingTimeConstant = static_cast(value.getNumber()); - analyserNode->setSmoothingTimeConstant(smoothingTimeConstant); + auto event = [analyserNode, smoothingTimeConstant](BaseAudioContext&) { + analyserNode->setSmoothingTimeConstant(smoothingTimeConstant); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + smoothingTimeConstant_ = smoothingTimeConstant; } JSI_PROPERTY_SETTER_IMPL(AnalyserNodeHostObject, window) { auto analyserNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - analyserNode->setWindowType(js_enum_parser::windowTypeFromString(type)); + auto windowType = js_enum_parser::windowTypeFromString(value.asString(runtime).utf8(runtime)); + auto event = [analyserNode, windowType](BaseAudioContext&) { + analyserNode->setWindowType(windowType); + }; + analyserNode->scheduleAudioEvent(std::move(event)); + windowType_ = windowType; } JSI_HOST_FUNCTION_IMPL(AnalyserNodeHostObject, getFloatFrequencyData) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h index 6b80d6d2c..811480b8c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/analysis/AnalyserNodeHostObject.h @@ -1,10 +1,10 @@ #pragma once #include +#include #include #include -#include namespace audioapi { using namespace facebook; @@ -19,7 +19,6 @@ class AnalyserNodeHostObject : public AudioNodeHostObject { const AnalyserOptions &options); JSI_PROPERTY_GETTER_DECL(fftSize); - JSI_PROPERTY_GETTER_DECL(frequencyBinCount); JSI_PROPERTY_GETTER_DECL(minDecibels); JSI_PROPERTY_GETTER_DECL(maxDecibels); JSI_PROPERTY_GETTER_DECL(smoothingTimeConstant); @@ -35,6 +34,13 @@ class AnalyserNodeHostObject : public AudioNodeHostObject { JSI_HOST_FUNCTION_DECL(getByteFrequencyData); JSI_HOST_FUNCTION_DECL(getFloatTimeDomainData); JSI_HOST_FUNCTION_DECL(getByteTimeDomainData); + + private: + int fftSize_; + float minDecibels_; + float maxDecibels_; + float smoothingTimeConstant_; + WindowType windowType_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp index b2e785530..45d88869d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.cpp @@ -7,12 +7,19 @@ #include #include +#include namespace audioapi { BiquadFilterNodeHostObject::BiquadFilterNodeHostObject(const std::shared_ptr& context, const BiquadFilterOptions &options) - : AudioNodeHostObject(context->createBiquadFilter(options), options) { + : AudioNodeHostObject(context->createBiquadFilter(options), options), type_(options.type) { + auto biquadFilterNode = std::static_pointer_cast(node_); + frequencyParam_ = std::make_shared(biquadFilterNode->getFrequencyParam()); + detuneParam_ = std::make_shared(biquadFilterNode->getDetuneParam()); + QParam_ = std::make_shared(biquadFilterNode->getQParam()); + gainParam_ = std::make_shared(biquadFilterNode->getGainParam()); + addGetters( JSI_EXPORT_PROPERTY_GETTER(BiquadFilterNodeHostObject, frequency), JSI_EXPORT_PROPERTY_GETTER(BiquadFilterNodeHostObject, detune), @@ -26,40 +33,34 @@ BiquadFilterNodeHostObject::BiquadFilterNodeHostObject(const std::shared_ptr(node_); - auto frequencyParam_ = - std::make_shared(biquadFilterNode->getFrequencyParam()); return jsi::Object::createFromHostObject(runtime, frequencyParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, detune) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto detuneParam_ = std::make_shared(biquadFilterNode->getDetuneParam()); return jsi::Object::createFromHostObject(runtime, detuneParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, Q) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto QParam_ = std::make_shared(biquadFilterNode->getQParam()); return jsi::Object::createFromHostObject(runtime, QParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, gain) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto gainParam_ = std::make_shared(biquadFilterNode->getGainParam()); return jsi::Object::createFromHostObject(runtime, gainParam_); } JSI_PROPERTY_GETTER_IMPL(BiquadFilterNodeHostObject, type) { - auto biquadFilterNode = std::static_pointer_cast(node_); - auto type = biquadFilterNode->getType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::filterTypeToString(type)); + return jsi::String::createFromUtf8(runtime, js_enum_parser::filterTypeToString(type_)); } JSI_PROPERTY_SETTER_IMPL(BiquadFilterNodeHostObject, type) { auto biquadFilterNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - biquadFilterNode->setType(js_enum_parser::filterTypeFromString(type)); + + auto type = js_enum_parser::filterTypeFromString(value.asString(runtime).utf8(runtime)); + auto event = [biquadFilterNode, type](BaseAudioContext&) { + biquadFilterNode->setType(type); + }; + biquadFilterNode->scheduleAudioEvent(std::move(event)); + type_ = type; } JSI_HOST_FUNCTION_IMPL(BiquadFilterNodeHostObject, getFrequencyResponse) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h index b2a36802d..2071d6d0b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/BiquadFilterNodeHostObject.h @@ -11,6 +11,7 @@ using namespace facebook; struct BiquadFilterOptions; class BaseAudioContext; +class AudioParamHostObject; class BiquadFilterNodeHostObject : public AudioNodeHostObject { public: @@ -27,5 +28,13 @@ class BiquadFilterNodeHostObject : public AudioNodeHostObject { JSI_PROPERTY_SETTER_DECL(type); JSI_HOST_FUNCTION_DECL(getFrequencyResponse); + + private: + std::shared_ptr frequencyParam_; + std::shared_ptr detuneParam_; + std::shared_ptr QParam_; + std::shared_ptr gainParam_; + + BiquadFilterType type_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp index d6f72a4c2..4be5b697a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.cpp @@ -1,52 +1,55 @@ #include #include -#include #include #include +#include #include +#include namespace audioapi { -ConvolverNodeHostObject::ConvolverNodeHostObject(const std::shared_ptr& context, const ConvolverOptions &options) - : AudioNodeHostObject(context->createConvolver(options), options) { - addGetters( - JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, normalize), - JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, buffer)); +ConvolverNodeHostObject::ConvolverNodeHostObject( + const std::shared_ptr &context, + const ConvolverOptions &options) + : AudioNodeHostObject(context->createConvolver(options), options), + normalize_(!options.disableNormalization) { + addGetters(JSI_EXPORT_PROPERTY_GETTER(ConvolverNodeHostObject, normalize)); addSetters(JSI_EXPORT_PROPERTY_SETTER(ConvolverNodeHostObject, normalize)); addFunctions(JSI_EXPORT_FUNCTION(ConvolverNodeHostObject, setBuffer)); } JSI_PROPERTY_GETTER_IMPL(ConvolverNodeHostObject, normalize) { - auto convolverNode = std::static_pointer_cast(node_); - return {convolverNode->getNormalize_()}; -} - -JSI_PROPERTY_GETTER_IMPL(ConvolverNodeHostObject, buffer) { - auto convolverNode = std::static_pointer_cast(node_); - auto buffer = convolverNode->getBuffer(); - auto bufferHostObject = std::make_shared(buffer); - auto jsiObject = jsi::Object::createFromHostObject(runtime, bufferHostObject); - jsiObject.setExternalMemoryPressure(runtime, bufferHostObject->getSizeInBytes() + 16); - return jsiObject; + return jsi::Value(normalize_); } JSI_PROPERTY_SETTER_IMPL(ConvolverNodeHostObject, normalize) { auto convolverNode = std::static_pointer_cast(node_); - convolverNode->setNormalize(value.getBool()); + auto normalize = value.getBool(); + + auto event = [convolverNode, normalize](BaseAudioContext &) { + convolverNode->setNormalize(normalize); + }; + convolverNode->scheduleAudioEvent(std::move(event)); + normalize_ = normalize; } JSI_HOST_FUNCTION_IMPL(ConvolverNodeHostObject, setBuffer) { auto convolverNode = std::static_pointer_cast(node_); - if (args[0].isUndefined()) { - convolverNode->setBuffer(nullptr); - return jsi::Value::undefined(); + + std::shared_ptr copiedBuffer; + + if (args[0].isObject()) { + auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); + thisValue.asObject(runtime).setExternalMemoryPressure( + runtime, bufferHostObject->getSizeInBytes()); + copiedBuffer = std::make_shared(*bufferHostObject->audioBuffer_); } + auto event = [convolverNode, copiedBuffer](BaseAudioContext &) { + convolverNode->setBuffer(copiedBuffer); + }; + convolverNode->scheduleAudioEvent(std::move(event)); - auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - convolverNode->setBuffer(bufferHostObject->audioBuffer_); - thisValue.asObject(runtime).setExternalMemoryPressure( - runtime, bufferHostObject->getSizeInBytes() + 16); return jsi::Value::undefined(); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h index a4366c973..650b29373 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/ConvolverNodeHostObject.h @@ -16,8 +16,10 @@ class ConvolverNodeHostObject : public AudioNodeHostObject { const std::shared_ptr &context, const ConvolverOptions &options); JSI_PROPERTY_GETTER_DECL(normalize); - JSI_PROPERTY_GETTER_DECL(buffer); JSI_PROPERTY_SETTER_DECL(normalize); JSI_HOST_FUNCTION_DECL(setBuffer); + + private: + bool normalize_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.cpp index 17929e069..da7330987 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 @@ -10,23 +10,19 @@ namespace audioapi { DelayNodeHostObject::DelayNodeHostObject(const std::shared_ptr& context, const DelayOptions &options) : AudioNodeHostObject(context->createDelay(options), options) { + auto delayNode = std::static_pointer_cast(node_); + delayTimeParam_ = std::make_shared(delayNode->getDelayTimeParam()); addGetters(JSI_EXPORT_PROPERTY_GETTER(DelayNodeHostObject, delayTime)); } -size_t DelayNodeHostObject::getSizeInBytes() const { - auto delayNode = std::static_pointer_cast(node_); - auto base = sizeof(float) * delayNode->getDelayTimeParam()->getMaxValue(); - if (std::shared_ptr context = delayNode->context_.lock()) { - return base * context->getSampleRate(); - } else { - return base * 44100; // Fallback to common sample rate - } +JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { + return jsi::Object::createFromHostObject(runtime, delayTimeParam_); } -JSI_PROPERTY_GETTER_IMPL(DelayNodeHostObject, delayTime) { +size_t DelayNodeHostObject::getSizeInBytes() const { auto delayNode = std::static_pointer_cast(node_); - auto delayTimeParam = std::make_shared(delayNode->getDelayTimeParam()); - return jsi::Object::createFromHostObject(runtime, delayTimeParam); + auto base = sizeof(float) * delayNode->getDelayTimeParam()->getMaxValue(); + return base * delayNode->getContextSampleRate(); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/DelayNodeHostObject.h index 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 4f9c040ed..237ec7551 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.cpp @@ -12,13 +12,14 @@ GainNodeHostObject::GainNodeHostObject( const std::shared_ptr &context, const GainOptions &options) : AudioNodeHostObject(context->createGain(options), options) { + auto gainNode = std::static_pointer_cast(node_); + gainParam_ = std::make_shared(gainNode->getGainParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(GainNodeHostObject, gain)); } JSI_PROPERTY_GETTER_IMPL(GainNodeHostObject, gain) { - auto gainNode = std::static_pointer_cast(node_); - auto gainParam = std::make_shared(gainNode->getGainParam()); - return jsi::Object::createFromHostObject(runtime, gainParam); + return jsi::Object::createFromHostObject(runtime, gainParam_); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/GainNodeHostObject.h index 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/effects/StereoPannerNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp index 5170659ef..a5f10016d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.cpp @@ -12,12 +12,13 @@ StereoPannerNodeHostObject::StereoPannerNodeHostObject( const std::shared_ptr &context, const StereoPannerOptions &options) : AudioNodeHostObject(context->createStereoPanner(options), options) { + auto stereoPannerNode = std::static_pointer_cast(node_); + panParam_ = std::make_shared(stereoPannerNode->getPanParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(StereoPannerNodeHostObject, pan)); } JSI_PROPERTY_GETTER_IMPL(StereoPannerNodeHostObject, pan) { - auto stereoPannerNode = std::static_pointer_cast(node_); - auto panParam_ = std::make_shared(stereoPannerNode->getPanParam()); return jsi::Object::createFromHostObject(runtime, panParam_); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h index 37eaed62c..3b8043a56 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/StereoPannerNodeHostObject.h @@ -11,6 +11,7 @@ using namespace facebook; struct StereoPannerOptions; class BaseAudioContext; +class AudioParamHostObject; class StereoPannerNodeHostObject : public AudioNodeHostObject { public: @@ -19,5 +20,8 @@ class StereoPannerNodeHostObject : public AudioNodeHostObject { const StereoPannerOptions &options); JSI_PROPERTY_GETTER_DECL(pan); + + private: + std::shared_ptr panParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp index 05ff69806..aea7cb004 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.cpp @@ -7,69 +7,55 @@ #include #include +#include namespace audioapi { WaveShaperNodeHostObject::WaveShaperNodeHostObject( const std::shared_ptr &context, const WaveShaperOptions &options) - : AudioNodeHostObject(context->createWaveShaper(options), options) { - addGetters( - JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, oversample), - JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, curve)); - + : AudioNodeHostObject(context->createWaveShaper(options), options), oversample_(options.oversample) { + addGetters(JSI_EXPORT_PROPERTY_GETTER(WaveShaperNodeHostObject, oversample)); addSetters(JSI_EXPORT_PROPERTY_SETTER(WaveShaperNodeHostObject, oversample)); addFunctions(JSI_EXPORT_FUNCTION(WaveShaperNodeHostObject, setCurve)); } JSI_PROPERTY_GETTER_IMPL(WaveShaperNodeHostObject, oversample) { - auto waveShaperNode = std::static_pointer_cast(node_); return jsi::String::createFromUtf8( - runtime, js_enum_parser::overSampleTypeToString(waveShaperNode->getOversample())); -} - -JSI_PROPERTY_GETTER_IMPL(WaveShaperNodeHostObject, curve) { - auto waveShaperNode = std::static_pointer_cast(node_); - auto curve = waveShaperNode->getCurve(); - - if (curve == nullptr) { - return jsi::Value::null(); - } - - // copy AudioArray holding curve data to avoid subsequent modifications - auto audioArrayBuffer = std::make_shared(*curve); - auto arrayBuffer = jsi::ArrayBuffer(runtime, audioArrayBuffer); - - auto float32ArrayCtor = runtime.global().getPropertyAsFunction(runtime, "Float32Array"); - auto float32Array = float32ArrayCtor.callAsConstructor(runtime, arrayBuffer).getObject(runtime); - float32Array.setExternalMemoryPressure(runtime, audioArrayBuffer->size()); - - return float32Array; + runtime, js_enum_parser::overSampleTypeToString(oversample_)); } JSI_PROPERTY_SETTER_IMPL(WaveShaperNodeHostObject, oversample) { auto waveShaperNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - waveShaperNode->setOversample(js_enum_parser::overSampleTypeFromString(type)); + + auto oversample = js_enum_parser::overSampleTypeFromString(value.asString(runtime).utf8(runtime)); + auto event = [waveShaperNode, oversample](BaseAudioContext &) { + waveShaperNode->setOversample(oversample); + }; + waveShaperNode->scheduleAudioEvent(std::move(event)); + oversample_ = oversample; } JSI_HOST_FUNCTION_IMPL(WaveShaperNodeHostObject, setCurve) { auto waveShaperNode = std::static_pointer_cast(node_); - if (args[0].isNull()) { - waveShaperNode->setCurve(nullptr); - return jsi::Value::undefined(); - } + std::shared_ptr curve = nullptr; - auto arrayBuffer = - args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + if (args[0].isObject()) { + auto arrayBuffer = + args[0].getObject(runtime).getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + // *2 because it is copied to internal curve array for processing + thisValue.asObject(runtime).setExternalMemoryPressure(runtime, arrayBuffer.size(runtime) * 2); - auto curve = std::make_shared( - reinterpret_cast(arrayBuffer.data(runtime)), - static_cast(arrayBuffer.size(runtime) / sizeof(float))); + auto size = static_cast(arrayBuffer.size(runtime) / sizeof(float)); + curve = std::make_shared( + reinterpret_cast(arrayBuffer.data(runtime)), size); + } - waveShaperNode->setCurve(curve); - thisValue.asObject(runtime).setExternalMemoryPressure(runtime, arrayBuffer.size(runtime)); + auto event = [waveShaperNode, curve](BaseAudioContext &) { + waveShaperNode->setCurve(curve); + }; + waveShaperNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h index 17bae85b7..9911e0fa7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/effects/WaveShaperNodeHostObject.h @@ -20,9 +20,11 @@ class WaveShaperNodeHostObject : public AudioNodeHostObject { const WaveShaperOptions &options); JSI_PROPERTY_GETTER_DECL(oversample); - JSI_PROPERTY_GETTER_DECL(curve); JSI_PROPERTY_SETTER_DECL(oversample); JSI_HOST_FUNCTION_DECL(setCurve); + + private: + OverSampleType oversample_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp index 9f503d682..d421d980d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferBaseSourceNodeHostObject.cpp @@ -1,16 +1,24 @@ #include - #include #include #include + #include +#include namespace audioapi { AudioBufferBaseSourceNodeHostObject::AudioBufferBaseSourceNodeHostObject( const std::shared_ptr &node, const BaseAudioBufferSourceOptions &options) - : AudioScheduledSourceNodeHostObject(node, options) { + : AudioScheduledSourceNodeHostObject(node, options), pitchCorrection_(options.pitchCorrection) { + auto sourceNode = std::static_pointer_cast(node_); + detuneParam_ = std::make_shared(sourceNode->getDetuneParam()); + playbackRateParam_ = std::make_shared(sourceNode->getPlaybackRateParam()); + onPositionChangedInterval_ = sourceNode->getOnPositionChangedInterval(); + inputLatency_ = sourceNode->getInputLatency(); + outputLatency_ = sourceNode->getOutputLatency(); + addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, detune), JSI_EXPORT_PROPERTY_GETTER(AudioBufferBaseSourceNodeHostObject, playbackRate), @@ -26,55 +34,60 @@ 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 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->setOnPositionChangedInterval(static_cast(value.getNumber())); + 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 f65c39a14..6abb93d85 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 @@ -10,6 +10,7 @@ using namespace facebook; class AudioBufferBaseSourceNode; struct BaseAudioBufferSourceOptions; +class AudioParamHostObject; class AudioBufferBaseSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -28,6 +29,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; + + double inputLatency_; + double outputLatency_; + bool pitchCorrection_; + + void setOnPositionChangedCallbackId(uint64_t callbackId); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h index 09d440d55..b441a08fb 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferHostObject.h @@ -29,7 +29,8 @@ class AudioBufferHostObject : public JsiHostObject { } [[nodiscard]] inline size_t getSizeInBytes() const { - return audioBuffer_->getSize() * audioBuffer_->getNumberOfChannels() * sizeof(float); + // *2 because every time buffer is passed we create a copy of it. + return audioBuffer_->getSize() * audioBuffer_->getNumberOfChannels() * sizeof(float) * 2; } JSI_PROPERTY_GETTER_DECL(sampleRate); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp index f5996753c..365ac7533 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 +#include namespace audioapi { @@ -25,32 +27,27 @@ AudioBufferQueueSourceNodeHostObject::AudioBufferQueueSourceNodeHostObject( } AudioBufferQueueSourceNodeHostObject::~AudioBufferQueueSourceNodeHostObject() { - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioBufferQueueSourceNode->setOnBufferEndedCallbackId(0); + setOnBufferEndedCallbackId(0); } JSI_PROPERTY_SETTER_IMPL(AudioBufferQueueSourceNodeHostObject, onBufferEnded) { - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - - audioBufferQueueSourceNode->setOnBufferEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnBufferEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, start) { - auto when = args[0].getNumber(); - auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - if (!args[1].isNumber()) { - audioBufferQueueSourceNode->start(when); - } else { - auto offset = args[1].getNumber(); + auto event = [ + audioBufferQueueSourceNode, + when = args[0].getNumber(), + offset = args[1].isNumber() ? args[1].getNumber() : -1](BaseAudioContext &) { audioBufferQueueSourceNode->start(when, offset); - } + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -58,7 +55,10 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, start) { JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, pause) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - audioBufferQueueSourceNode->pause(); + auto event = [audioBufferQueueSourceNode](BaseAudioContext &) { + audioBufferQueueSourceNode->pause(); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -68,18 +68,28 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, enqueueBuffer) { auto audioBufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - - auto bufferId = audioBufferQueueSourceNode->enqueueBuffer(audioBufferHostObject->audioBuffer_); - - return jsi::String::createFromUtf8(runtime, bufferId); + // TODO: add optimized memory management for buffer changes, e.g. + // when the same buffer is reused across threads and + // buffer modification is not allowed on JS thread + auto copiedBuffer = std::make_shared(*audioBufferHostObject->audioBuffer_); + auto event = [audioBufferQueueSourceNode, + copiedBuffer, + bufferId = bufferId_](BaseAudioContext &) { + audioBufferQueueSourceNode->enqueueBuffer(copiedBuffer, 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()); - - audioBufferQueueSourceNode->dequeueBuffer(bufferId); + auto event = [audioBufferQueueSourceNode, + bufferId = static_cast(args[0].getNumber())](BaseAudioContext &) { + audioBufferQueueSourceNode->dequeueBuffer(bufferId); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -87,9 +97,24 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, dequeueBuffer) { JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, clearBuffers) { auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); - audioBufferQueueSourceNode->clearBuffers(); + auto event = [audioBufferQueueSourceNode](BaseAudioContext &) { + audioBufferQueueSourceNode->clearBuffers(); + }; + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } +void AudioBufferQueueSourceNodeHostObject::setOnBufferEndedCallbackId(uint64_t callbackId) { + auto audioBufferQueueSourceNode = std::static_pointer_cast(node_); + + auto event = [audioBufferQueueSourceNode, callbackId](BaseAudioContext &) { + audioBufferQueueSourceNode->setOnBufferEndedCallbackId(callbackId); + }; + + audioBufferQueueSourceNode->unregisterOnBufferEndedCallback(onBufferEndedCallbackId_); + audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event)); + onBufferEndedCallbackId_ = callbackId; +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h index 6485a7c3a..b5eb4af37 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,12 @@ class AudioBufferQueueSourceNodeHostObject : public AudioBufferBaseSourceNodeHos JSI_HOST_FUNCTION_DECL(enqueueBuffer); JSI_HOST_FUNCTION_DECL(dequeueBuffer); JSI_HOST_FUNCTION_DECL(clearBuffers); + + protected: + size_t bufferId_ = 0; + uint64_t onBufferEndedCallbackId_ = 0; + + void setOnBufferEndedCallbackId(uint64_t callbackId); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp index ac23adb1d..10112ce84 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.cpp @@ -1,21 +1,31 @@ #include +#include #include -#include #include #include +#include +#include #include +#include namespace audioapi { AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject( const std::shared_ptr &context, const AudioBufferSourceOptions &options) - : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options), options) { + : AudioBufferBaseSourceNodeHostObject(context->createBufferSource(options), options), + loop_(options.loop), + loopSkip_(options.loopSkip), + loopStart_(options.loopStart), + loopEnd_(options.loopEnd) { + if (options.buffer != nullptr) { + setBuffer(options.buffer); + } + addGetters( JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loop), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopSkip), - JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, buffer), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopStart), JSI_EXPORT_PROPERTY_GETTER(AudioBufferSourceNodeHostObject, loopEnd)); @@ -35,93 +45,91 @@ AudioBufferSourceNodeHostObject::AudioBufferSourceNodeHostObject( } AudioBufferSourceNodeHostObject::~AudioBufferSourceNodeHostObject() { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioBufferSourceNode->setOnLoopEndedCallbackId(0); + setOnLoopEndedCallbackId(0); } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loop) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loop = audioBufferSourceNode->getLoop(); - return {loop}; + return {loop_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopSkip = audioBufferSourceNode->getLoopSkip(); - return {loopSkip}; -} - -JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, buffer) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto buffer = audioBufferSourceNode->getBuffer(); - - if (!buffer) { - return jsi::Value::null(); - } - - auto bufferHostObject = std::make_shared(buffer); - auto jsiObject = jsi::Object::createFromHostObject(runtime, bufferHostObject); - jsiObject.setExternalMemoryPressure(runtime, bufferHostObject->getSizeInBytes() + 16); - return jsiObject; + return {loopSkip_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopStart = audioBufferSourceNode->getLoopStart(); - return {loopStart}; + return {loopStart_}; } JSI_PROPERTY_GETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - auto loopEnd = audioBufferSourceNode->getLoopEnd(); - return {loopEnd}; + return {loopEnd_}; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loop) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoop(value.getBool()); + auto loop = value.getBool(); + + auto event = [audioBufferSourceNode, loop](BaseAudioContext &) { + audioBufferSourceNode->setLoop(loop); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loop_ = loop; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopSkip) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopSkip(value.getBool()); + auto loopSkip = value.getBool(); + + auto event = [audioBufferSourceNode, loopSkip](BaseAudioContext &) { + audioBufferSourceNode->setLoopSkip(loopSkip); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopSkip_ = loopSkip; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopStart) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopStart(value.getNumber()); + auto loopStart = value.getNumber(); + + auto event = [audioBufferSourceNode, loopStart](BaseAudioContext &) { + audioBufferSourceNode->setLoopStart(loopStart); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopStart_ = loopStart; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, loopEnd) { auto audioBufferSourceNode = std::static_pointer_cast(node_); - audioBufferSourceNode->setLoopEnd(value.getNumber()); + auto loopEnd = value.getNumber(); + + auto event = [audioBufferSourceNode, loopEnd](BaseAudioContext &) { + audioBufferSourceNode->setLoopEnd(loopEnd); + }; + + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + loopEnd_ = loopEnd; } JSI_PROPERTY_SETTER_IMPL(AudioBufferSourceNodeHostObject, onLoopEnded) { - auto audioBufferSourceNode = std::static_pointer_cast(node_); - - audioBufferSourceNode->setOnLoopEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnLoopEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, start) { - auto when = args[0].getNumber(); - auto offset = args[1].getNumber(); - auto audioBufferSourceNode = std::static_pointer_cast(node_); - if (args[2].isUndefined()) { - audioBufferSourceNode->start(when, offset); - - return jsi::Value::undefined(); - } - - auto duration = args[2].getNumber(); - audioBufferSourceNode->start(when, offset, duration); + auto event = [audioBufferSourceNode, + when = args[0].getNumber(), + offset = args[1].getNumber(), + duration = args[2].isUndefined() ? -1 : args[2].getNumber()](BaseAudioContext &) { + audioBufferSourceNode->start(when, offset, duration); + }; + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); return jsi::Value::undefined(); } @@ -130,15 +138,73 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferSourceNodeHostObject, setBuffer) { auto audioBufferSourceNode = std::static_pointer_cast(node_); if (args[0].isNull()) { - audioBufferSourceNode->setBuffer(std::shared_ptr(nullptr)); - return jsi::Value::undefined(); + setBuffer(nullptr); + } else { + auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); + thisValue.asObject(runtime).setExternalMemoryPressure( + runtime, bufferHostObject->getSizeInBytes()); + + setBuffer(bufferHostObject->audioBuffer_); } - auto bufferHostObject = args[0].getObject(runtime).asHostObject(runtime); - thisValue.asObject(runtime).setExternalMemoryPressure( - runtime, bufferHostObject->getSizeInBytes() + 16); - audioBufferSourceNode->setBuffer(bufferHostObject->audioBuffer_); return jsi::Value::undefined(); } +void AudioBufferSourceNodeHostObject::setOnLoopEndedCallbackId(uint64_t callbackId) { + auto audioBufferSourceNode = std::static_pointer_cast(node_); + + auto event = [audioBufferSourceNode, callbackId](BaseAudioContext &) { + audioBufferSourceNode->setOnLoopEndedCallbackId(callbackId); + }; + + audioBufferSourceNode->unregisterOnLoopEndedCallback(onLoopEndedCallbackId_); + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); + onLoopEndedCallbackId_ = callbackId; +} + +void AudioBufferSourceNodeHostObject::setBuffer(const std::shared_ptr &buffer) { + // TODO: add optimized memory management for buffer changes, e.g. + // when the same buffer is reused across threads and + // buffer modification is not allowed on JS thread + auto audioBufferSourceNode = std::static_pointer_cast(node_); + + std::shared_ptr copiedBuffer; + std::shared_ptr playbackRateBuffer; + std::shared_ptr audioBuffer; + + if (buffer == nullptr) { + copiedBuffer = nullptr; + playbackRateBuffer = nullptr; + audioBuffer = std::make_shared( + RENDER_QUANTUM_SIZE, 1, audioBufferSourceNode->getContextSampleRate()); + } else { + if (pitchCorrection_) { + int extraTailFrames = + static_cast((inputLatency_ + outputLatency_) * buffer->getSampleRate()); + size_t totalSize = buffer->getSize() + extraTailFrames; + copiedBuffer = std::make_shared( + totalSize, buffer->getNumberOfChannels(), buffer->getSampleRate()); + copiedBuffer->copy(*buffer, 0, 0, buffer->getSize()); + copiedBuffer->zero(buffer->getSize(), extraTailFrames); + } else { + copiedBuffer = std::make_shared(*buffer); + } + + playbackRateBuffer = std::make_shared( + 3 * RENDER_QUANTUM_SIZE, + copiedBuffer->getNumberOfChannels(), + audioBufferSourceNode->getContextSampleRate()); + audioBuffer = std::make_shared( + RENDER_QUANTUM_SIZE, + copiedBuffer->getNumberOfChannels(), + audioBufferSourceNode->getContextSampleRate()); + } + + auto event = + [audioBufferSourceNode, copiedBuffer, playbackRateBuffer, audioBuffer](BaseAudioContext &) { + audioBufferSourceNode->setBuffer(copiedBuffer, playbackRateBuffer, audioBuffer); + }; + audioBufferSourceNode->scheduleAudioEvent(std::move(event)); +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioBufferSourceNodeHostObject.h index ad3359962..24b3552df 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,8 @@ using namespace facebook; struct AudioBufferSourceOptions; class BaseAudioContext; +class AudioBufferHostObject; +class AudioBuffer; class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObject { public: @@ -21,7 +23,6 @@ class AudioBufferSourceNodeHostObject : public AudioBufferBaseSourceNodeHostObje JSI_PROPERTY_GETTER_DECL(loop); JSI_PROPERTY_GETTER_DECL(loopSkip); - JSI_PROPERTY_GETTER_DECL(buffer); JSI_PROPERTY_GETTER_DECL(loopStart); JSI_PROPERTY_GETTER_DECL(loopEnd); @@ -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; + + void setOnLoopEndedCallbackId(uint64_t callbackId); + void setBuffer(const std::shared_ptr &buffer); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp index 0f6c06c38..3a0932b20 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace audioapi { @@ -18,33 +19,49 @@ AudioScheduledSourceNodeHostObject::AudioScheduledSourceNodeHostObject( } AudioScheduledSourceNodeHostObject::~AudioScheduledSourceNodeHostObject() { - auto audioScheduledSourceNode = std::static_pointer_cast(node_); - // When JSI object is garbage collected (together with the eventual callback), // underlying source node might still be active and try to call the // non-existing callback. - audioScheduledSourceNode->setOnEndedCallbackId(0); + setOnEndedCallbackId(0); } JSI_PROPERTY_SETTER_IMPL(AudioScheduledSourceNodeHostObject, onEnded) { - auto audioScheduleSourceNode = std::static_pointer_cast(node_); - - audioScheduleSourceNode->setOnEndedCallbackId( - std::stoull(value.getString(runtime).utf8(runtime))); + auto callbackId = std::stoull(value.getString(runtime).utf8(runtime)); + setOnEndedCallbackId(callbackId); } JSI_HOST_FUNCTION_IMPL(AudioScheduledSourceNodeHostObject, start) { - auto when = args[0].getNumber(); auto audioScheduleSourceNode = std::static_pointer_cast(node_); - audioScheduleSourceNode->start(when); + + auto event = [audioScheduleSourceNode, when = args[0].getNumber()] (BaseAudioContext &) { + audioScheduleSourceNode->start(when); + }; + audioScheduleSourceNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } JSI_HOST_FUNCTION_IMPL(AudioScheduledSourceNodeHostObject, stop) { - auto time = args[0].getNumber(); auto audioScheduleSourceNode = std::static_pointer_cast(node_); - audioScheduleSourceNode->stop(time); + + auto event = [audioScheduleSourceNode, when = args[0].getNumber()] (BaseAudioContext &) { + audioScheduleSourceNode->stop(when); + }; + audioScheduleSourceNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } +void AudioScheduledSourceNodeHostObject::setOnEndedCallbackId(uint64_t callbackId) { + auto sourceNode = std::static_pointer_cast(node_); + + auto event = [sourceNode, callbackId](BaseAudioContext &) { + sourceNode->setOnEndedCallbackId(callbackId); + }; + + sourceNode->unregisterOnEndedCallback(onEndedCallbackId_); + sourceNode->scheduleAudioEvent(std::move(event)); + onEndedCallbackId_ = callbackId; +} + } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/AudioScheduledSourceNodeHostObject.h index f80caf5b6..c23a880f0 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 @@ -23,5 +23,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 57ec5e828..6f024488f 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.cpp @@ -11,12 +11,13 @@ ConstantSourceNodeHostObject::ConstantSourceNodeHostObject( const std::shared_ptr &context, const ConstantSourceOptions &options) : AudioScheduledSourceNodeHostObject(context->createConstantSource(options), options) { + auto constantSourceNode = std::static_pointer_cast(node_); + offsetParam_ = std::make_shared(constantSourceNode->getOffsetParam()); + addGetters(JSI_EXPORT_PROPERTY_GETTER(ConstantSourceNodeHostObject, offset)); } JSI_PROPERTY_GETTER_IMPL(ConstantSourceNodeHostObject, offset) { - auto constantSourceNode = std::static_pointer_cast(node_); - auto offsetParam_ = std::make_shared(constantSourceNode->getOffsetParam()); return jsi::Object::createFromHostObject(runtime, offsetParam_); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/ConstantSourceNodeHostObject.h index 8cb158d66..966966a56 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 @@ -11,6 +11,7 @@ using namespace facebook; struct ConstantSourceOptions; class BaseAudioContext; +class AudioParamHostObject; class ConstantSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -19,5 +20,8 @@ class ConstantSourceNodeHostObject : public AudioScheduledSourceNodeHostObject { const ConstantSourceOptions &options); JSI_PROPERTY_GETTER_DECL(offset); + + private: + std::shared_ptr offsetParam_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp index 0dfd11326..838cd77de 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.cpp @@ -7,13 +7,19 @@ #include #include #include +#include namespace audioapi { OscillatorNodeHostObject::OscillatorNodeHostObject( const std::shared_ptr &context, const OscillatorOptions &options) - : AudioScheduledSourceNodeHostObject(context->createOscillator(options), options) { + : AudioScheduledSourceNodeHostObject(context->createOscillator(options), options), type_(options.type) { + auto oscillatorNode = std::static_pointer_cast(node_); + frequencyParam_ = + std::make_shared(oscillatorNode->getFrequencyParam()); + detuneParam_ = std::make_shared(oscillatorNode->getDetuneParam()); + addGetters( JSI_EXPORT_PROPERTY_GETTER(OscillatorNodeHostObject, frequency), JSI_EXPORT_PROPERTY_GETTER(OscillatorNodeHostObject, detune), @@ -25,35 +31,39 @@ OscillatorNodeHostObject::OscillatorNodeHostObject( } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, frequency) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto frequencyParam_ = - std::make_shared(oscillatorNode->getFrequencyParam()); return jsi::Object::createFromHostObject(runtime, frequencyParam_); } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, detune) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto detuneParam_ = std::make_shared(oscillatorNode->getDetuneParam()); return jsi::Object::createFromHostObject(runtime, detuneParam_); } JSI_PROPERTY_GETTER_IMPL(OscillatorNodeHostObject, type) { - auto oscillatorNode = std::static_pointer_cast(node_); - auto waveType = oscillatorNode->getType(); - return jsi::String::createFromUtf8(runtime, js_enum_parser::oscillatorTypeToString(waveType)); + return jsi::String::createFromUtf8(runtime, js_enum_parser::oscillatorTypeToString(type_)); } JSI_HOST_FUNCTION_IMPL(OscillatorNodeHostObject, setPeriodicWave) { auto oscillatorNode = std::static_pointer_cast(node_); auto periodicWave = args[0].getObject(runtime).getHostObject(runtime); - oscillatorNode->setPeriodicWave(periodicWave->periodicWave_); + + auto event = [oscillatorNode, periodicWave = periodicWave->periodicWave_](BaseAudioContext &) { + oscillatorNode->setPeriodicWave(periodicWave); + }; + oscillatorNode->scheduleAudioEvent(std::move(event)); + return jsi::Value::undefined(); } JSI_PROPERTY_SETTER_IMPL(OscillatorNodeHostObject, type) { auto oscillatorNode = std::static_pointer_cast(node_); - auto type = value.asString(runtime).utf8(runtime); - oscillatorNode->setType(js_enum_parser::oscillatorTypeFromString(type)); + auto type = js_enum_parser::oscillatorTypeFromString(value.asString(runtime).utf8(runtime)); + + auto event = [oscillatorNode, type](BaseAudioContext &) { + oscillatorNode->setType(type); + }; + type_ = type; + + oscillatorNode->scheduleAudioEvent(std::move(event)); } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h index 7f6042ea1..039b1b0fc 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/OscillatorNodeHostObject.h @@ -12,6 +12,7 @@ using namespace facebook; struct OscillatorOptions; class BaseAudioContext; +class AudioParamHostObject; class OscillatorNodeHostObject : public AudioScheduledSourceNodeHostObject { public: @@ -26,5 +27,10 @@ class OscillatorNodeHostObject : public AudioScheduledSourceNodeHostObject { JSI_HOST_FUNCTION_DECL(setPeriodicWave); JSI_PROPERTY_SETTER_DECL(type); + + private: + std::shared_ptr frequencyParam_; + std::shared_ptr detuneParam_; + OscillatorType type_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp deleted file mode 100644 index 90ca56353..000000000 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -namespace audioapi { - -StreamerNodeHostObject::StreamerNodeHostObject( - const std::shared_ptr &context, - const StreamerOptions &options) - : AudioScheduledSourceNodeHostObject(context->createStreamer(options), options) { - addFunctions(JSI_EXPORT_FUNCTION(StreamerNodeHostObject, initialize)); - addGetters(JSI_EXPORT_PROPERTY_GETTER(StreamerNodeHostObject, streamPath)); -} - -JSI_PROPERTY_GETTER_IMPL(StreamerNodeHostObject, streamPath) { - auto streamerNode = std::static_pointer_cast(node_); - return jsi::String::createFromUtf8(runtime, streamerNode->getStreamPath()); -} - -JSI_HOST_FUNCTION_IMPL(StreamerNodeHostObject, initialize) { -#if !RN_AUDIO_API_FFMPEG_DISABLED - auto streamerNode = std::static_pointer_cast(node_); - auto path = args[0].getString(runtime).utf8(runtime); - auto result = streamerNode->initialize(path); - return {result}; -#else - return false; -#endif -} - -} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h index 3f2c09f69..ad023fd7f 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/sources/StreamerNodeHostObject.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include #include @@ -16,15 +19,13 @@ class StreamerNodeHostObject : public AudioScheduledSourceNodeHostObject { public: explicit StreamerNodeHostObject( const std::shared_ptr &context, - const StreamerOptions &options); + const StreamerOptions &options) + : AudioScheduledSourceNodeHostObject(context->createStreamer(options), options) {} [[nodiscard]] static inline size_t getSizeInBytes() { return SIZE; } - JSI_PROPERTY_GETTER_DECL(streamPath); - JSI_HOST_FUNCTION_DECL(initialize); - private: static constexpr size_t SIZE = 4'000'000; // 4MB }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp index 312d45af0..4bb8ac12e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp @@ -1,8 +1,6 @@ #include #include -using WindowType = audioapi::AnalyserNode::WindowType; - namespace audioapi::js_enum_parser { WindowType windowTypeFromString(const std::string &type) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/utils/JsEnumParser.h index fadf44720..c3d92ce37 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 @@ -7,12 +7,13 @@ #include #include #include +#include #include #include namespace audioapi::js_enum_parser { -std::string windowTypeToString(AnalyserNode::WindowType type); -AnalyserNode::WindowType windowTypeFromString(const std::string &type); +std::string windowTypeToString(WindowType type); +WindowType windowTypeFromString(const std::string &type); std::string overSampleTypeToString(OverSampleType type); OverSampleType overSampleTypeFromString(const std::string &type); std::string oscillatorTypeToString(OscillatorType type); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp index 853f5633c..7484d908b 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 @@ -52,7 +52,7 @@ bool AudioContext::resume() { return true; } - if (isInitialized_ && audioPlayer_->resume()) { + if (isInitialized_.load(std::memory_order_acquire) && audioPlayer_->resume()) { setState(ContextState::RUNNING); return true; } @@ -80,8 +80,8 @@ bool AudioContext::start() { return false; } - if (!isInitialized_ && audioPlayer_->start()) { - isInitialized_ = true; + if (!isInitialized_.load(std::memory_order_acquire) && audioPlayer_->start()) { + isInitialized_.store(true, std::memory_order_release); setState(ContextState::RUNNING); return true; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h index 967b38426..e1926a4d3 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.h @@ -33,7 +33,7 @@ class AudioContext : public BaseAudioContext { #else std::shared_ptr audioPlayer_; #endif - bool isInitialized_; + std::atomic isInitialized_ = false; bool isDriverRunning() const override; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.cpp index 94959779c..fd63a223d 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 @@ -26,31 +26,15 @@ AudioNode::AudioNode( } AudioNode::~AudioNode() { - if (isInitialized_) { + if (isInitialized_.load(std::memory_order_acquire)) { cleanup(); } } -int AudioNode::getNumberOfInputs() const { - return numberOfInputs_; -} - -int AudioNode::getNumberOfOutputs() const { - return numberOfOutputs_; -} - size_t AudioNode::getChannelCount() const { return channelCount_; } -ChannelCountMode AudioNode::getChannelCountMode() const { - return channelCountMode_; -} - -ChannelInterpretation AudioNode::getChannelInterpretation() const { - return channelInterpretation_; -} - void AudioNode::connect(const std::shared_ptr &node) { if (std::shared_ptr context = context_.lock()) { context->getGraphManager()->addPendingNodeConnection( @@ -122,7 +106,7 @@ std::shared_ptr AudioNode::processAudio( const std::shared_ptr &outputBuffer, int framesToProcess, bool checkIsAlreadyProcessed) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return outputBuffer; } @@ -274,7 +258,7 @@ void AudioNode::onInputDisabled() { } void AudioNode::onInputConnected(AudioNode *node) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return; } @@ -286,7 +270,7 @@ void AudioNode::onInputConnected(AudioNode *node) { } void AudioNode::onInputDisconnected(AudioNode *node) { - if (!isInitialized_) { + if (!isInitialized_.load(std::memory_order_acquire)) { return; } @@ -302,7 +286,7 @@ void AudioNode::onInputDisconnected(AudioNode *node) { } void AudioNode::cleanup() { - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); for (auto it = outputNodes_.begin(), end = outputNodes_.end(); it != end; ++it) { it->get()->onInputDisconnected(this); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h index e63c83c1d..3d81ae82a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioNode.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,15 +8,16 @@ #include #include +#include #include #include #include +#include #include namespace audioapi { class AudioBuffer; -class BaseAudioContext; class AudioParam; class AudioNode : public std::enable_shared_from_this { @@ -25,11 +27,7 @@ class AudioNode : public std::enable_shared_from_this { const AudioNodeOptions &options = AudioNodeOptions()); virtual ~AudioNode(); - int getNumberOfInputs() const; - int getNumberOfOutputs() const; size_t getChannelCount() const; - ChannelCountMode getChannelCountMode() const; - ChannelInterpretation getChannelInterpretation() const; void connect(const std::shared_ptr &node); void connect(const std::shared_ptr ¶m); void disconnect(); @@ -40,10 +38,27 @@ class AudioNode : public std::enable_shared_from_this { int framesToProcess, bool checkIsAlreadyProcessed); + float getContextSampleRate() const { + if (std::shared_ptr context = context_.lock()) { + return context->getSampleRate(); + } + + return DEFAULT_SAMPLE_RATE; + } + + /// @note JS Thread only bool isEnabled() const; + /// @note JS Thread only bool requiresTailProcessing() const; - void enable(); - virtual void disable(); + + template + bool inline scheduleAudioEvent(F &&event) noexcept { + if (std::shared_ptr context = context_.lock()) { + return context->scheduleAudioEvent(std::forward(event)); + } + + return false; + } protected: friend class AudioGraphManager; @@ -66,12 +81,15 @@ class AudioNode : public std::enable_shared_from_this { std::unordered_set> outputParams_ = {}; int numberOfEnabledInputNodes_ = 0; - bool isInitialized_ = false; - bool isEnabled_ = true; + std::atomic isInitialized_ = false; std::size_t lastRenderedFrame_{SIZE_MAX}; + void enable(); + virtual void disable(); + private: + bool isEnabled_ = true; std::vector> inputBuffers_ = {}; virtual std::shared_ptr processInputs( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp index be3b6f82c..f4860fd67 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.cpp @@ -19,7 +19,6 @@ AudioParam::AudioParam( minValue_(minValue), maxValue_(maxValue), eventsQueue_(), - eventScheduler_(32), startTime_(0), endTime_(0), startValue_(defaultValue), @@ -48,14 +47,14 @@ float AudioParam::getValueAtTime(double time) { } // Calculate value using the current automation function and clamp to valid - setValue(calculateValue_(startTime_, endTime_, startValue_, endValue_, time)); - return value_; + auto value = calculateValue_(startTime_, endTime_, startValue_, endValue_, time); + setValue(value); + return value; } void AudioParam::setValueAtTime(float value, double startTime) { - auto event = [value, startTime](AudioParam ¶m) { // Ignore events scheduled before the end of existing automation - if (startTime < param.getQueueEndTime()) { + if (startTime < this->getQueueEndTime()) { return; } @@ -69,21 +68,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; } @@ -102,20 +98,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; } @@ -135,20 +128,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 @@ -161,17 +151,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( @@ -179,8 +166,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; } @@ -204,29 +190,21 @@ void AudioParam::setValueCurveAtTime( return endValue; }; - param.updateQueue(ParamChangeEvent( + this->updateQueue(ParamChangeEvent( startTime, startTime + duration, - param.getQueueEndValue(), + this->getQueueEndValue(), values->span()[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) { @@ -256,7 +234,6 @@ std::shared_ptr AudioParam::calculateInputs( } std::shared_ptr AudioParam::processARateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBuffer = calculateInputs(audioBuffer_, framesToProcess); std::shared_ptr context = context_.lock(); @@ -278,7 +255,6 @@ std::shared_ptr AudioParam::processARateParam(int framesToProcess, } float AudioParam::processKRateParam(int framesToProcess, double time) { - processScheduledEvents(); auto processingBuffer = calculateInputs(audioBuffer_, framesToProcess); // Return block-rate parameter value plus first sample of input modulation diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h index 7f5b7130e..c901d00e0 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/AudioParam.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -23,59 +24,49 @@ class AudioParam { float maxValue, const std::shared_ptr &context); - /// JS-Thread only methods - /// These methods are called only from HostObjects invoked on the JS thread. - - // JS-Thread only [[nodiscard]] inline float getValue() const noexcept { return value_.load(std::memory_order_relaxed); } - // JS-Thread only [[nodiscard]] inline float getDefaultValue() const noexcept { return defaultValue_; } - // JS-Thread only [[nodiscard]] inline float getMinValue() const noexcept { return minValue_; } - // JS-Thread only [[nodiscard]] inline float getMaxValue() const noexcept { return maxValue_; } - // JS-Thread only inline void setValue(float value) { value_.store(std::clamp(value, minValue_, maxValue_), std::memory_order_release); } - // JS-Thread only 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( const 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); + template < + typename F, + typename = std::enable_if_t, BaseAudioContext &>>> + bool inline scheduleAudioEvent(F &&event) noexcept { + if (std::shared_ptr context = context_.lock()) { + return context->scheduleAudioEvent(std::forward(event)); + } + + return false; + } + /// Audio-Thread only methods /// These methods are called only from the Audio rendering thread. @@ -100,7 +91,6 @@ class AudioParam { float maxValue_; AudioParamEventQueue eventsQueue_; - CrossThreadEventScheduler eventScheduler_; // Current automation state (cached for performance) double startTime_; @@ -132,11 +122,6 @@ class AudioParam { return eventsQueue_.back().getEndValue(); } - /// @brief Process all scheduled events. - inline void processScheduledEvents() noexcept(noexcept(eventScheduler_.processAllEvents(*this))) { - eventScheduler_.processAllEvents(*this); - } - /// @brief Update the parameter queue with a new event. /// @param event The new event to add to the queue. /// @note Handles connecting start value of the new event to the end value of the previous event. diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp index f119be1d5..d9057646e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/BaseAudioContext.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -16,6 +15,7 @@ #include #include #include +#include #if !RN_AUDIO_API_FFMPEG_DISABLED #include #endif // RN_AUDIO_API_FFMPEG_DISABLED @@ -42,7 +42,10 @@ BaseAudioContext::BaseAudioContext( sampleRate_(sampleRate), graphManager_(std::make_shared()), audioEventHandlerRegistry_(audioEventHandlerRegistry), - runtimeRegistry_(runtimeRegistry) {} + runtimeRegistry_(runtimeRegistry), + audioEventScheduler_( + std::make_unique>(AUDIO_SCHEDULER_CAPACITY)) { +} void BaseAudioContext::initialize() { destination_ = std::make_shared(shared_from_this()); 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 ff7da2d28..8699f1f6b 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,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include @@ -113,6 +115,21 @@ class BaseAudioContext : public std::enable_shared_from_this { virtual void initialize(); + void inline processAudioEvents() { + audioEventScheduler_->processAllEvents(*this); + } + + template + bool inline scheduleAudioEvent(F &&event) noexcept { + if (getState() != ContextState::RUNNING) { + processAudioEvents(); + event(*this); + return true; + } + + return audioEventScheduler_->scheduleEvent(std::forward(event)); + } + protected: std::shared_ptr destination_; @@ -128,6 +145,9 @@ class BaseAudioContext : public std::enable_shared_from_this { std::shared_ptr cachedSawtoothWave_ = nullptr; std::shared_ptr cachedTriangleWave_ = nullptr; + static constexpr size_t AUDIO_SCHEDULER_CAPACITY = 1024; + std::unique_ptr> audioEventScheduler_; + [[nodiscard]] virtual bool isDriverRunning() const = 0; }; 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 aac15b052..210bff547 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,13 @@ class OfflineAudioContext : public BaseAudioContext { const RuntimeRegistry &runtimeRegistry); ~OfflineAudioContext() override; + /// @note JS Thread only void resume(); + + /// @note JS Thread only void suspend(double when, const OfflineAudioContextSuspendCallback &callback); + /// @note JS Thread only void startRendering(OfflineAudioContextResultCallback callback); private: diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp index dd93c628a..9a0fe19df 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.cpp @@ -23,7 +23,7 @@ AnalyserNode::AnalyserNode( minDecibels_(options.minDecibels), maxDecibels_(options.maxDecibels), smoothingTimeConstant_(options.smoothingTimeConstant), - windowType_(WindowType::BLACKMAN), + windowType_(options.windowType), inputArray_(std::make_unique(MAX_FFT_SIZE * 2)), downMixBuffer_( std::make_unique(RENDER_QUANTUM_SIZE, 1, context->getSampleRate())), @@ -32,31 +32,7 @@ AnalyserNode::AnalyserNode( complexData_(std::vector>(fftSize_)), magnitudeArray_(std::make_unique(fftSize_ / 2)) { setWindowData(windowType_, fftSize_); - isInitialized_ = true; -} - -int AnalyserNode::getFftSize() const { - return fftSize_; -} - -int AnalyserNode::getFrequencyBinCount() const { - return fftSize_ / 2; -} - -float AnalyserNode::getMinDecibels() const { - return minDecibels_; -} - -float AnalyserNode::getMaxDecibels() const { - return maxDecibels_; -} - -float AnalyserNode::getSmoothingTimeConstant() const { - return smoothingTimeConstant_; -} - -AnalyserNode::WindowType AnalyserNode::getWindowType() const { - return windowType_; + isInitialized_.store(true, std::memory_order_release); } void AnalyserNode::setFftSize(int fftSize) { @@ -84,7 +60,7 @@ void AnalyserNode::setSmoothingTimeConstant(float smoothingTimeConstant) { smoothingTimeConstant_ = smoothingTimeConstant; } -void AnalyserNode::setWindowType(AnalyserNode::WindowType type) { +void AnalyserNode::setWindowType(WindowType type) { setWindowData(type, fftSize_); } @@ -197,7 +173,7 @@ void AnalyserNode::doFFTAnalysis() { } } -void AnalyserNode::setWindowData(AnalyserNode::WindowType type, int size) { +void AnalyserNode::setWindowData(WindowType type, int size) { if (windowType_ == type && windowData_ != nullptr && windowData_->getSize() == size) { return; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h index c1f6db736..b80159eab 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/analysis/AnalyserNode.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -19,23 +20,15 @@ struct AnalyserOptions; class AnalyserNode : public AudioNode { public: - enum class WindowType { BLACKMAN, HANN }; explicit AnalyserNode( const std::shared_ptr &context, const AnalyserOptions &options); - int getFftSize() const; - int getFrequencyBinCount() const; - float getMinDecibels() const; - float getMaxDecibels() const; - float getSmoothingTimeConstant() const; - AnalyserNode::WindowType getWindowType() const; - void setFftSize(int fftSize); void setMinDecibels(float minDecibels); void setMaxDecibels(float maxDecibels); void setSmoothingTimeConstant(float smoothingTimeConstant); - void setWindowType(AnalyserNode::WindowType); + void setWindowType(WindowType); void getFloatFrequencyData(float *data, int length); void getByteFrequencyData(uint8_t *data, int length); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp index e11763597..53628b4c7 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/destinations/AudioDestinationNode.cpp @@ -10,7 +10,7 @@ namespace audioapi { AudioDestinationNode::AudioDestinationNode(const std::shared_ptr &context) : AudioNode(context, AudioDestinationOptions()), currentSampleFrame_(0) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::size_t AudioDestinationNode::getCurrentSampleFrame() const { @@ -18,21 +18,18 @@ std::size_t AudioDestinationNode::getCurrentSampleFrame() const { } double AudioDestinationNode::getCurrentTime() const { - if (std::shared_ptr context = context_.lock()) { - return static_cast(getCurrentSampleFrame()) / context->getSampleRate(); - } else { - return 0.0; - } + return static_cast(getCurrentSampleFrame()) / getContextSampleRate(); } void AudioDestinationNode::renderAudio( const std::shared_ptr &destinationBuffer, int numFrames) { - if (numFrames < 0 || !destinationBuffer || !isInitialized_) { + if (numFrames < 0 || !destinationBuffer || !isInitialized_.load(std::memory_order_acquire)) { return; } if (std::shared_ptr context = context_.lock()) { + context->processAudioEvents(); context->getGraphManager()->preProcessGraph(); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp index 1e31c345c..ea386ef9d 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.cpp @@ -59,11 +59,7 @@ BiquadFilterNode::BiquadFilterNode( x2_.resize(MAX_CHANNEL_COUNT, 0.0f); y1_.resize(MAX_CHANNEL_COUNT, 0.0f); y2_.resize(MAX_CHANNEL_COUNT, 0.0f); - isInitialized_ = true; -} - -BiquadFilterType BiquadFilterNode::getType() { - return type_; + isInitialized_.store(true, std::memory_order_release); } void BiquadFilterNode::setType(BiquadFilterType type) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h index 3763f9c2e..bed69eaeb 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/BiquadFilterNode.h @@ -59,7 +59,6 @@ class BiquadFilterNode : public AudioNode { const std::shared_ptr &context, const BiquadFilterOptions &options); - [[nodiscard]] BiquadFilterType getType(); void setType(BiquadFilterType); [[nodiscard]] std::shared_ptr getFrequencyParam() const; [[nodiscard]] std::shared_ptr getDetuneParam() const; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp index df8794ad8..22a8d6b15 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp @@ -26,15 +26,7 @@ ConvolverNode::ConvolverNode( buffer_(nullptr), internalBuffer_(nullptr) { setBuffer(options.buffer); - isInitialized_ = true; -} - -bool ConvolverNode::getNormalize_() const { - return normalize_; -} - -const std::shared_ptr &ConvolverNode::getBuffer() const { - return buffer_; + isInitialized_.store(true, std::memory_order_release); } void ConvolverNode::setNormalize(bool normalize) { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h index 0f3fcfdbb..b5b410275 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h @@ -25,8 +25,6 @@ class ConvolverNode : public AudioNode { const std::shared_ptr &context, const ConvolverOptions &options); - [[nodiscard]] bool getNormalize_() const; - [[nodiscard]] const std::shared_ptr &getBuffer() const; void setNormalize(bool normalize); void setBuffer(const std::shared_ptr &buffer); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp index 2b54e4656..c514d0f00 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/DelayNode.cpp @@ -19,7 +19,7 @@ DelayNode::DelayNode(const std::shared_ptr &context, const Del 1), // +1 to enable delayTime equal to maxDelayTime channelCount_, context->getSampleRate())) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr DelayNode::getDelayTimeParam() const { @@ -30,11 +30,7 @@ void DelayNode::onInputDisabled() { numberOfEnabledInputNodes_ -= 1; if (isEnabled() && numberOfEnabledInputNodes_ == 0) { signalledToStop_ = true; - if (std::shared_ptr context = context_.lock()) { - remainingFrames_ = delayTimeParam_->getValue() * context->getSampleRate(); - } else { - remainingFrames_ = 0; - } + remainingFrames_ = delayTimeParam_->getValue() * getContextSampleRate(); } } @@ -98,8 +94,11 @@ std::shared_ptr DelayNode::processNode( // normal processing std::shared_ptr context = context_.lock(); - if (context == nullptr) + if (context == nullptr) { + processingBuffer->zero(); return processingBuffer; + } + auto delayTime = delayTimeParam_->processKRateParam(framesToProcess, context->getCurrentTime()); size_t writeIndex = static_cast(readIndex_ + delayTime * context->getSampleRate()) % delayBuffer_->getSize(); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp index 69bbf4c9c..48687bc42 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/GainNode.cpp @@ -16,7 +16,7 @@ GainNode::GainNode(const std::shared_ptr &context, const GainO MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context)) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr GainNode::getGainParam() const { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp index 079c066b5..162259bd5 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.cpp @@ -23,10 +23,10 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include #include #include +#include #include #include #include @@ -39,7 +39,9 @@ namespace audioapi { IIRFilterNode::IIRFilterNode( const std::shared_ptr &context, const IIRFilterOptions &options) - : AudioNode(context, options), feedforward_(options.feedforward), feedback_(options.feedback) { + : AudioNode(context, options), + feedforward_(createNormalizedVector(options.feedforward, options.feedback[0])), + feedback_(createNormalizedVector(options.feedback, options.feedback[0])) { int maxChannels = MAX_CHANNEL_COUNT; xBuffers_.resize(maxChannels); @@ -51,20 +53,7 @@ IIRFilterNode::IIRFilterNode( yBuffers_[c].resize(bufferLength, 0.0f); } - size_t feedforwardLength = feedforward_.size(); - size_t feedbackLength = feedback_.size(); - - if (feedback_[0] != 1) { - float scale = feedback_[0]; - for (unsigned k = 1; k < feedbackLength; ++k) - feedback_[k] /= scale; - - for (unsigned k = 0; k < feedforwardLength; ++k) - feedforward_[k] /= scale; - - feedback_[0] = 1.0f; - } - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } // Compute Z-transform of the filter @@ -88,7 +77,7 @@ void IIRFilterNode::getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - size_t length) { + size_t length) const { std::shared_ptr context = context_.lock(); if (context == nullptr) return; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h index 28ebcef9d..deba8fd60 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/IIRFilterNode.h @@ -42,11 +42,12 @@ class IIRFilterNode : public AudioNode { const std::shared_ptr &context, const IIRFilterOptions &options); + /// @note Thread safe, because feedforward_ and feedback_ are readonly after construction. void getFrequencyResponse( const float *frequencyArray, float *magResponseOutput, float *phaseResponseOutput, - size_t length); + size_t length) const; protected: std::shared_ptr processNode( @@ -56,8 +57,8 @@ class IIRFilterNode : public AudioNode { private: static constexpr size_t bufferLength = 32; - std::vector feedforward_; - std::vector feedback_; + const std::vector feedforward_; + const std::vector feedback_; std::vector> xBuffers_; // xBuffers_[channel][index] std::vector> yBuffers_; @@ -71,5 +72,18 @@ class IIRFilterNode : public AudioNode { result = result * z + std::complex(coefficients[k]); return result; } + + static std::vector createNormalizedVector( + const std::vector &inputVector, + float scaleFactor) { + std::vector result = inputVector; + if (scaleFactor != 1.0f && scaleFactor != 0.0f && !result.empty()) { + for (float &val : result) { + val /= scaleFactor; + } + } + + return result; + } }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp index 7b81797c5..5b971d3c4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/StereoPannerNode.cpp @@ -15,7 +15,7 @@ StereoPannerNode::StereoPannerNode( const StereoPannerOptions &options) : AudioNode(context, options), panParam_(std::make_shared(options.pan, -1.0f, 1.0f, context)) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr StereoPannerNode::getPanParam() const { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp index 7ae64471c..7ba53d6bc 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include @@ -21,29 +21,18 @@ WaveShaperNode::WaveShaperNode( waveShapers_.emplace_back(std::make_unique(nullptr)); } setCurve(options.curve); - isInitialized_ = true; -} - -OverSampleType WaveShaperNode::getOversample() const { - return oversample_.load(std::memory_order_acquire); + isInitialized_.store(true, std::memory_order_release); } void WaveShaperNode::setOversample(OverSampleType type) { - std::scoped_lock lock(mutex_); - oversample_.store(type, std::memory_order_release); + oversample_ = type; for (int i = 0; i < waveShapers_.size(); i++) { waveShapers_[i]->setOversample(type); } } -std::shared_ptr WaveShaperNode::getCurve() const { - std::scoped_lock lock(mutex_); - return curve_; -} - -void WaveShaperNode::setCurve(const std::shared_ptr &curve) { - std::scoped_lock lock(mutex_); +void WaveShaperNode::setCurve(const std::shared_ptr &curve) { curve_ = curve; for (int i = 0; i < waveShapers_.size(); i++) { @@ -54,16 +43,6 @@ void WaveShaperNode::setCurve(const std::shared_ptr &curve) { std::shared_ptr WaveShaperNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (!isInitialized_) { - return processingBuffer; - } - - std::unique_lock lock(mutex_, std::try_to_lock); - - if (!lock.owns_lock()) { - return processingBuffer; - } - if (curve_ == nullptr) { return processingBuffer; } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h index 18a3f96a2..23aabfb92 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WaveShaperNode.h @@ -15,7 +15,7 @@ namespace audioapi { class AudioBuffer; -class AudioArrayBuffer; +class AudioArray; struct WaveShaperOptions; class WaveShaperNode : public AudioNode { @@ -24,11 +24,8 @@ class WaveShaperNode : public AudioNode { const std::shared_ptr &context, const WaveShaperOptions &options); - [[nodiscard]] OverSampleType getOversample() const; - [[nodiscard]] std::shared_ptr getCurve() const; - void setOversample(OverSampleType); - void setCurve(const std::shared_ptr &curve); + void setCurve(const std::shared_ptr &curve); protected: std::shared_ptr processNode( @@ -36,9 +33,8 @@ class WaveShaperNode : public AudioNode { int framesToProcess) override; private: - std::atomic oversample_; - std::shared_ptr curve_; - mutable std::mutex mutex_; + OverSampleType oversample_; + std::shared_ptr curve_; std::vector> waveShapers_{}; }; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp index cac5c720a..d66ef27cf 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletNode.cpp @@ -18,7 +18,7 @@ WorkletNode::WorkletNode( bufferLength_(bufferLength), inputChannelCount_(inputChannelCount), curBuffIndex_(0) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletNode::processNode( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp index f1bb4c642..27f5d7d0a 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp @@ -18,7 +18,8 @@ WorkletProcessingNode::WorkletProcessingNode( inputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); outputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); } - isInitialized_ = true; + + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletProcessingNode::processNode( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp index f90b8195d..176fbe95e 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,58 +39,42 @@ std::shared_ptr AudioBufferBaseSourceNode::getPlaybackRateParam() co } void AudioBufferBaseSourceNode::setOnPositionChangedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onPositionChangedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); - - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::POSITION_CHANGED, oldCallbackId); - } + onPositionChangedCallbackId_ = callbackId; } void AudioBufferBaseSourceNode::setOnPositionChangedInterval(int interval) { - if (std::shared_ptr context = context_.lock()) { - onPositionChangedInterval_ = - static_cast(context->getSampleRate() * static_cast(interval) / 1000); - } + onPositionChangedInterval_ = + static_cast(getContextSampleRate() * static_cast(interval) / 1000); } int AudioBufferBaseSourceNode::getOnPositionChangedInterval() const { return onPositionChangedInterval_; } -std::mutex &AudioBufferBaseSourceNode::getBufferLock() { - return bufferLock_; -} - double AudioBufferBaseSourceNode::getInputLatency() const { if (pitchCorrection_) { - if (std::shared_ptr context = context_.lock()) { - return static_cast(stretch_->inputLatency()) / context->getSampleRate(); - } else { - return 0; - } + return static_cast(stretch_->inputLatency()) / getContextSampleRate(); } return 0; } double AudioBufferBaseSourceNode::getOutputLatency() const { if (pitchCorrection_) { - if (std::shared_ptr context = context_.lock()) { - return static_cast(stretch_->outputLatency()) / context->getSampleRate(); - } else { - return 0; - } + return static_cast(stretch_->outputLatency()) / getContextSampleRate(); } 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 139594fdb..9f7b709d4 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,11 +28,12 @@ class AudioBufferBaseSourceNode : public AudioScheduledSourceNode { [[nodiscard]] double getInputLatency() const; [[nodiscard]] double getOutputLatency() const; + /// @note Thread safe, because does not access state of the node. + void unregisterOnPositionChangedCallback(uint64_t callbackId); + protected: // pitch correction - bool pitchCorrection_; - - std::mutex bufferLock_; + const bool pitchCorrection_; // pitch correction std::shared_ptr> stretch_; @@ -45,11 +46,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 dbee512b9..5b45a11a4 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -23,7 +24,7 @@ AudioBufferQueueSourceNode::AudioBufferQueueSourceNode( const BaseAudioBufferSourceOptions &options) : AudioBufferBaseSourceNode(context, options) { buffers_ = {}; - stretch_->presetDefault(channelCount_, context->getSampleRate()); + stretch_->presetDefault(static_cast(channelCount_), context->getSampleRate()); if (options.pitchCorrection) { // If pitch correction is enabled, add extra frames at the end @@ -35,13 +36,7 @@ AudioBufferQueueSourceNode::AudioBufferQueueSourceNode( tailBuffer_->zero(); } - isInitialized_ = true; -} - -AudioBufferQueueSourceNode::~AudioBufferQueueSourceNode() { - Locker locker(getBufferLock()); - - buffers_ = {}; + isInitialized_.store(true, std::memory_order_release); } void AudioBufferQueueSourceNode::stop(double when) { @@ -58,7 +53,7 @@ void AudioBufferQueueSourceNode::start(double when) { void AudioBufferQueueSourceNode::start(double when, double offset) { start(when); - if (buffers_.empty()) { + if (buffers_.empty() || offset < 0) { return; } @@ -71,45 +66,50 @@ void AudioBufferQueueSourceNode::pause() { isPaused_ = true; } -std::string AudioBufferQueueSourceNode::enqueueBuffer(const std::shared_ptr &buffer) { - auto locker = Locker(getBufferLock()); - buffers_.emplace(bufferId_, buffer); +void AudioBufferQueueSourceNode::enqueueBuffer(const std::shared_ptr &buffer, size_t bufferId) { + buffers_.emplace_back(bufferId, buffer); if (tailBuffer_ != nullptr) { addExtraTailFrames_ = true; } - - return std::to_string(bufferId_++); } void AudioBufferQueueSourceNode::dequeueBuffer(const size_t bufferId) { - auto locker = Locker(getBufferLock()); - if (buffers_.empty()) { - return; - } + if (auto context = context_.lock()) { + if (buffers_.empty()) { + return; + } - if (buffers_.front().first == bufferId) { - buffers_.pop(); - vReadIndex_ = 0.0; - return; - } + auto graphManager = context->getGraphManager(); - // If the buffer is not at the front, we need to remove it from the queue. - // And keep vReadIndex_ at the same position. - std::queue>> newQueue; - while (!buffers_.empty()) { - if (buffers_.front().first != bufferId) { - newQueue.push(buffers_.front()); - } - buffers_.pop(); + if (buffers_.front().first == bufferId) { + graphManager->addAudioBufferForDestruction(std::move(buffers_.front().second)); + buffers_.pop_front(); + vReadIndex_ = 0.0; + return; + } + + // If the buffer is not at the front, we need to remove it from the linked list.. + // And keep vReadIndex_ at the same position. + for (auto it = std::next(buffers_.begin()); it != buffers_.end(); ++it) { + if (it->first == bufferId) { + graphManager->addAudioBufferForDestruction(std::move(it->second)); + buffers_.erase(it); + return; + } + } } - std::swap(buffers_, newQueue); } void AudioBufferQueueSourceNode::clearBuffers() { - auto locker = Locker(getBufferLock()); - buffers_ = {}; - vReadIndex_ = 0.0; + if (auto context = context_.lock()) { + for (auto it = buffers_.begin(); it != buffers_.end(); ++it) { + context->getGraphManager()->addAudioBufferForDestruction(std::move(it->second)); + } + + buffers_.clear(); + vReadIndex_ = 0.0; + } } void AudioBufferQueueSourceNode::disable() { @@ -123,59 +123,49 @@ void AudioBufferQueueSourceNode::disable() { } AudioScheduledSourceNode::disable(); - buffers_ = {}; + clearBuffers(); } void AudioBufferQueueSourceNode::setOnBufferEndedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onBufferEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onBufferEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::BUFFER_ENDED, oldCallbackId); - } +void AudioBufferQueueSourceNode::unregisterOnBufferEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::BUFFER_ENDED, callbackId); } std::shared_ptr AudioBufferQueueSourceNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // no audio data to fill, zero the output and return. - if (buffers_.empty()) { - processingBuffer->zero(); - return processingBuffer; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBuffer, framesToProcess); - } else { - processWithPitchCorrection(processingBuffer, framesToProcess); - } + // no audio data to fill, zero the output and return. + if (buffers_.empty()) { + processingBuffer->zero(); + return processingBuffer; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBuffer, framesToProcess); } else { - processingBuffer->zero(); + processWithPitchCorrection(processingBuffer, framesToProcess); } + handleStopScheduled(); + return processingBuffer; } double AudioBufferQueueSourceNode::getCurrentPosition() const { - if (std::shared_ptr context = context_.lock()) { - return dsp::sampleFrameToTime(static_cast(vReadIndex_), context->getSampleRate()) + - playedBuffersDuration_; - } else { - return 0.0; - } + return dsp::sampleFrameToTime(static_cast(vReadIndex_), getContextSampleRate()) + + playedBuffersDuration_; } void AudioBufferQueueSourceNode::sendOnBufferEndedEvent(size_t bufferId, bool isLastBufferInQueue) { - auto onBufferEndedCallbackId = onBufferEndedCallbackId_.load(std::memory_order_acquire); - - if (onBufferEndedCallbackId != 0) { + if (onBufferEndedCallbackId_ != 0) { std::unordered_map body = { {"bufferId", std::to_string(bufferId)}, {"isLastBufferInQueue", isLastBufferInQueue}}; audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::BUFFER_ENDED, onBufferEndedCallbackId, body); + AudioEvent::BUFFER_ENDED, onBufferEndedCallbackId_, body); } } @@ -188,60 +178,64 @@ void AudioBufferQueueSourceNode::processWithoutInterpolation( size_t startOffset, size_t offsetLength, float playbackRate) { - auto readIndex = static_cast(vReadIndex_); - size_t writeIndex = startOffset; - - auto data = buffers_.front(); - auto bufferId = data.first; - auto buffer = data.second; - - size_t framesLeft = offsetLength; - - while (framesLeft > 0) { - size_t framesToEnd = buffer->getSize() - readIndex; - size_t framesToCopy = std::min(framesToEnd, framesLeft); - framesToCopy = framesToCopy > 0 ? framesToCopy : 0; - - assert(readIndex >= 0); - assert(writeIndex >= 0); - assert(readIndex + framesToCopy <= buffer->getSize()); - assert(writeIndex + framesToCopy <= processingBuffer->getSize()); - - processingBuffer->copy(*buffer, readIndex, writeIndex, framesToCopy); - - writeIndex += framesToCopy; - readIndex += framesToCopy; - framesLeft -= framesToCopy; - - if (readIndex >= buffer->getSize()) { - playedBuffersDuration_ += buffer->getDuration(); - buffers_.pop(); - - if (!(buffers_.empty() && addExtraTailFrames_)) { - sendOnBufferEndedEvent(bufferId, buffers_.empty()); - } - - if (buffers_.empty()) { - if (addExtraTailFrames_) { - buffers_.emplace(bufferId, tailBuffer_); - addExtraTailFrames_ = false; - } else { - processingBuffer->zero(writeIndex, framesLeft); - readIndex = 0; - - break; - } + if (auto context = context_.lock()) { + auto readIndex = static_cast(vReadIndex_); + size_t writeIndex = startOffset; + + auto data = buffers_.front(); + auto bufferId = data.first; + auto buffer = data.second; + + size_t framesLeft = offsetLength; + + while (framesLeft > 0) { + size_t framesToEnd = buffer->getSize() - readIndex; + size_t framesToCopy = std::min(framesToEnd, framesLeft); + framesToCopy = framesToCopy > 0 ? framesToCopy : 0; + + assert(readIndex >= 0); + assert(writeIndex >= 0); + assert(readIndex + framesToCopy <= buffer->getSize()); + assert(writeIndex + framesToCopy <= processingBuffer->getSize()); + + processingBuffer->copy(*buffer, readIndex, writeIndex, framesToCopy); + + writeIndex += framesToCopy; + readIndex += framesToCopy; + framesLeft -= framesToCopy; + + if (readIndex >= buffer->getSize()) { + playedBuffersDuration_ += buffer->getDuration(); + buffers_.pop_front(); + + if (!(buffers_.empty() && addExtraTailFrames_)) { + sendOnBufferEndedEvent(bufferId, buffers_.empty()); + } + + if (buffers_.empty()) { + if (addExtraTailFrames_) { + buffers_.emplace_back(bufferId, tailBuffer_); + addExtraTailFrames_ = false; + } else { + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + processingBuffer->zero(writeIndex, framesLeft); + readIndex = 0; + + break; + } + } + + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + data = buffers_.front(); + bufferId = data.first; + buffer = data.second; + readIndex = 0; + } } - data = buffers_.front(); - bufferId = data.first; - buffer = data.second; - readIndex = 0; - } + // update reading index for next render quantum + vReadIndex_ = static_cast(readIndex); } - - // update reading index for next render quantum - vReadIndex_ = static_cast(readIndex); } void AudioBufferQueueSourceNode::processWithInterpolation( @@ -249,71 +243,75 @@ void AudioBufferQueueSourceNode::processWithInterpolation( size_t startOffset, size_t offsetLength, float playbackRate) { - size_t writeIndex = startOffset; - size_t framesLeft = offsetLength; - - auto data = buffers_.front(); - auto bufferId = data.first; - auto buffer = data.second; - - while (framesLeft > 0) { - auto readIndex = static_cast(vReadIndex_); - size_t nextReadIndex = readIndex + 1; - auto factor = static_cast(vReadIndex_ - static_cast(readIndex)); - - bool crossBufferInterpolation = false; - std::shared_ptr nextBuffer = nullptr; - - if (nextReadIndex >= buffer->getSize()) { - if (buffers_.size() > 1) { - auto tempQueue = buffers_; - tempQueue.pop(); - nextBuffer = tempQueue.front().second; - nextReadIndex = 0; - crossBufferInterpolation = true; - } else { - nextReadIndex = readIndex; - } - } - - for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i += 1) { - const auto destination = processingBuffer->getChannel(i)->span(); - const auto currentSource = buffer->getChannel(i)->span(); - - if (crossBufferInterpolation) { - const auto nextSource = nextBuffer->getChannel(i)->span(); - float currentSample = currentSource[readIndex]; - float nextSample = nextSource[nextReadIndex]; - destination[writeIndex] = currentSample + factor * (nextSample - currentSample); - } else { - destination[writeIndex] = - dsp::linearInterpolate(currentSource, readIndex, nextReadIndex, factor); - } - } - - writeIndex += 1; - // queue source node always use positive playbackRate - vReadIndex_ += std::abs(playbackRate); - framesLeft -= 1; - - if (vReadIndex_ >= static_cast(buffer->getSize())) { - playedBuffersDuration_ += buffer->getDuration(); - buffers_.pop(); - - sendOnBufferEndedEvent(bufferId, buffers_.empty()); - - if (buffers_.empty()) { - processingBuffer->zero(writeIndex, framesLeft); - vReadIndex_ = 0.0; - break; - } - - vReadIndex_ = vReadIndex_ - buffer->getSize(); - data = buffers_.front(); - bufferId = data.first; - buffer = data.second; + if (auto context = context_.lock()) { + size_t writeIndex = startOffset; + size_t framesLeft = offsetLength; + + auto data = buffers_.front(); + auto bufferId = data.first; + auto buffer = data.second; + + while (framesLeft > 0) { + auto readIndex = static_cast(vReadIndex_); + size_t nextReadIndex = readIndex + 1; + auto factor = static_cast(vReadIndex_ - static_cast(readIndex)); + + bool crossBufferInterpolation = false; + std::shared_ptr nextBuffer = nullptr; + + if (nextReadIndex >= buffer->getSize()) { + if (buffers_.size() > 1) { + auto tempQueue = buffers_; + tempQueue.pop_front(); + nextBuffer = tempQueue.front().second; + nextReadIndex = 0; + crossBufferInterpolation = true; + } else { + nextReadIndex = readIndex; + } + } + + for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i += 1) { + const auto destination = processingBuffer->getChannel(i)->span(); + const auto currentSource = buffer->getChannel(i)->span(); + + if (crossBufferInterpolation) { + const auto nextSource = nextBuffer->getChannel(i)->span(); + float currentSample = currentSource[readIndex]; + float nextSample = nextSource[nextReadIndex]; + destination[writeIndex] = currentSample + factor * (nextSample - currentSample); + } else { + destination[writeIndex] = + dsp::linearInterpolate(currentSource, readIndex, nextReadIndex, factor); + } + } + + writeIndex += 1; + // queue source node always use positive playbackRate + vReadIndex_ += std::abs(playbackRate); + framesLeft -= 1; + + if (vReadIndex_ >= static_cast(buffer->getSize())) { + playedBuffersDuration_ += buffer->getDuration(); + buffers_.pop_front(); + + sendOnBufferEndedEvent(bufferId, buffers_.empty()); + + if (buffers_.empty()) { + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + processingBuffer->zero(writeIndex, framesLeft); + vReadIndex_ = 0.0; + break; + } + + context->getGraphManager()->addAudioBufferForDestruction(std::move(buffer)); + vReadIndex_ = vReadIndex_ - buffer->getSize(); + data = buffers_.front(); + bufferId = data.first; + buffer = data.second; + } + } } - } } } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h index 7ab344fef..d02302f42 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h @@ -6,8 +6,8 @@ #include #include +#include #include -#include #include namespace audioapi { @@ -21,7 +21,6 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { explicit AudioBufferQueueSourceNode( const std::shared_ptr &context, const BaseAudioBufferSourceOptions &options); - ~AudioBufferQueueSourceNode() override; void stop(double when) override; @@ -29,13 +28,16 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { void start(double when, double offset); void pause(); - std::string enqueueBuffer(const std::shared_ptr &buffer); + void enqueueBuffer(const std::shared_ptr &buffer, size_t bufferId); void dequeueBuffer(size_t bufferId); void clearBuffers(); void disable() override; void setOnBufferEndedCallbackId(uint64_t callbackId); + /// @note Thread safe, because does not access state of the node. + void unregisterOnBufferEndedCallback(uint64_t callbackId); + protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -47,8 +49,7 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { private: // User provided buffers - std::queue>> buffers_; - size_t bufferId_ = 0; + std::list>> buffers_; bool isPaused_ = false; bool addExtraTailFrames_ = false; @@ -56,7 +57,7 @@ class AudioBufferQueueSourceNode : public AudioBufferBaseSourceNode { double playedBuffersDuration_ = 0; - std::atomic onBufferEndedCallbackId_ = 0; // 0 means no callback + uint64_t onBufferEndedCallbackId_ = 0; // 0 means no callback void processWithoutInterpolation( const std::shared_ptr &processingBuffer, diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp index a2a9ba4c0..794dc318c 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp @@ -1,15 +1,17 @@ -#include #include #include #include +#include #include #include #include #include +#include #include #include #include #include +#include namespace audioapi { @@ -18,40 +20,10 @@ AudioBufferSourceNode::AudioBufferSourceNode( const AudioBufferSourceOptions &options) : AudioBufferBaseSourceNode(context, options), loop_(options.loop), - loopSkip_(false), + loopSkip_(options.loopSkip), loopStart_(options.loopStart), loopEnd_(options.loopEnd) { - buffer_ = std::shared_ptr(options.buffer); - alignedBuffer_ = std::shared_ptr(nullptr); - - isInitialized_ = true; -} - -AudioBufferSourceNode::~AudioBufferSourceNode() { - Locker locker(getBufferLock()); - - buffer_.reset(); - alignedBuffer_.reset(); -} - -bool AudioBufferSourceNode::getLoop() const { - return loop_; -} - -bool AudioBufferSourceNode::getLoopSkip() const { - return loopSkip_; -} - -double AudioBufferSourceNode::getLoopStart() const { - return loopStart_; -} - -double AudioBufferSourceNode::getLoopEnd() const { - return loopEnd_; -} - -std::shared_ptr AudioBufferSourceNode::getBuffer() const { - return buffer_; + isInitialized_.store(true, std::memory_order_release); } void AudioBufferSourceNode::setLoop(bool loop) { @@ -64,9 +36,7 @@ void AudioBufferSourceNode::setLoopSkip(bool loopSkip) { void AudioBufferSourceNode::setLoopStart(double loopStart) { if (loopSkip_) { - if (std::shared_ptr context = context_.lock()) { - vReadIndex_ = loopStart * context->getSampleRate(); - } + vReadIndex_ = loopStart * getContextSampleRate(); } loopStart_ = loopStart; } @@ -75,40 +45,45 @@ void AudioBufferSourceNode::setLoopEnd(double loopEnd) { loopEnd_ = loopEnd; } -void AudioBufferSourceNode::setBuffer(const std::shared_ptr &buffer) { - Locker locker(getBufferLock()); +void AudioBufferSourceNode::setBuffer( + const std::shared_ptr &buffer, + const std::shared_ptr &playbackRateBuffer, + const std::shared_ptr &audioBuffer) { std::shared_ptr context = context_.lock(); - if (buffer == nullptr || context == nullptr) { - buffer_ = std::shared_ptr(nullptr); - alignedBuffer_ = std::shared_ptr(nullptr); - loopEnd_ = 0; + if (context == nullptr) { return; } - buffer_ = buffer; - channelCount_ = buffer_->getNumberOfChannels(); + auto graphManager = context->getGraphManager(); - stretch_->presetDefault(static_cast(channelCount_), buffer_->getSampleRate()); + if (buffer_ != nullptr) { + graphManager->addAudioBufferForDestruction(std::move(buffer_)); + } - if (pitchCorrection_) { - int extraTailFrames = - static_cast((getInputLatency() + getOutputLatency()) * context->getSampleRate()); - size_t totalSize = buffer_->getSize() + extraTailFrames; + if (playbackRateBuffer_ != nullptr) { + graphManager->addAudioBufferForDestruction(std::move(playbackRateBuffer_)); + } - alignedBuffer_ = std::make_shared(totalSize, channelCount_, buffer_->getSampleRate()); - alignedBuffer_->copy(*buffer_, 0, 0, buffer_->getSize()); + graphManager->addAudioBufferForDestruction(std::move(audioBuffer_)); - alignedBuffer_->zero(buffer_->getSize(), extraTailFrames); - } else { - alignedBuffer_ = std::make_shared(*buffer_); + if (buffer == nullptr) { + loopEnd_ = 0; + channelCount_ = 1; + + buffer_ = nullptr; + playbackRateBuffer_ = nullptr; + audioBuffer_ = + std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); + return; } - audioBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - playbackRateBuffer_ = std::make_shared( - RENDER_QUANTUM_SIZE * 3, channelCount_, context->getSampleRate()); + buffer_ = buffer; + playbackRateBuffer_ = playbackRateBuffer; + audioBuffer_ = audioBuffer; + channelCount_ = buffer_->getNumberOfChannels(); loopEnd_ = buffer_->getDuration(); + stretch_->presetDefault(static_cast(channelCount_), buffer_->getSampleRate()); } void AudioBufferSourceNode::start(double when, double offset, double duration) { @@ -118,54 +93,48 @@ void AudioBufferSourceNode::start(double when, double offset, double duration) { AudioScheduledSourceNode::stop(when + duration); } - if (!alignedBuffer_) { + if (buffer_ == nullptr) { return; } - offset = - std::min(offset, static_cast(alignedBuffer_->getSize()) / alignedBuffer_->getSampleRate()); + offset = std::min(offset, static_cast(buffer_->getSize()) / buffer_->getSampleRate()); if (loop_) { offset = std::min(offset, loopEnd_); } - vReadIndex_ = static_cast(alignedBuffer_->getSampleRate() * offset); + vReadIndex_ = static_cast(buffer_->getSampleRate() * offset); } void AudioBufferSourceNode::disable() { AudioScheduledSourceNode::disable(); - alignedBuffer_.reset(); } void AudioBufferSourceNode::setOnLoopEndedCallbackId(uint64_t callbackId) { - auto oldCallbackId = onLoopEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onLoopEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::LOOP_ENDED, oldCallbackId); - } +void AudioBufferSourceNode::unregisterOnLoopEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::LOOP_ENDED, callbackId); } std::shared_ptr AudioBufferSourceNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (auto locker = Locker::tryLock(getBufferLock())) { - // No audio data to fill, zero the output and return. - if (!alignedBuffer_) { - processingBuffer->zero(); - return processingBuffer; - } - - if (!pitchCorrection_) { - processWithoutPitchCorrection(processingBuffer, framesToProcess); - } else { - processWithPitchCorrection(processingBuffer, framesToProcess); - } + // No audio data to fill, zero the output and return. + if (buffer_ == nullptr) { + processingBuffer->zero(); + return processingBuffer; + } - handleStopScheduled(); + if (!pitchCorrection_) { + processWithoutPitchCorrection(processingBuffer, framesToProcess); } else { - processingBuffer->zero(); + processWithPitchCorrection(processingBuffer, framesToProcess); } + handleStopScheduled(); + return processingBuffer; } @@ -174,10 +143,9 @@ double AudioBufferSourceNode::getCurrentPosition() const { } void AudioBufferSourceNode::sendOnLoopEndedEvent() { - auto onLoopEndedCallbackId = onLoopEndedCallbackId_.load(std::memory_order_acquire); - if (onLoopEndedCallbackId != 0) { + if (onLoopEndedCallbackId_ != 0) { audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::LOOP_ENDED, onLoopEndedCallbackId, {}); + AudioEvent::LOOP_ENDED, onLoopEndedCallbackId_, {}); } } @@ -195,15 +163,8 @@ void AudioBufferSourceNode::processWithoutInterpolation( auto readIndex = static_cast(vReadIndex_); size_t writeIndex = startOffset; - size_t frameStart; - size_t frameEnd; - if (std::shared_ptr context = context_.lock()) { - frameStart = static_cast(getVirtualStartFrame(context->getSampleRate())); - frameEnd = static_cast(getVirtualEndFrame(context->getSampleRate())); - } else { - processingBuffer->zero(); - return; - } + auto frameStart = static_cast(getVirtualStartFrame(getContextSampleRate())); + auto frameEnd = static_cast(getVirtualEndFrame(getContextSampleRate())); size_t frameDelta = frameEnd - frameStart; size_t framesLeft = offsetLength; @@ -223,16 +184,16 @@ void AudioBufferSourceNode::processWithoutInterpolation( assert(readIndex >= 0); assert(writeIndex >= 0); - assert(readIndex + framesToCopy <= alignedBuffer_->getSize()); + assert(readIndex + framesToCopy <= buffer_->getSize()); assert(writeIndex + framesToCopy <= processingBuffer->getSize()); // Direction is forward, we can normally copy the data if (direction == 1) { - processingBuffer->copy(*alignedBuffer_, readIndex, writeIndex, framesToCopy); + processingBuffer->copy(*buffer_, readIndex, writeIndex, framesToCopy); } else { for (size_t ch = 0; ch < processingBuffer->getNumberOfChannels(); ch += 1) { processingBuffer->getChannel(ch)->copyReverse( - *alignedBuffer_->getChannel(ch), readIndex, writeIndex, framesToCopy); + *buffer_->getChannel(ch), readIndex, writeIndex, framesToCopy); } } @@ -268,15 +229,8 @@ void AudioBufferSourceNode::processWithInterpolation( size_t writeIndex = startOffset; - double vFrameStart; - double vFrameEnd; - if (std::shared_ptr context = context_.lock()) { - vFrameStart = getVirtualStartFrame(context->getSampleRate()); - vFrameEnd = getVirtualEndFrame(context->getSampleRate()); - } else { - processingBuffer->zero(); - return; - } + auto vFrameStart = getVirtualStartFrame(getContextSampleRate()); + auto vFrameEnd = getVirtualEndFrame(getContextSampleRate()); auto vFrameDelta = vFrameEnd - vFrameStart; auto frameStart = static_cast(vFrameStart); @@ -300,7 +254,7 @@ void AudioBufferSourceNode::processWithInterpolation( for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i++) { auto destination = processingBuffer->getChannel(i)->span(); - const auto source = alignedBuffer_->getChannel(i)->span(); + const auto source = buffer_->getChannel(i)->span(); destination[writeIndex] = dsp::linearInterpolate(source, readIndex, nextReadIndex, factor); } @@ -329,7 +283,7 @@ double AudioBufferSourceNode::getVirtualStartFrame(float sampleRate) const { } double AudioBufferSourceNode::getVirtualEndFrame(float sampleRate) { - auto inputBufferLength = static_cast(alignedBuffer_->getSize()); + auto inputBufferLength = static_cast(buffer_->getSize()); auto loopEndFrame = loopEnd_ * sampleRate; return loop_ && loopEndFrame > 0 && loopStart_ < loopEnd_ diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h index 5a28a2c95..33d2f93d8 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 @@ -20,19 +20,18 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { explicit AudioBufferSourceNode( const std::shared_ptr &context, const AudioBufferSourceOptions &options); - ~AudioBufferSourceNode() override; - - [[nodiscard]] bool getLoop() const; - [[nodiscard]] bool getLoopSkip() const; - [[nodiscard]] double getLoopStart() const; - [[nodiscard]] double getLoopEnd() const; - [[nodiscard]] std::shared_ptr getBuffer() const; void setLoop(bool loop); void setLoopSkip(bool loopSkip); void setLoopStart(double loopStart); void setLoopEnd(double loopEnd); - void setBuffer(const std::shared_ptr &buffer); + + /// @note Buffer can be set (not to nullptr) only once. + /// This is consistent with Web Audio API. + void setBuffer( + const std::shared_ptr &buffer, + const std::shared_ptr &playbackRateBuffer, + const std::shared_ptr &audioBuffer); using AudioScheduledSourceNode::start; void start(double when, double offset, double duration = -1); @@ -40,6 +39,9 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { void setOnLoopEndedCallbackId(uint64_t callbackId); + /// @note Thread safe, because does not access state of the node. + void unregisterOnLoopEndedCallback(uint64_t callbackId); + protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -55,9 +57,8 @@ class AudioBufferSourceNode : public AudioBufferBaseSourceNode { // User provided buffer std::shared_ptr buffer_; - std::shared_ptr alignedBuffer_; - std::atomic onLoopEndedCallbackId_ = 0; // 0 means no callback + uint64_t onLoopEndedCallbackId_ = 0; // 0 means no callback void sendOnLoopEndedEvent(); void processWithoutInterpolation( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp index a2e048436..e7935f45b 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp @@ -63,11 +63,11 @@ bool AudioScheduledSourceNode::isStopScheduled() { } void AudioScheduledSourceNode::setOnEndedCallbackId(const uint64_t callbackId) { - auto oldCallbackId = onEndedCallbackId_.exchange(callbackId, std::memory_order_acq_rel); + onEndedCallbackId_ = callbackId; +} - if (oldCallbackId != 0) { - audioEventHandlerRegistry_->unregisterHandler(AudioEvent::ENDED, oldCallbackId); - } +void AudioScheduledSourceNode::unregisterOnEndedCallback(uint64_t callbackId) { + audioEventHandlerRegistry_->unregisterHandler(AudioEvent::ENDED, callbackId); } void AudioScheduledSourceNode::updatePlaybackInfo( @@ -77,12 +77,6 @@ void AudioScheduledSourceNode::updatePlaybackInfo( size_t &nonSilentFramesToProcess, float sampleRate, size_t currentSampleFrame) { - if (!isInitialized_) { - startOffset = 0; - nonSilentFramesToProcess = 0; - return; - } - auto firstFrame = currentSampleFrame; size_t lastFrame = firstFrame + framesToProcess - 1; @@ -165,10 +159,9 @@ void AudioScheduledSourceNode::updatePlaybackInfo( void AudioScheduledSourceNode::disable() { AudioNode::disable(); - auto onEndedCallbackId = onEndedCallbackId_.load(std::memory_order_acquire); - if (onEndedCallbackId != 0) { + if (onEndedCallbackId_ != 0) { audioEventHandlerRegistry_->invokeHandlerWithEventBody( - AudioEvent::ENDED, onEndedCallbackId, {}); + AudioEvent::ENDED, onEndedCallbackId_, {}); } } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h index 118f5dc49..54203d6fa 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 @@ -44,13 +44,16 @@ class AudioScheduledSourceNode : public AudioNode { void disable() override; + /// @note 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/sources/ConstantSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp index 2516d1b4d..e55fdfaf1 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/ConstantSourceNode.cpp @@ -13,7 +13,7 @@ ConstantSourceNode::ConstantSourceNode( : AudioScheduledSourceNode(context) { offsetParam_ = std::make_shared( options.offset, MOST_NEGATIVE_SINGLE_FLOAT, MOST_POSITIVE_SINGLE_FLOAT, context); - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr ConstantSourceNode::getOffsetParam() const { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp index 62ef6bd8a..4819d8468 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/OscillatorNode.cpp @@ -29,7 +29,7 @@ OscillatorNode::OscillatorNode( audioBuffer_ = std::make_shared(RENDER_QUANTUM_SIZE, 1, context->getSampleRate()); - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr OscillatorNode::getFrequencyParam() const { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp index fe8acfff4..374ec5201 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp @@ -13,12 +13,11 @@ RecorderAdapterNode::RecorderAdapterNode(const std::shared_ptr : AudioNode(context, AudioScheduledSourceNodeOptions()) { // It should be marked as initialized only after it is connected to the // recorder. Internal buffer size is based on the recorder's buffer length. - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); } void RecorderAdapterNode::init(size_t bufferSize, int channelCount) { - std::shared_ptr context = context_.lock(); - if (isInitialized_ || context == nullptr) { + if (isInitialized_.load(std::memory_order_acquire)) { return; } @@ -42,12 +41,12 @@ void RecorderAdapterNode::init(size_t bufferSize, int channelCount) { // context output and not enforcing anything on the system output/input configuration. // A lot of words for a couple of lines of implementation :shrug: adapterOutputBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - isInitialized_ = true; + std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, getContextSampleRate()); + isInitialized_.store(true, std::memory_order_release); } void RecorderAdapterNode::cleanup() { - isInitialized_ = false; + isInitialized_.store(false, std::memory_order_release); buff_.clear(); adapterOutputBuffer_.reset(); } @@ -55,11 +54,6 @@ void RecorderAdapterNode::cleanup() { std::shared_ptr RecorderAdapterNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { - if (!isInitialized_) { - processingBuffer->zero(); - return processingBuffer; - } - readFrames(framesToProcess); processingBuffer->sum(*adapterOutputBuffer_, ChannelInterpretation::SPEAKERS); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h index 0e17b264d..043fa8690 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/RecorderAdapterNode.h @@ -28,7 +28,6 @@ class RecorderAdapterNode : public AudioNode { void init(size_t bufferSize, int channelCount); void cleanup(); - int channelCount_{}; // TODO: CircularOverflowableAudioBuffer std::vector> buff_; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp index bf8c60729..d54c32506 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.cpp @@ -8,10 +8,10 @@ * FFmpeg, you must comply with the terms of the LGPL for FFmpeg itself. */ -#include #include #include #include +#include #include #include #include @@ -38,7 +38,11 @@ StreamerNode::StreamerNode( bufferedAudioBufferSize_(0), audio_stream_index_(-1), maxResampledSamples_(0), - processedSamples_(0) {} + processedSamples_(0) { +#if !RN_AUDIO_API_FFMPEG_DISABLED + initialize(options.streamPath); +#endif // RN_AUDIO_API_FFMPEG_DISABLED +} #else StreamerNode::StreamerNode( const std::shared_ptr &context, @@ -52,60 +56,6 @@ StreamerNode::~StreamerNode() { #endif // RN_AUDIO_API_FFMPEG_DISABLED } -bool StreamerNode::initialize(const std::string &input_url) { -#if !RN_AUDIO_API_FFMPEG_DISABLED - streamPath_ = input_url; - std::shared_ptr context = context_.lock(); - if (context == nullptr) { - return false; - } - - if (isInitialized_) { - cleanup(); - } - - if (!openInput(input_url)) { - if (VERBOSE) - printf("Failed to open input\n"); - return false; - } - - if (!findAudioStream() || !setupDecoder() || !setupResampler(context->getSampleRate())) { - if (VERBOSE) - printf("Failed to find/setup audio stream\n"); - cleanup(); - return false; - } - - pkt_ = av_packet_alloc(); - frame_ = av_frame_alloc(); - - if (pkt_ == nullptr || frame_ == nullptr) { - if (VERBOSE) - printf("Failed to allocate packet or frame\n"); - cleanup(); - return false; - } - - channelCount_ = codecpar_->ch_layout.nb_channels; - audioBuffer_ = - std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); - - auto [sender, receiver] = channels::spsc::channel< - StreamingData, - STREAMER_NODE_SPSC_OVERFLOW_STRATEGY, - STREAMER_NODE_SPSC_WAIT_STRATEGY>(CHANNEL_CAPACITY); - sender_ = std::move(sender); - receiver_ = std::move(receiver); - - streamingThread_ = std::thread(&StreamerNode::streamAudio, this); - isInitialized_ = true; - return true; -#else - return false; -#endif // RN_AUDIO_API_FFMPEG_DISABLED -} - std::shared_ptr StreamerNode::processNode( const std::shared_ptr &processingBuffer, int framesToProcess) { @@ -150,7 +100,8 @@ std::shared_ptr StreamerNode::processNode( } } if (bufferedAudioBuffer_ != nullptr) { - processingBuffer->copy(*bufferedAudioBuffer_, processedSamples_, alreadyProcessed, framesToProcess); + processingBuffer->copy( + *bufferedAudioBuffer_, processedSamples_, alreadyProcessed, framesToProcess); processedSamples_ += framesToProcess; } #endif // RN_AUDIO_API_FFMPEG_DISABLED @@ -159,6 +110,56 @@ std::shared_ptr StreamerNode::processNode( } #if !RN_AUDIO_API_FFMPEG_DISABLED +bool StreamerNode::initialize(const std::string &input_url) { + streamPath_ = input_url; + std::shared_ptr context = context_.lock(); + if (context == nullptr) { + return false; + } + + if (isInitialized_.load(std::memory_order_acquire)) { + return false; + } + + if (!openInput(input_url)) { + if (VERBOSE) + printf("Failed to open input\n"); + return false; + } + + if (!findAudioStream() || !setupDecoder() || !setupResampler(context->getSampleRate())) { + if (VERBOSE) + printf("Failed to find/setup audio stream\n"); + cleanup(); + return false; + } + + pkt_ = av_packet_alloc(); + frame_ = av_frame_alloc(); + + if (pkt_ == nullptr || frame_ == nullptr) { + if (VERBOSE) + printf("Failed to allocate packet or frame\n"); + cleanup(); + return false; + } + + channelCount_ = codecpar_->ch_layout.nb_channels; + audioBuffer_ = + std::make_shared(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate()); + + auto [sender, receiver] = channels::spsc::channel< + StreamingData, + STREAMER_NODE_SPSC_OVERFLOW_STRATEGY, + STREAMER_NODE_SPSC_WAIT_STRATEGY>(CHANNEL_CAPACITY); + sender_ = std::move(sender); + receiver_ = std::move(receiver); + + streamingThread_ = std::thread(&StreamerNode::streamAudio, this); + isInitialized_.store(true, std::memory_order_release); + return true; +} + bool StreamerNode::setupResampler(float outSampleRate) { // Allocate resampler context swrCtx_ = swr_alloc(); @@ -346,10 +347,10 @@ void StreamerNode::cleanup() { } audio_stream_index_ = -1; - isInitialized_ = false; decoder_ = nullptr; codecpar_ = nullptr; maxResampledSamples_ = 0; + isInitialized_.store(false, std::memory_order_release); } #endif // RN_AUDIO_API_FFMPEG_DISABLED } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h index 45de420b2..842a00538 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/StreamerNode.h @@ -68,15 +68,6 @@ class StreamerNode : public AudioScheduledSourceNode { const StreamerOptions &options); ~StreamerNode() override; - /** - * @brief Initialize all necessary ffmpeg components for streaming audio - */ - bool initialize(const std::string &inputUrl); - - std::string getStreamPath() const { - return streamPath_; - } - protected: std::shared_ptr processNode( const std::shared_ptr &processingBuffer, @@ -113,6 +104,12 @@ class StreamerNode : public AudioScheduledSourceNode { STREAMER_NODE_SPSC_WAIT_STRATEGY> receiver_; + /// @brief Initialize the StreamerNode by opening the input stream, + /// finding the audio stream, setting up the decoder, and starting the streaming thread. + /// @param inputUrl The URL of the input stream + /// @return true if initialization was successful, false otherwise + bool initialize(const std::string &inputUrl); + /** * @brief Setting up the resampler * @param outSampleRate Sample rate for the output audio diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp index 09d9450a8..bfaf57555 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/sources/WorkletSourceNode.cpp @@ -9,14 +9,14 @@ WorkletSourceNode::WorkletSourceNode( const std::shared_ptr &context, WorkletsRunner &&workletRunner) : AudioScheduledSourceNode(context), workletRunner_(std::move(workletRunner)) { - isInitialized_ = true; - // Prepare buffers for audio processing size_t outputChannelCount = this->getChannelCount(); outputBuffsHandles_.resize(outputChannelCount); for (size_t i = 0; i < outputChannelCount; ++i) { outputBuffsHandles_[i] = std::make_shared(RENDER_QUANTUM_SIZE); } + + isInitialized_.store(true, std::memory_order_release); } std::shared_ptr WorkletSourceNode::processNode( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/types/WindowType.h b/packages/react-native-audio-api/common/cpp/audioapi/core/types/WindowType.h new file mode 100644 index 000000000..9af9163cc --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/types/WindowType.h @@ -0,0 +1,5 @@ +#pragma once + +namespace audioapi { +enum class WindowType { BLACKMAN, HANN }; +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp index f5bc89fcb..3755ec935 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.cpp @@ -151,7 +151,7 @@ void AudioGraphManager::addAudioParam(const std::shared_ptr ¶m) sender_.send(std::move(event)); } -void AudioGraphManager::addAudioBuffeForDestruction(std::shared_ptr buffer) { +void AudioGraphManager::addAudioBufferForDestruction(std::shared_ptr buffer) { // direct access because this is called from the Audio thread audioBuffers_.emplace_back(std::move(buffer)); } diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h index 401885498..467aa9355 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioGraphManager.h @@ -103,7 +103,7 @@ class AudioGraphManager { /// @brief Adds an audio buffer to the manager for destruction. /// @note Called directly from the Audio thread (bypasses SPSC). - void addAudioBuffeForDestruction(std::shared_ptr buffer); + void addAudioBufferForDestruction(std::shared_ptr buffer); void cleanup(); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h index 5307e837e..ce333c176 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/Constants.h @@ -11,6 +11,7 @@ namespace audioapi { static constexpr int RENDER_QUANTUM_SIZE = 128; static constexpr size_t MAX_FFT_SIZE = 32768; static constexpr int MAX_CHANNEL_COUNT = 32; +static constexpr float DEFAULT_SAMPLE_RATE = 44100.0f; // stretcher static constexpr float UPPER_FREQUENCY_LIMIT_DETECTION = 333.0f; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h b/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h index c3316aeba..98fca1aeb 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/types/NodeOptions.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,7 @@ struct AnalyserOptions : AudioNodeOptions { float minDecibels = -100.0f; float maxDecibels = -30.0f; float smoothingTimeConstant = 0.8f; + WindowType windowType = WindowType::BLACKMAN; }; struct BiquadFilterOptions : AudioNodeOptions { @@ -104,6 +106,12 @@ struct AudioBufferSourceOptions : BaseAudioBufferSourceOptions { float loopStart = 0.0f; float loopEnd = 0.0f; bool loop = false; + bool loopSkip = false; + + explicit AudioBufferSourceOptions(BaseAudioBufferSourceOptions &&options) + : BaseAudioBufferSourceOptions(options) { + channelCount = 1; + } }; struct StreamerOptions : AudioScheduledSourceNodeOptions { diff --git a/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp b/packages/react-native-audio-api/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp index 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/AudioScheduledSourceTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/AudioScheduledSourceTest.cpp index f8ce75501..ed0c9beb5 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/AudioScheduledSourceTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/AudioScheduledSourceTest.cpp @@ -29,7 +29,7 @@ class TestableAudioScheduledSourceNode : public AudioScheduledSourceNode { public: explicit TestableAudioScheduledSourceNode(std::shared_ptr context) : AudioScheduledSourceNode(context) { - isInitialized_ = true; + isInitialized_.store(true, std::memory_order_release); } void updatePlaybackInfo( diff --git a/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp b/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp index 3382e74e1..6faa6bfe4 100644 --- a/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp +++ b/packages/react-native-audio-api/common/cpp/test/src/core/effects/WaveShaperNodeTest.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -28,7 +28,7 @@ class TestableWaveShaperNode : public WaveShaperNode { public: explicit TestableWaveShaperNode(std::shared_ptr context) : WaveShaperNode(context, WaveShaperOptions()) { - testCurve_ = std::make_shared(3); + testCurve_ = std::make_shared(3); auto data = testCurve_->span(); data[0] = -2.0f; data[1] = 0.0f; @@ -41,7 +41,7 @@ class TestableWaveShaperNode : public WaveShaperNode { return WaveShaperNode::processNode(processingBuffer, framesToProcess); } - std::shared_ptr testCurve_; + std::shared_ptr testCurve_; }; TEST_F(WaveShaperNodeTest, WaveShaperNodeCanBeCreated) { @@ -52,7 +52,6 @@ TEST_F(WaveShaperNodeTest, WaveShaperNodeCanBeCreated) { TEST_F(WaveShaperNodeTest, NullCanBeAsignedToCurve) { auto waveShaper = context->createWaveShaper(WaveShaperOptions()); ASSERT_NO_THROW(waveShaper->setCurve(nullptr)); - ASSERT_EQ(waveShaper->getCurve(), nullptr); } TEST_F(WaveShaperNodeTest, NoneOverSamplingProcessesCorrectly) { diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm index d6fb6e7a2..f23ec9d63 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm @@ -50,7 +50,7 @@ if (isConnected()) { if (auto lock = Locker::tryLock(adapterNodeMutex_)) { - for (size_t channel = 0; channel < adapterNode_->channelCount_; ++channel) { + for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) { auto data = (float *)inputBuffer->mBuffers[channel].mData; adapterNode_->buff_[channel]->write(data, numFrames); diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/AudioDecoder.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/AudioDecoder.mm index a1a68a3b5..eb1715158 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/AudioDecoder.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/AudioDecoder.mm @@ -6,6 +6,7 @@ #include #include #include +#include #if !RN_AUDIO_API_FFMPEG_DISABLED #include #endif // RN_AUDIO_API_FFMPEG_DISABLED diff --git a/packages/react-native-audio-api/src/core/AnalyserNode.ts b/packages/react-native-audio-api/src/core/AnalyserNode.ts index f2ac9ecfd..93b0014dc 100644 --- a/packages/react-native-audio-api/src/core/AnalyserNode.ts +++ b/packages/react-native-audio-api/src/core/AnalyserNode.ts @@ -83,7 +83,7 @@ export default class AnalyserNode extends AudioNode { } public get frequencyBinCount(): number { - return (this.node as IAnalyserNode).frequencyBinCount; + return Math.floor((this.node as IAnalyserNode).fftSize / 2); } public getFloatFrequencyData(array: Float32Array): void { diff --git a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts index 594ef77c4..ee69da691 100644 --- a/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts +++ b/packages/react-native-audio-api/src/core/AudioBufferSourceNode.ts @@ -11,26 +11,42 @@ export default class AudioBufferSourceNode extends AudioBufferBaseSourceNode { private onLoopEndedSubscription?: AudioEventSubscription; private onLoopEndedCallback?: (event: EventEmptyType) => void; + private _buffer: AudioBuffer | null = null; + private bufferHasBeenSet: boolean = false; + constructor(context: BaseAudioContext, options?: TAudioBufferSourceOptions) { const node = context.context.createBufferSource(options || {}); super(context, node); + + if (options?.buffer) { + this._buffer = options.buffer; + this.bufferHasBeenSet = true; + } } public get buffer(): AudioBuffer | null { - const buffer = (this.node as IAudioBufferSourceNode).buffer; - if (!buffer) { - return null; - } - return new AudioBuffer(buffer); + return this._buffer; } public set buffer(buffer: AudioBuffer | null) { - if (!buffer) { - (this.node as IAudioBufferSourceNode).setBuffer(null); + if (buffer === null) { + if (this.buffer !== null) { + (this.node as IAudioBufferSourceNode).setBuffer(null); + this._buffer = null; + } + return; } + if (this.bufferHasBeenSet) { + throw new InvalidStateError( + 'The buffer can only be set once and cannot be changed afterwards.' + ); + } + (this.node as IAudioBufferSourceNode).setBuffer(buffer.buffer); + this._buffer = buffer; + this.bufferHasBeenSet = true; } public get loopSkip(): boolean { diff --git a/packages/react-native-audio-api/src/core/BaseAudioContext.ts b/packages/react-native-audio-api/src/core/BaseAudioContext.ts index 452dcbe85..55d354e4e 100644 --- a/packages/react-native-audio-api/src/core/BaseAudioContext.ts +++ b/packages/react-native-audio-api/src/core/BaseAudioContext.ts @@ -130,8 +130,8 @@ export default class BaseAudioContext { return new OscillatorNode(this); } - createStreamer(): StreamerNode { - return new StreamerNode(this); + createStreamer(streamPath: string): StreamerNode { + return new StreamerNode(this, { streamPath }); } createConstantSource(): ConstantSourceNode { diff --git a/packages/react-native-audio-api/src/core/ConvolverNode.ts b/packages/react-native-audio-api/src/core/ConvolverNode.ts index 50d4982e0..bac02de46 100644 --- a/packages/react-native-audio-api/src/core/ConvolverNode.ts +++ b/packages/react-native-audio-api/src/core/ConvolverNode.ts @@ -5,28 +5,32 @@ import AudioNode from './AudioNode'; import AudioBuffer from './AudioBuffer'; export default class ConvolverNode extends AudioNode { + private _buffer: AudioBuffer | null = null; + constructor(context: BaseAudioContext, options?: TConvolverOptions) { const convolverNode: IConvolverNode = context.context.createConvolver( options || {} ); super(context, convolverNode); + + if (options?.buffer) { + this.buffer = options.buffer; + } this.normalize = convolverNode.normalize; } public get buffer(): AudioBuffer | null { - const buffer = (this.node as IConvolverNode).buffer; - if (!buffer) { - return null; - } - return new AudioBuffer(buffer); + return this._buffer; } public set buffer(buffer: AudioBuffer | null) { if (!buffer) { (this.node as IConvolverNode).setBuffer(null); + this._buffer = null; return; } (this.node as IConvolverNode).setBuffer(buffer.buffer); + this._buffer = buffer; } public get normalize(): boolean { diff --git a/packages/react-native-audio-api/src/core/IIRFilterNode.ts b/packages/react-native-audio-api/src/core/IIRFilterNode.ts index 94ab1a334..f04a301d0 100644 --- a/packages/react-native-audio-api/src/core/IIRFilterNode.ts +++ b/packages/react-native-audio-api/src/core/IIRFilterNode.ts @@ -6,7 +6,7 @@ import BaseAudioContext from './BaseAudioContext'; export default class IIRFilterNode extends AudioNode { constructor(context: BaseAudioContext, options: TIIRFilterOptions) { - const iirFilterNode = context.context.createIIRFilter(options || {}); + const iirFilterNode = context.context.createIIRFilter(options); super(context, iirFilterNode); } diff --git a/packages/react-native-audio-api/src/core/StreamerNode.ts b/packages/react-native-audio-api/src/core/StreamerNode.ts index 8e82d0ee7..76193ebbf 100644 --- a/packages/react-native-audio-api/src/core/StreamerNode.ts +++ b/packages/react-native-audio-api/src/core/StreamerNode.ts @@ -1,36 +1,17 @@ -import { IStreamerNode } from '../interfaces'; import AudioScheduledSourceNode from './AudioScheduledSourceNode'; import { TStreamerOptions } from '../types'; -import { InvalidStateError, NotSupportedError } from '../errors'; +import { NotSupportedError } from '../errors'; import BaseAudioContext from './BaseAudioContext'; export default class StreamerNode extends AudioScheduledSourceNode { - private hasBeenSetup: boolean = false; - constructor(context: BaseAudioContext, options?: TStreamerOptions) { - const node = context.context.createStreamer(options || {}); + readonly streamPath: string; + + constructor(context: BaseAudioContext, options: TStreamerOptions) { + const node = context.context.createStreamer(options); if (!node) { throw new NotSupportedError('StreamerNode requires FFmpeg build'); } super(context, node); - if (options?.streamPath) { - if (this.initialize(options.streamPath)) { - this.hasBeenSetup = true; - } - } - } - - public initialize(streamPath: string): boolean { - if (this.hasBeenSetup) { - throw new InvalidStateError('Node is already setup'); - } - const res = (this.node as IStreamerNode).initialize(streamPath); - if (res) { - this.hasBeenSetup = true; - } - return res; - } - - public get streamPath(): string { - return (this.node as IStreamerNode).streamPath; + this.streamPath = options.streamPath; } } diff --git a/packages/react-native-audio-api/src/core/WaveShaperNode.ts b/packages/react-native-audio-api/src/core/WaveShaperNode.ts index ed0670f37..ffccaad57 100644 --- a/packages/react-native-audio-api/src/core/WaveShaperNode.ts +++ b/packages/react-native-audio-api/src/core/WaveShaperNode.ts @@ -6,21 +6,19 @@ import { TWaveShaperOptions } from '../types'; export default class WaveShaperNode extends AudioNode { private isCurveSet: boolean = false; + private _curve: Float32Array | null = null; constructor(context: BaseAudioContext, options?: TWaveShaperOptions) { const node = context.context.createWaveShaper(options || {}); super(context, node); if (options?.curve) { + this._curve = options.curve; this.isCurveSet = true; } } get curve(): Float32Array | null { - if (!this.isCurveSet) { - return null; - } - - return (this.node as IWaveShaperNode).curve; + return this._curve; } get oversample(): OverSampleType { diff --git a/packages/react-native-audio-api/src/interfaces.ts b/packages/react-native-audio-api/src/interfaces.ts index 6c8b49b1d..57ee64218 100644 --- a/packages/react-native-audio-api/src/interfaces.ts +++ b/packages/react-native-audio-api/src/interfaces.ts @@ -101,7 +101,7 @@ export interface IBaseAudioContext { ) => IPeriodicWave; createAnalyser: (analyserOptions: TAnalyserOptions) => IAnalyserNode; createConvolver: (convolverOptions?: TConvolverOptions) => IConvolverNode; - createStreamer: (streamerOptions?: TStreamerOptions) => IStreamerNode | null; // null when FFmpeg is not enabled + createStreamer: (streamerOptions: TStreamerOptions) => IStreamerNode | null; // null when FFmpeg is not enabled createWaveShaper: (waveShaperOptions?: TWaveShaperOptions) => IWaveShaperNode; } @@ -195,10 +195,7 @@ export interface IOscillatorNode extends IAudioScheduledSourceNode { setPeriodicWave(periodicWave: IPeriodicWave): void; } -export interface IStreamerNode extends IAudioNode { - readonly streamPath: string; - initialize(streamPath: string): boolean; -} +export interface IStreamerNode extends IAudioNode {} export interface IConstantSourceNode extends IAudioScheduledSourceNode { readonly offset: IAudioParam; diff --git a/packages/react-native-audio-api/src/mock/index.ts b/packages/react-native-audio-api/src/mock/index.ts index 62554fdd9..a6bc90d48 100644 --- a/packages/react-native-audio-api/src/mock/index.ts +++ b/packages/react-native-audio-api/src/mock/index.ts @@ -461,27 +461,11 @@ class AudioBufferQueueSourceNodeMock extends AudioScheduledSourceNodeMock { } class StreamerNodeMock extends AudioScheduledSourceNodeMock { - private hasBeenSetup: boolean = false; - private _streamPath: string = ''; + readonly streamPath: string = ''; - constructor(context: BaseAudioContextMock, options?: TStreamerOptions) { - super(context, {}); - if (options?.streamPath) { - this.initialize(options.streamPath); - } - } - - initialize(streamPath: string): boolean { - if (this.hasBeenSetup) { - throw new Error('Node is already setup'); - } - this._streamPath = streamPath; - this.hasBeenSetup = true; - return true; - } - - get streamPath(): string { - return this._streamPath; + constructor(context: BaseAudioContextMock, options: TStreamerOptions) { + super(context, options); + this.streamPath = options.streamPath; } pause(): void {} @@ -659,8 +643,8 @@ class BaseAudioContextMock { return new AudioBufferQueueSourceNodeMock(this, options); } - createStreamer(options?: TStreamerOptions): StreamerNodeMock { - return new StreamerNodeMock(this, options); + createStreamer(streamPath: string): StreamerNodeMock { + return new StreamerNodeMock(this, { streamPath }); } createWorkletNode( diff --git a/packages/react-native-audio-api/src/types.ts b/packages/react-native-audio-api/src/types.ts index 3383c32e7..a750f37a6 100644 --- a/packages/react-native-audio-api/src/types.ts +++ b/packages/react-native-audio-api/src/types.ts @@ -178,7 +178,7 @@ export interface TConstantSourceOptions { } export interface TStreamerOptions { - streamPath?: string; + streamPath: string; } export interface TPeriodicWaveConstraints { diff --git a/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx b/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx index 5bca7effd..fa46a49cf 100644 --- a/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx +++ b/packages/react-native-audio-api/src/web-core/AudioBufferSourceNode.tsx @@ -212,6 +212,7 @@ class AudioBufferSourceNodeStretcher implements IAudioAPIBufferSourceNodeWeb { private _loopEnd: number = -1; private _buffer: AudioBuffer | null = null; + private bufferHasBeenSet: boolean = false; constructor(context: BaseAudioContext) { const promise = async () => { @@ -421,7 +422,16 @@ class AudioBufferSourceNodeStretcher implements IAudioAPIBufferSourceNodeWeb { } set buffer(buffer: AudioBuffer | null) { + if (buffer !== null && this.bufferHasBeenSet) { + throw new InvalidStateError( + 'The buffer can only be set once and cannot be changed afterwards.' + ); + } + this._buffer = buffer; + if (buffer !== null) { + this.bufferHasBeenSet = true; + } const action = (node: IStretcherNode) => { node.dropBuffers();