Skip to content

Commit

Permalink
feat: proper deconstruction
Browse files Browse the repository at this point in the history
  • Loading branch information
michalsek committed Nov 26, 2024
1 parent c997218 commit 6d61fbd
Show file tree
Hide file tree
Showing 12 changed files with 48 additions and 82 deletions.
7 changes: 3 additions & 4 deletions apps/common-app/src/examples/DrumMachine/usePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ AudioBufferSourceNode::AudioBufferSourceNode(BaseAudioContext *context)
: AudioScheduledSourceNode(context), loop_(false), bufferIndex_(0) {
numberOfInputs_ = 0;
buffer_ = std::shared_ptr<AudioBuffer>(nullptr);
isInitialized_ = true;
}

bool AudioBufferSourceNode::getLoop() const {
Expand Down Expand Up @@ -51,6 +52,7 @@ void AudioBufferSourceNode::processNode(AudioBus* processingBus, int framesToPro

if (!loop_) {
playbackState_ = PlaybackState::FINISHED;
disable();
}

return;
Expand All @@ -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);
}
}
}

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ AudioDestinationNode::AudioDestinationNode(BaseAudioContext *context)
numberOfOutputs_ = 0;
numberOfInputs_ = INT_MAX;
channelCountMode_ = ChannelCountMode::EXPLICIT;
isInitialized_ = true;
}

