From 8afa7b98bb5a4c93a200a46ee1a48db235367a78 Mon Sep 17 00:00:00 2001 From: Michal Sek Date: Fri, 22 Nov 2024 11:05:54 +0100 Subject: [PATCH] feat: locker + audio node manager wip --- .../src/examples/DrumMachine/DrumMachine.tsx | 4 +- .../common/cpp/core/AudioDestinationNode.cpp | 3 +- .../common/cpp/core/AudioNode.cpp | 20 ++++-- .../common/cpp/core/AudioNode.h | 9 ++- .../common/cpp/core/AudioNodeManager.cpp | 63 +++++++++++++++++++ .../common/cpp/core/AudioNodeManager.h | 36 +++++++++++ .../common/cpp/core/BaseAudioContext.cpp | 8 ++- .../common/cpp/core/BaseAudioContext.h | 4 ++ .../common/cpp/core/BiquadFilterNode.cpp | 3 +- .../common/cpp/utils/Locker.hpp | 47 ++++++++++++++ 10 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp create mode 100644 packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h create mode 100644 packages/react-native-audio-api/common/cpp/utils/Locker.hpp diff --git a/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx b/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx index 67bbf041..e179a73f 100644 --- a/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx +++ b/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx @@ -22,7 +22,7 @@ const defaultPreset = 'Empty'; function setupPlayer(audioCtx: AudioContext) { const kick = new Kick(audioCtx); - // const clap = new Clap(audioCtx); + const clap = new Clap(audioCtx); const hiHat = new HiHat(audioCtx); const playNote = (name: InstrumentName, time: number) => { @@ -31,7 +31,7 @@ function setupPlayer(audioCtx: AudioContext) { kick.play(time); break; case 'clap': - // clap.play(time); + clap.play(time); break; case 'hi-hat': hiHat.play(time); diff --git a/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp b/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp index 391bb4f3..c97f1d29 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp @@ -40,7 +40,8 @@ void AudioDestinationNode::renderAudio(AudioBus *destinationBus, int32_t numFram destinationBus->copy(processedBus); } - destinationBus->normalize(); + // destinationBus->normalize(); + destinationBus->zero(); currentSampleFrame_ += numFrames; } diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp b/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp index 168dc147..97e950b4 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp @@ -1,5 +1,6 @@ #include "AudioBus.h" #include "AudioNode.h" +#include "AudioNodeManager.h" #include "BaseAudioContext.h" namespace audioapi { @@ -34,11 +35,19 @@ std::string AudioNode::getChannelInterpretation() const { } void AudioNode::connect(const std::shared_ptr &node) { + context_->getNodeManager()->addPendingConnection(shared_from_this(), node, AudioNodeManager::ConnectionType::CONNECT); +} + +void AudioNode::connectNode(std::shared_ptr &node) { outputNodes_.push_back(node); node->inputNodes_.push_back(shared_from_this()); } void AudioNode::disconnect(const std::shared_ptr &node) { + context_->getNodeManager()->addPendingConnection(shared_from_this(), node, AudioNodeManager::ConnectionType::DISCONNECT); +} + +void AudioNode::disconnectNode(std::shared_ptr &node) { outputNodes_.erase( std::remove(outputNodes_.begin(), outputNodes_.end(), node), outputNodes_.end()); @@ -51,6 +60,10 @@ void AudioNode::disconnect(const std::shared_ptr &node) { } } +bool AudioNode::isInitialized() const { + return isInitialized_; +} + std::string AudioNode::toString(ChannelCountMode mode) { switch (mode) { @@ -77,7 +90,6 @@ std::string AudioNode::toString(ChannelInterpretation interpretation) { } AudioBus* AudioNode::processAudio(AudioBus* outputBus, int framesToProcess) { - debugName_; if (!isInitialized_) { return outputBus; } @@ -115,12 +127,12 @@ AudioBus* AudioNode::processAudio(AudioBus* outputBus, int framesToProcess) { return processingBus; } - for (auto it = inputNodes_.begin(); it != inputNodes_.end(); it += 1) { + for (auto it = inputNodes_.begin(); it != inputNodes_.end(); ++it) { // Process first connected node, it can be directly connected to the processingBus, - // resulting in one less summing operation.q + // resulting in one less summing operation. if (it == inputNodes_.begin()) { it->get()->processAudio(processingBus, framesToProcess); - } else if (it->get()) { + } else { // Enforce the summing to be done using the internal bus. AudioBus* inputBus = it->get()->processAudio(0, framesToProcess); if (inputBus) { diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNode.h b/packages/react-native-audio-api/common/cpp/core/AudioNode.h index a1c645d1..829377e2 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNode.h +++ b/packages/react-native-audio-api/common/cpp/core/AudioNode.h @@ -3,6 +3,7 @@ #include #include #include +#include "AudioNode.h" #include "Constants.h" namespace audioapi { @@ -22,7 +23,11 @@ class AudioNode : public std::enable_shared_from_this { void connect(const std::shared_ptr &node); void disconnect(const std::shared_ptr &node); + bool isInitialized() const; + protected: + friend class AudioNodeManager; + enum class ChannelCountMode { MAX, CLAMPED_MAX, EXPLICIT }; enum class ChannelInterpretation { SPEAKERS, DISCRETE }; @@ -47,10 +52,12 @@ class AudioNode : public std::enable_shared_from_this { std::vector> inputNodes_ = {}; std::vector> outputNodes_ = {}; - void cleanup(); AudioBus* processAudio(AudioBus* outputBus, int framesToProcess); virtual void processNode(AudioBus* processingBus, int framesToProcess) = 0; + + void connectNode(std::shared_ptr &node); + void disconnectNode(std::shared_ptr &node); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp new file mode 100644 index 00000000..3977ef32 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp @@ -0,0 +1,63 @@ + +#include "Locker.hpp" +#include "AudioNode.h" +#include "AudioNodeManager.h" + +namespace audioapi { + +AudioNodeManager::AudioNodeManager() {} + +AudioNodeManager::~AudioNodeManager() { + audioNodesToConnect_.clear(); + audioNodesToDelete_.clear(); +} + +void AudioNodeManager::addPendingConnection(std::shared_ptr from, std::shared_ptr to, ConnectionType type) { + Locker lock(getGraphLock()); + + audioNodesToConnect_.push_back(std::make_tuple(from, to, type)); +} + +void AudioNodeManager::setNodeToDelete(std::shared_ptr node) { + Locker lock(getGraphLock()); + + audioNodesToDelete_.push_back(node); +} + +void AudioNodeManager::preProcessGraph() { + if (!Locker::tryLock(getGraphLock())) { + return; + } +} + +void AudioNodeManager::postProcessGraph() { + if (!Locker::tryLock(getGraphLock())) { + return; + } +} + +std::mutex& AudioNodeManager::getGraphLock() { + return graphLock_; +} + +void AudioNodeManager::settlePendingConnections() { + for (auto& connection : audioNodesToConnect_) { + std::shared_ptr from = std::get<0>(connection); + std::shared_ptr to = std::get<1>(connection); + ConnectionType type = std::get<2>(connection); + + if (type == ConnectionType::CONNECT) { + from->connectNode(to); + } else { + from->disconnectNode(to); + } + } + + audioNodesToConnect_.clear(); +} + +void AudioNodeManager::settlePendingDeletions() { + audioNodesToDelete_.clear(); +} + +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h new file mode 100644 index 00000000..030aac16 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include + +namespace audioapi { + +class AudioNode; + +class AudioNodeManager { + public: + enum class ConnectionType { CONNECT, DISCONNECT }; + AudioNodeManager(); + ~AudioNodeManager(); + + void preProcessGraph(); + void postProcessGraph(); + void addPendingConnection(std::shared_ptr from, std::shared_ptr to, ConnectionType type); + + void setNodeToDelete(std::shared_ptr node); + + std::mutex& getGraphLock(); + + private: + std::mutex graphLock_; + + std::vector, std::shared_ptr, ConnectionType>> audioNodesToConnect_; + std::vector> audioNodesToDelete_; + + void settlePendingConnections(); + void settlePendingDeletions(); +}; + +} // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.cpp b/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.cpp index d9ef5336..1f266bc8 100644 --- a/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.cpp +++ b/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.cpp @@ -13,6 +13,7 @@ #include "OscillatorNode.h" #include "StereoPannerNode.h" #include "BiquadFilterNode.h" +#include "AudioNodeManager.h" #include "AudioDestinationNode.h" #include "AudioBufferSourceNode.h" @@ -28,8 +29,9 @@ BaseAudioContext::BaseAudioContext() { sampleRate_ = audioPlayer_->getSampleRate(); bufferSizeInFrames_ = audioPlayer_->getBufferSizeInFrames(); - destination_ = std::make_shared(this); audioPlayer_->start(); + nodeManager_ = std::make_shared(); + destination_ = std::make_shared(this); } std::string BaseAudioContext::getState() { @@ -93,6 +95,10 @@ std::function BaseAudioContext::renderAudio() { }; } +AudioNodeManager* BaseAudioContext::getNodeManager() { + return nodeManager_.get(); +} + std::string BaseAudioContext::toString(State state) { switch (state) { case State::SUSPENDED: diff --git a/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.h b/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.h index e8eec2d1..39d15dab 100644 --- a/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.h +++ b/packages/react-native-audio-api/common/cpp/core/BaseAudioContext.h @@ -16,6 +16,7 @@ class StereoPannerNode; class BiquadFilterNode; class AudioDestinationNode; class AudioBufferSourceNode; +class AudioNodeManager; #ifdef ANDROID class AudioPlayer; @@ -41,6 +42,8 @@ class BaseAudioContext { static std::shared_ptr createBuffer(int numberOfChannels, int length, int sampleRate); std::function renderAudio(); + AudioNodeManager* getNodeManager(); + protected: enum class State { SUSPENDED, RUNNING, CLOSED }; static std::string toString(State state); @@ -55,6 +58,7 @@ class BaseAudioContext { State state_ = State::RUNNING; int sampleRate_; int bufferSizeInFrames_; + std::shared_ptr nodeManager_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp b/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp index 7b92afcb..1e00960f 100644 --- a/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp @@ -356,7 +356,6 @@ void BiquadFilterNode::applyFilter() { } void BiquadFilterNode::processNode(AudioBus* processingBus, int framesToProcess) { - printf("BiquadFilterNode::processNode\n"); resetCoefficients(); applyFilter(); @@ -371,7 +370,7 @@ void BiquadFilterNode::processNode(AudioBus* processingBus, int framesToProcess) float a1 = a1_; float a2 = a2_; - for (int i = 0; i < processingBus->getNumberOfChannels(); i += 1) { + for (int i = 0; i < framesToProcess; i += 1) { float input = (*processingBus->getChannel(0))[i]; float output = b0 * input + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; diff --git a/packages/react-native-audio-api/common/cpp/utils/Locker.hpp b/packages/react-native-audio-api/common/cpp/utils/Locker.hpp new file mode 100644 index 00000000..2f13adc0 --- /dev/null +++ b/packages/react-native-audio-api/common/cpp/utils/Locker.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +namespace audioapi { + +// Small easy interface to manage locking +class Locker { + public: + Locker(): lockPtr_(0) {} + Locker(std::mutex& lockPtr): lockPtr_(&lockPtr) { + lock(); + } + + ~Locker() { + unlock(); + } + + explicit operator bool() const { return !!lockPtr_; } + + void lock() { + if (lockPtr_) { + lockPtr_->lock(); + } + } + + void unlock() { + if (lockPtr_) { + lockPtr_->unlock(); + } + } + + static Locker tryLock(std::mutex& lock) { + Locker result = Locker(); + + if (lock.try_lock()) { + result.lockPtr_ = &lock; + } + + return result; + } + + private: + std::mutex* lockPtr_; +}; + +} // namespace audioapi