diff --git a/apps/common-app/src/examples/DrumMachine/usePlayer.tsx b/apps/common-app/src/examples/DrumMachine/usePlayer.tsx index 597809f9..6f977556 100644 --- a/apps/common-app/src/examples/DrumMachine/usePlayer.tsx +++ b/apps/common-app/src/examples/DrumMachine/usePlayer.tsx @@ -133,10 +133,9 @@ export default function usePlayer(options: PlayerOptions) { playingInstruments.value = getPlayingInstruments(); } - // return () => { - // console.log('Closing audio context'); - // audioContext.close(); - // }; + return () => { + audioContext.close(); + }; // \/ Shared values are not necessary in deps array // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPlaying, setup]); diff --git a/packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.cpp b/packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.cpp index 46baf544..adaf5fcf 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioBufferSourceNode.cpp @@ -9,6 +9,7 @@ AudioBufferSourceNode::AudioBufferSourceNode(BaseAudioContext *context) : AudioScheduledSourceNode(context), loop_(false), bufferIndex_(0) { numberOfInputs_ = 0; buffer_ = std::shared_ptr(nullptr); + isInitialized_ = true; } bool AudioBufferSourceNode::getLoop() const { @@ -51,6 +52,7 @@ void AudioBufferSourceNode::processNode(AudioBus* processingBus, int framesToPro if (!loop_) { playbackState_ = PlaybackState::FINISHED; + disable(); } return; @@ -59,20 +61,31 @@ void AudioBufferSourceNode::processNode(AudioBus* processingBus, int framesToPro // The buffer is longer than the number of frames to process. // We have to keep track of where we are in the buffer. if (framesToProcess < buffer_->getLength()) { - int processingBufferPosition = 0; + int outputBusIndex = 0; int framesToCopy = 0; - while (processingBufferPosition < framesToProcess) - { - framesToCopy = std::min(framesToProcess, buffer_->getLength() - bufferIndex_); + while (framesToProcess - outputBusIndex > 0) { + framesToCopy = std::min(framesToProcess - outputBusIndex, buffer_->getLength() - bufferIndex_); - processingBus->copy(buffer_->bus_.get(), processingBufferPosition, bufferIndex_, framesToCopy); - processingBufferPosition += framesToCopy; - bufferIndex_ = (bufferIndex_ + framesToCopy) % buffer_->getLength(); + processingBus->copy(buffer_->bus_.get(), bufferIndex_, outputBusIndex, framesToCopy); - if (!loop_ && processingBufferPosition >= framesToCopy) { - playbackState_ = PlaybackState::FINISHED; - bufferIndex_ = 0; + bufferIndex_ += framesToCopy; + outputBusIndex += framesToCopy; + + if (bufferIndex_ < buffer_->getLength()) { + continue; + } + + + bufferIndex_ %= buffer_->getLength(); + + if (!loop_) { + playbackState_ = PlaybackState::FINISHED; + disable(); + + if (framesToProcess - outputBusIndex > 0) { + processingBus->zero(outputBusIndex, framesToProcess - outputBusIndex); + } } } @@ -84,7 +97,9 @@ void AudioBufferSourceNode::processNode(AudioBus* processingBus, int framesToPro // If we don't loop the buffer, copy it once and zero the remaining processing bus frames. processingBus->copy(buffer_->bus_.get()); processingBus->zero(buffer_->getLength(), framesToProcess - buffer_->getLength()); + playbackState_ = PlaybackState::FINISHED; + disable(); return; } 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 233bd33b..898d2258 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioDestinationNode.cpp @@ -12,6 +12,7 @@ AudioDestinationNode::AudioDestinationNode(BaseAudioContext *context) numberOfOutputs_ = 0; numberOfInputs_ = INT_MAX; channelCountMode_ = ChannelCountMode::EXPLICIT; + isInitialized_ = true; } std::size_t AudioDestinationNode::getCurrentSampleFrame() const { @@ -24,7 +25,6 @@ double AudioDestinationNode::getCurrentTime() const { void AudioDestinationNode::renderAudio(AudioBus *destinationBus, int32_t numFrames) { context_->getNodeManager()->preProcessGraph(); - destinationBus->zero(); if (!numFrames) { @@ -39,8 +39,6 @@ void AudioDestinationNode::renderAudio(AudioBus *destinationBus, int32_t numFram destinationBus->normalize(); - context_->getNodeManager()->postProcessGraph(); - 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 bc8283f8..be7d7f19 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioNode.cpp @@ -12,6 +12,7 @@ AudioNode::AudioNode(BaseAudioContext *context) : context_(context) { } AudioNode::~AudioNode() { + isInitialized_ = false; cleanup(); } @@ -39,9 +40,8 @@ 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) { +void AudioNode::connectNode(const std::shared_ptr &node) { outputNodes_.push_back(node); - node->onInputConnected(this); } @@ -49,7 +49,7 @@ 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) { +void AudioNode::disconnectNode(const std::shared_ptr &node) { node->onInputDisconnected(this); auto position = std::find(outputNodes_.begin(), outputNodes_.end(), node); @@ -104,6 +104,10 @@ std::string AudioNode::toString(ChannelInterpretation interpretation) { } AudioBus* AudioNode::processAudio(AudioBus* outputBus, int framesToProcess) { + if (!isInitialized_) { + return outputBus; + } + std::size_t currentSampleFrame = context_->getCurrentSampleFrame(); // check if the node has already been processed for this rendering quantum @@ -197,13 +201,12 @@ void AudioNode::onInputConnected(AudioNode *node) { void AudioNode::onInputDisconnected(AudioNode *node) { auto position = std::find(inputNodes_.begin(), inputNodes_.end(), node); - if (position == inputNodes_.end()) { - return; + if (position != inputNodes_.end()) { + inputNodes_.erase(position); } - inputNodes_.erase(position); - if (inputNodes_.size() > 0 || outputNodes_.size() == 0) { + if (inputNodes_.size() > 0) { return; } 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 f2bdfa7b..e769c8ad 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNode.h +++ b/packages/react-native-audio-api/common/cpp/core/AudioNode.h @@ -42,6 +42,7 @@ class AudioNode : public std::enable_shared_from_this { int numberOfOutputs_ = 1; int numberOfEnabledInputNodes_ = 0; + bool isInitialized_ = false; bool isEnabled_ = true; std::size_t lastRenderedFrame_ { SIZE_MAX }; @@ -61,8 +62,8 @@ class AudioNode : public std::enable_shared_from_this { 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); + void connectNode(const std::shared_ptr &node); + void disconnectNode(const std::shared_ptr &node); void onInputEnabled(); void onInputDisabled(); @@ -71,32 +72,3 @@ class AudioNode : public std::enable_shared_from_this { }; } // namespace audioapi - -/* -Audio node management and deletion - -1. AudioSourceNode finishes playing, then: - - it disables itself (so it won't by processed anymore) - - if there is no reference from JS side, we can mark self for deletion - - node that is marked for deletion should be disconnected from the graph first - - it should "notify" connected nodes that it has been disabled (and potentially prepares for deletion) - -2. Node gets "notified" that one of its sources is disabled: - - it lowers the count of enabled sources - - if the count of enabled sources is 0, disable itself - - if there is no reference from JS side, we can mark self for deletion - - node that is marked for deletion should be disconnected from the graph first - - it should "notify" connected nodes that it has been disabled (and potentially prepares for deletion) - -Translating into more technical terms: -We use shared pointers for keeping output nodes -We use shared pointers in audio node manager to keep track of all source nodes -when audio source node finished playing it: - - disables itself and tells all output nodes that it has been disabled - - each node up to destination, checks their input nodes and if was its only active input node, it disables itself. - - source node tells audio node manager to dereference it (only if it is the last reference to the source node). - - audio manager in pre-process or post-process will remove the reference. - - audio manager in pre-process or post-process will also have to check for source nodes with only one reference and delete them if already stopped. - - deletion of the node will dereference all connected nodes, resulting in destroy'ing them if they are not referenced from JS side. - -*/ diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp index aad8a632..ee7b5b45 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.cpp @@ -9,7 +9,7 @@ AudioNodeManager::AudioNodeManager() {} AudioNodeManager::~AudioNodeManager() { audioNodesToConnect_.clear(); - audioNodesToDelete_.clear(); + sourceNodes_.clear(); } void AudioNodeManager::addPendingConnection(const std::shared_ptr &from, const std::shared_ptr &to, ConnectionType type) { @@ -18,12 +18,6 @@ void AudioNodeManager::addPendingConnection(const std::shared_ptr &fr audioNodesToConnect_.push_back(std::make_tuple(from, to, type)); } -void AudioNodeManager::setNodeToDelete(const std::shared_ptr &node) { - Locker lock(getGraphLock()); - - audioNodesToDelete_.push_back(node); -} - void AudioNodeManager::addSourceNode(const std::shared_ptr &node) { Locker lock(getGraphLock()); @@ -35,17 +29,6 @@ void AudioNodeManager::preProcessGraph() { return; } - settlePendingDeletions(); - settlePendingConnections(); - removeFinishedSourceNodes(); -} - -void AudioNodeManager::postProcessGraph() { - if (!Locker::tryLock(getGraphLock())) { - return; - } - - settlePendingDeletions(); settlePendingConnections(); removeFinishedSourceNodes(); } @@ -70,16 +53,11 @@ void AudioNodeManager::settlePendingConnections() { audioNodesToConnect_.clear(); } -void AudioNodeManager::settlePendingDeletions() { - audioNodesToDelete_.clear(); -} - void AudioNodeManager::removeFinishedSourceNodes() { for (auto it = sourceNodes_.begin(); it != sourceNodes_.end();) { auto currentNode = it->get(); // Release the source node if use count is equal to 1 (this vector) if (!currentNode->isEnabled() && it->use_count() == 1) { - for (auto& outputNode : currentNode->outputNodes_) { currentNode->disconnectNode(outputNode); } diff --git a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h index 35bc7a69..21a4219e 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h +++ b/packages/react-native-audio-api/common/cpp/core/AudioNodeManager.h @@ -16,10 +16,8 @@ class AudioNodeManager { ~AudioNodeManager(); void preProcessGraph(); - void postProcessGraph(); void addPendingConnection(const std::shared_ptr &from, const std::shared_ptr &to, ConnectionType type); - void setNodeToDelete(const std::shared_ptr &node); void addSourceNode(const std::shared_ptr &node); std::mutex& getGraphLock(); @@ -28,10 +26,8 @@ class AudioNodeManager { std::mutex graphLock_; std::vector> sourceNodes_; - std::vector> audioNodesToDelete_; std::vector, std::shared_ptr, ConnectionType>> audioNodesToConnect_; - void settlePendingDeletions(); void settlePendingConnections(); void removeFinishedSourceNodes(); }; diff --git a/packages/react-native-audio-api/common/cpp/core/AudioScheduledSourceNode.cpp b/packages/react-native-audio-api/common/cpp/core/AudioScheduledSourceNode.cpp index c9b4275d..90926f24 100644 --- a/packages/react-native-audio-api/common/cpp/core/AudioScheduledSourceNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/AudioScheduledSourceNode.cpp @@ -7,6 +7,7 @@ namespace audioapi { AudioScheduledSourceNode::AudioScheduledSourceNode(BaseAudioContext *context) : AudioNode(context), playbackState_(PlaybackState::UNSCHEDULED) { numberOfInputs_ = 0; + isInitialized_ = true; } void AudioScheduledSourceNode::start(double time) { 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 1279587f..79b357ac 100644 --- a/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/BiquadFilterNode.cpp @@ -19,6 +19,7 @@ BiquadFilterNode::BiquadFilterNode(BaseAudioContext *context) gainParam_ = std::make_shared( context, 0.0, MIN_FILTER_GAIN, MAX_FILTER_GAIN); type_ = BiquadFilterType::LOWPASS; + isInitialized_ = true; } std::string BiquadFilterNode::getType() { diff --git a/packages/react-native-audio-api/common/cpp/core/GainNode.cpp b/packages/react-native-audio-api/common/cpp/core/GainNode.cpp index 88bc6f50..9e43ff14 100644 --- a/packages/react-native-audio-api/common/cpp/core/GainNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/GainNode.cpp @@ -7,6 +7,7 @@ namespace audioapi { GainNode::GainNode(BaseAudioContext *context) : AudioNode(context) { gainParam_ = std::make_shared(context, 1.0, -MAX_GAIN, MAX_GAIN); + isInitialized_ = true; } std::shared_ptr GainNode::getGainParam() const { diff --git a/packages/react-native-audio-api/common/cpp/core/OscillatorNode.cpp b/packages/react-native-audio-api/common/cpp/core/OscillatorNode.cpp index d23595dc..f8bbe403 100644 --- a/packages/react-native-audio-api/common/cpp/core/OscillatorNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/OscillatorNode.cpp @@ -13,6 +13,7 @@ OscillatorNode::OscillatorNode(BaseAudioContext *context) std::make_shared(context, 0.0, -MAX_DETUNE, MAX_DETUNE); type_ = OscillatorType::SINE; periodicWave_ = context_->getBasicWaveForm(type_); + isInitialized_ = true; } std::shared_ptr OscillatorNode::getFrequencyParam() const { diff --git a/packages/react-native-audio-api/common/cpp/core/StereoPannerNode.cpp b/packages/react-native-audio-api/common/cpp/core/StereoPannerNode.cpp index cad4bfb1..e3210516 100644 --- a/packages/react-native-audio-api/common/cpp/core/StereoPannerNode.cpp +++ b/packages/react-native-audio-api/common/cpp/core/StereoPannerNode.cpp @@ -12,6 +12,7 @@ StereoPannerNode::StereoPannerNode(BaseAudioContext *context) : AudioNode(context) { channelCountMode_ = ChannelCountMode::CLAMPED_MAX; panParam_ = std::make_shared(context, 0.0, -MAX_PAN, MAX_PAN); + isInitialized_ = true; } std::shared_ptr StereoPannerNode::getPanParam() const {