std::size_t AudioDestinationNode::getCurrentSampleFrame() const {
Expand All @@ -24,7 +25,6 @@ double AudioDestinationNode::getCurrentTime() const {

void AudioDestinationNode::renderAudio(AudioBus *destinationBus, int32_t numFrames) {
context_->getNodeManager()->preProcessGraph();

destinationBus->zero();

if (!numFrames) {
Expand All @@ -39,8 +39,6 @@ void AudioDestinationNode::renderAudio(AudioBus *destinationBus, int32_t numFram

destinationBus->normalize();

context_->getNodeManager()->postProcessGraph();

currentSampleFrame_ += numFrames;
}

Expand Down
17 changes: 10 additions & 7 deletions packages/react-native-audio-api/common/cpp/core/AudioNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ AudioNode::AudioNode(BaseAudioContext *context) : context_(context) {
}

AudioNode::~AudioNode() {
isInitialized_ = false;
cleanup();
}

Expand Down Expand Up @@ -39,17 +40,16 @@ void AudioNode::connect(const std::shared_ptr<AudioNode> &node) {
context_->getNodeManager()->addPendingConnection(shared_from_this(), node, AudioNodeManager::ConnectionType::CONNECT);
}

void AudioNode::connectNode(std::shared_ptr<AudioNode> &node) {
void AudioNode::connectNode(const std::shared_ptr<AudioNode> &node) {
outputNodes_.push_back(node);

node->onInputConnected(this);
}

void AudioNode::disconnect(const std::shared_ptr<AudioNode> &node) {
context_->getNodeManager()->addPendingConnection(shared_from_this(), node, AudioNodeManager::ConnectionType::DISCONNECT);
}

void AudioNode::disconnectNode(std::shared_ptr<AudioNode> &node) {
void AudioNode::disconnectNode(const std::shared_ptr<AudioNode> &node) {
node->onInputDisconnected(this);

auto position = std::find(outputNodes_.begin(), outputNodes_.end(), node);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
34 changes: 3 additions & 31 deletions packages/react-native-audio-api/common/cpp/core/AudioNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
int numberOfOutputs_ = 1;
int numberOfEnabledInputNodes_ = 0;

bool isInitialized_ = false;
bool isEnabled_ = true;

std::size_t lastRenderedFrame_ { SIZE_MAX };
Expand All @@ -61,8 +62,8 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
AudioBus* processAudio(AudioBus* outputBus, int framesToProcess);
virtual void processNode(AudioBus* processingBus, int framesToProcess) = 0;

void connectNode(std::shared_ptr<AudioNode> &node);
void disconnectNode(std::shared_ptr<AudioNode> &node);
void connectNode(const std::shared_ptr<AudioNode> &node);
void disconnectNode(const std::shared_ptr<AudioNode> &node);

void onInputEnabled();
void onInputDisabled();
Expand All @@ -71,32 +72,3 @@ class AudioNode : public std::enable_shared_from_this<AudioNode> {
};

} // 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.
*/
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ AudioNodeManager::AudioNodeManager() {}

AudioNodeManager::~AudioNodeManager() {
audioNodesToConnect_.clear();
audioNodesToDelete_.clear();
sourceNodes_.clear();
}

void AudioNodeManager::addPendingConnection(const std::shared_ptr<AudioNode> &from, const std::shared_ptr<AudioNode> &to, ConnectionType type) {
Expand All @@ -18,12 +18,6 @@ void AudioNodeManager::addPendingConnection(const std::shared_ptr<AudioNode> &fr
audioNodesToConnect_.push_back(std::make_tuple(from, to, type));
}

void AudioNodeManager::setNodeToDelete(const std::shared_ptr<AudioNode> &node) {
Locker lock(getGraphLock());

audioNodesToDelete_.push_back(node);
}

void AudioNodeManager::addSourceNode(const std::shared_ptr<AudioNode> &node) {
Locker lock(getGraphLock());

Expand All @@ -35,17 +29,6 @@ void AudioNodeManager::preProcessGraph() {
return;
}

settlePendingDeletions();
settlePendingConnections();
removeFinishedSourceNodes();
}

void AudioNodeManager::postProcessGraph() {
if (!Locker::tryLock(getGraphLock())) {
return;
}

settlePendingDeletions();
settlePendingConnections();
removeFinishedSourceNodes();
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ class AudioNodeManager {
~AudioNodeManager();

void preProcessGraph();
void postProcessGraph();
void addPendingConnection(const std::shared_ptr<AudioNode> &from, const std::shared_ptr<AudioNode> &to, ConnectionType type);

void setNodeToDelete(const std::shared_ptr<AudioNode> &node);
void addSourceNode(const std::shared_ptr<AudioNode> &node);

std::mutex& getGraphLock();
Expand All @@ -28,10 +26,8 @@ class AudioNodeManager {
std::mutex graphLock_;

std::vector<std::shared_ptr<AudioNode>> sourceNodes_;
std::vector<std::shared_ptr<AudioNode>> audioNodesToDelete_;
std::vector<std::tuple<std::shared_ptr<AudioNode>, std::shared_ptr<AudioNode>, ConnectionType>> audioNodesToConnect_;

void settlePendingDeletions();
void settlePendingConnections();
void removeFinishedSourceNodes();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace audioapi {
AudioScheduledSourceNode::AudioScheduledSourceNode(BaseAudioContext *context)
: AudioNode(context), playbackState_(PlaybackState::UNSCHEDULED) {
numberOfInputs_ = 0;
isInitialized_ = true;
}

void AudioScheduledSourceNode::start(double time) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ BiquadFilterNode::BiquadFilterNode(BaseAudioContext *context)
gainParam_ = std::make_shared<AudioParam>(
context, 0.0, MIN_FILTER_GAIN, MAX_FILTER_GAIN);
type_ = BiquadFilterType::LOWPASS;
isInitialized_ = true;
}

std::string BiquadFilterNode::getType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace audioapi {

GainNode::GainNode(BaseAudioContext *context) : AudioNode(context) {
gainParam_ = std::make_shared<AudioParam>(context, 1.0, -MAX_GAIN, MAX_GAIN);
isInitialized_ = true;
}

std::shared_ptr<AudioParam> GainNode::getGainParam() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ OscillatorNode::OscillatorNode(BaseAudioContext *context)
std::make_shared<AudioParam>(context, 0.0, -MAX_DETUNE, MAX_DETUNE);
type_ = OscillatorType::SINE;
periodicWave_ = context_->getBasicWaveForm(type_);
isInitialized_ = true;
}

std::shared_ptr<AudioParam> OscillatorNode::getFrequencyParam() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ StereoPannerNode::StereoPannerNode(BaseAudioContext *context)
: AudioNode(context) {
channelCountMode_ = ChannelCountMode::CLAMPED_MAX;
panParam_ = std::make_shared<AudioParam>(context, 0.0, -MAX_PAN, MAX_PAN);
isInitialized_ = true;
}

std::shared_ptr<AudioParam> StereoPannerNode::getPanParam() const {
Expand Down

0 comments on commit 6d61fbd

Please sign in to comment.