/* ============================================================================== This file is part of the Water library. Copyright (c) 2015 ROLI Ltd. Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. For a full copy of the GNU General Public License see the doc/GPL.txt file. ============================================================================== */ #include "AudioProcessorGraph.h" #include "../containers/SortedSet.h" namespace water { //============================================================================== namespace GraphRenderingOps { struct AudioGraphRenderingOpBase { AudioGraphRenderingOpBase() noexcept {} virtual ~AudioGraphRenderingOpBase() {} virtual void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) = 0; }; // use CRTP template struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase { void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) override { static_cast (this)->perform (sharedAudioBufferChans, sharedCVBufferChans, sharedMidiBuffers, numSamples); } }; //============================================================================== struct ClearChannelOp : public AudioGraphRenderingOp { ClearChannelOp (const int channel, const bool cv) noexcept : channelNum (channel), isCV (cv) {} void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray&, const int numSamples) { if (isCV) sharedCVBufferChans.clear (channelNum, 0, numSamples); else sharedAudioBufferChans.clear (channelNum, 0, numSamples); } const int channelNum; const bool isCV; CARLA_DECLARE_NON_COPYABLE (ClearChannelOp) }; //============================================================================== struct CopyChannelOp : public AudioGraphRenderingOp { CopyChannelOp (const int srcChan, const int dstChan, const bool cv) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan), isCV (cv) {} void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray&, const int numSamples) { if (isCV) sharedCVBufferChans.copyFrom (dstChannelNum, 0, sharedCVBufferChans, srcChannelNum, 0, numSamples); else sharedAudioBufferChans.copyFrom (dstChannelNum, 0, sharedAudioBufferChans, srcChannelNum, 0, numSamples); } const int srcChannelNum, dstChannelNum; const bool isCV; CARLA_DECLARE_NON_COPYABLE (CopyChannelOp) }; //============================================================================== struct AddChannelOp : public AudioGraphRenderingOp { AddChannelOp (const int srcChan, const int dstChan, const bool cv) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan), isCV (cv) {} void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray&, const int numSamples) { if (isCV) sharedCVBufferChans.addFrom (dstChannelNum, 0, sharedCVBufferChans, srcChannelNum, 0, numSamples); else sharedAudioBufferChans.addFrom (dstChannelNum, 0, sharedAudioBufferChans, srcChannelNum, 0, numSamples); } const int srcChannelNum, dstChannelNum; const bool isCV; CARLA_DECLARE_NON_COPYABLE (AddChannelOp) }; //============================================================================== struct ClearMidiBufferOp : public AudioGraphRenderingOp { ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} void perform (AudioSampleBuffer&, AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) { sharedMidiBuffers.getUnchecked (bufferNum)->clear(); } const int bufferNum; CARLA_DECLARE_NON_COPYABLE (ClearMidiBufferOp) }; //============================================================================== struct CopyMidiBufferOp : public AudioGraphRenderingOp { CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} void perform (AudioSampleBuffer&, AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) { *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); } const int srcBufferNum, dstBufferNum; CARLA_DECLARE_NON_COPYABLE (CopyMidiBufferOp) }; //============================================================================== struct AddMidiBufferOp : public AudioGraphRenderingOp { AddMidiBufferOp (const int srcBuffer, const int dstBuffer) : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} void perform (AudioSampleBuffer&, AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) { sharedMidiBuffers.getUnchecked (dstBufferNum) ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); } const int srcBufferNum, dstBufferNum; CARLA_DECLARE_NON_COPYABLE (AddMidiBufferOp) }; //============================================================================== struct DelayChannelOp : public AudioGraphRenderingOp { DelayChannelOp (const int chan, const int delaySize, const bool cv) : channel (chan), bufferSize (delaySize + 1), readIndex (0), writeIndex (delaySize), isCV (cv) { buffer.calloc ((size_t) bufferSize); } void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray&, const int numSamples) { float* data = isCV ? sharedCVBufferChans.getWritePointer (channel, 0) : sharedAudioBufferChans.getWritePointer (channel, 0); HeapBlock& block = buffer; for (int i = numSamples; --i >= 0;) { block [writeIndex] = *data; *data++ = block [readIndex]; if (++readIndex >= bufferSize) readIndex = 0; if (++writeIndex >= bufferSize) writeIndex = 0; } } private: HeapBlock buffer; const int channel, bufferSize; int readIndex, writeIndex; const bool isCV; CARLA_DECLARE_NON_COPYABLE (DelayChannelOp) }; //============================================================================== struct ProcessBufferOp : public AudioGraphRenderingOp { ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, const Array& audioChannelsUsed, const uint totalNumChans, const Array& cvInChannelsUsed, const Array& cvOutChannelsUsed, const int midiBuffer) : node (n), processor (n->getProcessor()), audioChannelsToUse (audioChannelsUsed), cvInChannelsToUse (cvInChannelsUsed), cvOutChannelsToUse (cvOutChannelsUsed), totalAudioChans (jmax (1U, totalNumChans)), totalCVIns (cvInChannelsUsed.size()), totalCVOuts (cvOutChannelsUsed.size()), midiBufferToUse (midiBuffer) { audioChannels.calloc (totalAudioChans); cvInChannels.calloc (totalCVIns); cvOutChannels.calloc (totalCVOuts); while (audioChannelsToUse.size() < static_cast(totalAudioChans)) audioChannelsToUse.add (0); } void perform (AudioSampleBuffer& sharedAudioBufferChans, AudioSampleBuffer& sharedCVBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) { HeapBlock& audioChannelsCopy = audioChannels; HeapBlock& cvInChannelsCopy = cvInChannels; HeapBlock& cvOutChannelsCopy = cvOutChannels; for (uint i = 0; i < totalAudioChans; ++i) audioChannelsCopy[i] = sharedAudioBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); for (uint i = 0; i < totalCVIns; ++i) cvInChannels[i] = sharedCVBufferChans.getWritePointer (cvInChannelsToUse.getUnchecked (i), 0); for (uint i = 0; i < totalCVOuts; ++i) cvOutChannels[i] = sharedCVBufferChans.getWritePointer (cvOutChannelsToUse.getUnchecked (i), 0); AudioSampleBuffer audioBuffer (audioChannelsCopy, totalAudioChans, numSamples); AudioSampleBuffer cvInBuffer (cvInChannelsCopy, totalCVIns, numSamples); AudioSampleBuffer cvOutBuffer (cvOutChannelsCopy, totalCVOuts, numSamples); if (processor->isSuspended()) { audioBuffer.clear(); cvOutBuffer.clear(); } else { const CarlaRecursiveMutexLocker cml (processor->getCallbackLock()); callProcess (audioBuffer, cvInBuffer, cvOutBuffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); } } void callProcess (AudioSampleBuffer& audioBuffer, AudioSampleBuffer& cvInBuffer, AudioSampleBuffer& cvOutBuffer, MidiBuffer& midiMessages) { processor->processBlockWithCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages); } const AudioProcessorGraph::Node::Ptr node; AudioProcessor* const processor; private: Array audioChannelsToUse; Array cvInChannelsToUse; Array cvOutChannelsToUse; HeapBlock audioChannels; HeapBlock cvInChannels; HeapBlock cvOutChannels; AudioSampleBuffer tempBuffer; const uint totalAudioChans; const uint totalCVIns; const uint totalCVOuts; const int midiBufferToUse; CARLA_DECLARE_NON_COPYABLE (ProcessBufferOp) }; //============================================================================== /** Used to calculate the correct sequence of rendering ops needed, based on the best re-use of shared buffers at each stage. */ struct RenderingOpSequenceCalculator { RenderingOpSequenceCalculator (AudioProcessorGraph& g, const Array& nodes, Array& renderingOps) : graph (g), orderedNodes (nodes), totalLatency (0) { audioNodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros audioChannels.add (0); cvNodeIds.add ((uint32) zeroNodeID); cvChannels.add (0); midiNodeIds.add ((uint32) zeroNodeID); for (int i = 0; i < orderedNodes.size(); ++i) { createRenderingOpsForNode (*orderedNodes.getUnchecked(i), renderingOps, i); markAnyUnusedBuffersAsFree (i); } graph.setLatencySamples (totalLatency); } int getNumAudioBuffersNeeded() const noexcept { return audioNodeIds.size(); } int getNumCVBuffersNeeded() const noexcept { return cvNodeIds.size(); } int getNumMidiBuffersNeeded() const noexcept { return midiNodeIds.size(); } private: //============================================================================== AudioProcessorGraph& graph; const Array& orderedNodes; Array audioChannels, cvChannels; Array audioNodeIds, cvNodeIds, midiNodeIds; enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe, anonymousNodeID = 0xfffffffd }; static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID; } Array nodeDelayIDs; Array nodeDelays; int totalLatency; int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } void setNodeDelay (const uint32 nodeID, const int latency) { const int index = nodeDelayIDs.indexOf (nodeID); if (index >= 0) { nodeDelays.set (index, latency); } else { nodeDelayIDs.add (nodeID); nodeDelays.add (latency); } } int getInputLatencyForNode (const uint32 nodeID) const { int maxLatency = 0; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == nodeID) maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); } return maxLatency; } //============================================================================== void createRenderingOpsForNode (AudioProcessorGraph::Node& node, Array& renderingOps, const int ourRenderingIndex) { AudioProcessor& processor = *node.getProcessor(); const uint numAudioIns = processor.getTotalNumInputChannels(AudioProcessor::ChannelTypeAudio); const uint numAudioOuts = processor.getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio); const uint numCVIns = processor.getTotalNumInputChannels(AudioProcessor::ChannelTypeCV); const uint numCVOuts = processor.getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV); const uint totalAudioChans = jmax (numAudioIns, numAudioOuts); Array audioChannelsToUse, cvInChannelsToUse, cvOutChannelsToUse; int midiBufferToUse = -1; int maxLatency = getInputLatencyForNode (node.nodeId); for (uint inputChan = 0; inputChan < numAudioIns; ++inputChan) { // get a list of all the inputs to this node Array sourceNodes; Array sourceOutputChans; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node.nodeId && c->destChannelIndex == inputChan && c->channelType == AudioProcessor::ChannelTypeAudio) { sourceNodes.add (c->sourceNodeId); sourceOutputChans.add (c->sourceChannelIndex); } } int bufIndex = -1; if (sourceNodes.size() == 0) { // unconnected input channel bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio); renderingOps.add (new ClearChannelOp (bufIndex, false)); } else if (sourceNodes.size() == 1) { // channel with a straightforward single input.. const uint32 srcNode = sourceNodes.getUnchecked(0); const uint srcChan = sourceOutputChans.getUnchecked(0); bufIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio, srcNode, srcChan); if (bufIndex < 0) { // if not found, this is probably a feedback loop bufIndex = getReadOnlyEmptyBuffer(); wassert (bufIndex >= 0); } if (inputChan < numAudioOuts && isBufferNeededLater (AudioProcessor::ChannelTypeAudio, ourRenderingIndex, inputChan, srcNode, srcChan)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeAudio); renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer, false)); bufIndex = newFreeBuffer; } const int nodeDelay = getNodeDelay (srcNode); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, false)); } else { // channel with a mix of several inputs.. // try to find a re-usable channel from our inputs.. int reusableInputIndex = -1; for (int i = 0; i < sourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio, sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i)); if (sourceBufIndex >= 0 && ! isBufferNeededLater (AudioProcessor::ChannelTypeAudio, ourRenderingIndex, inputChan, sourceNodes.getUnchecked(i), sourceOutputChans.getUnchecked(i))) { // we've found one of our input chans that can be re-used.. reusableInputIndex = i; bufIndex = sourceBufIndex; const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay, false)); break; } } if (reusableInputIndex < 0) { // can't re-use any of our input chans, so get a new one and copy everything into it.. bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio); wassert (bufIndex != 0); markBufferAsContaining (AudioProcessor::ChannelTypeAudio, bufIndex, static_cast (anonymousNodeID), 0); const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio, sourceNodes.getUnchecked (0), sourceOutputChans.getUnchecked (0)); if (srcIndex < 0) { // if not found, this is probably a feedback loop renderingOps.add (new ClearChannelOp (bufIndex, false)); } else { renderingOps.add (new CopyChannelOp (srcIndex, bufIndex, false)); } reusableInputIndex = 0; const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, false)); } for (int j = 0; j < sourceNodes.size(); ++j) { if (j != reusableInputIndex) { int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeAudio, sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) { const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); if (nodeDelay < maxLatency) { if (! isBufferNeededLater (AudioProcessor::ChannelTypeAudio, ourRenderingIndex, inputChan, sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j))) { renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay, false)); } else // buffer is reused elsewhere, can't be delayed { const int bufferToDelay = getFreeBuffer (AudioProcessor::ChannelTypeAudio); renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay, false)); renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay, false)); srcIndex = bufferToDelay; } } renderingOps.add (new AddChannelOp (srcIndex, bufIndex, false)); } } } } CARLA_SAFE_ASSERT_CONTINUE (bufIndex >= 0); audioChannelsToUse.add (bufIndex); if (inputChan < numAudioOuts) markBufferAsContaining (AudioProcessor::ChannelTypeAudio, bufIndex, node.nodeId, inputChan); } for (uint outputChan = numAudioIns; outputChan < numAudioOuts; ++outputChan) { const int bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeAudio); CARLA_SAFE_ASSERT_CONTINUE (bufIndex > 0); audioChannelsToUse.add (bufIndex); markBufferAsContaining (AudioProcessor::ChannelTypeAudio, bufIndex, node.nodeId, outputChan); } for (uint inputChan = 0; inputChan < numCVIns; ++inputChan) { // get a list of all the inputs to this node Array sourceNodes; Array sourceOutputChans; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node.nodeId && c->destChannelIndex == inputChan && c->channelType == AudioProcessor::ChannelTypeCV) { sourceNodes.add (c->sourceNodeId); sourceOutputChans.add (c->sourceChannelIndex); } } int bufIndex = -1; if (sourceNodes.size() == 0) { // unconnected input channel bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV); renderingOps.add (new ClearChannelOp (bufIndex, true)); } else if (sourceNodes.size() == 1) { // channel with a straightforward single input.. const uint32 srcNode = sourceNodes.getUnchecked(0); const uint srcChan = sourceOutputChans.getUnchecked(0); bufIndex = getBufferContaining (AudioProcessor::ChannelTypeCV, srcNode, srcChan); if (bufIndex < 0) { // if not found, this is probably a feedback loop bufIndex = getReadOnlyEmptyBuffer(); wassert (bufIndex >= 0); } const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeCV); renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer, true)); bufIndex = newFreeBuffer; const int nodeDelay = getNodeDelay (srcNode); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, true)); } else { // channel with a mix of several inputs.. { bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV); wassert (bufIndex != 0); const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeCV, sourceNodes.getUnchecked (0), sourceOutputChans.getUnchecked (0)); if (srcIndex < 0) { // if not found, this is probably a feedback loop renderingOps.add (new ClearChannelOp (bufIndex, true)); } else { renderingOps.add (new CopyChannelOp (srcIndex, bufIndex, true)); } const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); if (nodeDelay < maxLatency) renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay, true)); } for (int j = 1; j < sourceNodes.size(); ++j) { int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeCV, sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) { const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); if (nodeDelay < maxLatency) { const int bufferToDelay = getFreeBuffer (AudioProcessor::ChannelTypeCV); renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay, true)); renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay, true)); srcIndex = bufferToDelay; } renderingOps.add (new AddChannelOp (srcIndex, bufIndex, true)); } } } CARLA_SAFE_ASSERT_CONTINUE (bufIndex >= 0); cvInChannelsToUse.add (bufIndex); markBufferAsContaining (AudioProcessor::ChannelTypeCV, bufIndex, node.nodeId, inputChan); } for (uint outputChan = 0; outputChan < numCVOuts; ++outputChan) { const int bufIndex = getFreeBuffer (AudioProcessor::ChannelTypeCV); CARLA_SAFE_ASSERT_CONTINUE (bufIndex > 0); cvOutChannelsToUse.add (bufIndex); markBufferAsContaining (AudioProcessor::ChannelTypeCV, bufIndex, node.nodeId, outputChan); } // Now the same thing for midi.. Array midiSourceNodes; for (int i = graph.getNumConnections(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = graph.getConnection (i); if (c->destNodeId == node.nodeId && c->channelType == AudioProcessor::ChannelTypeMIDI) midiSourceNodes.add (c->sourceNodeId); } if (midiSourceNodes.size() == 0) { // No midi inputs.. midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); // need to pick a buffer even if the processor doesn't use midi if (processor.acceptsMidi() || processor.producesMidi()) renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); } else if (midiSourceNodes.size() == 1) { // One midi input.. midiBufferToUse = getBufferContaining (AudioProcessor::ChannelTypeMIDI, midiSourceNodes.getUnchecked(0), 0); if (midiBufferToUse >= 0) { if (isBufferNeededLater (AudioProcessor::ChannelTypeMIDI, ourRenderingIndex, 0, midiSourceNodes.getUnchecked(0), 0)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. const int newFreeBuffer = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); midiBufferToUse = newFreeBuffer; } } else { // probably a feedback loop, so just use an empty one.. midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); // need to pick a buffer even if the processor doesn't use midi } } else { // More than one midi input being mixed.. int reusableInputIndex = -1; for (int i = 0; i < midiSourceNodes.size(); ++i) { const int sourceBufIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI, midiSourceNodes.getUnchecked(i), 0); if (sourceBufIndex >= 0 && ! isBufferNeededLater (AudioProcessor::ChannelTypeMIDI, ourRenderingIndex, 0, midiSourceNodes.getUnchecked(i), 0)) { // we've found one of our input buffers that can be re-used.. reusableInputIndex = i; midiBufferToUse = sourceBufIndex; break; } } if (reusableInputIndex < 0) { // can't re-use any of our input buffers, so get a new one and copy everything into it.. midiBufferToUse = getFreeBuffer (AudioProcessor::ChannelTypeMIDI); wassert (midiBufferToUse >= 0); const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI, midiSourceNodes.getUnchecked(0), 0); if (srcIndex >= 0) renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); else renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); reusableInputIndex = 0; } for (int j = 0; j < midiSourceNodes.size(); ++j) { if (j != reusableInputIndex) { const int srcIndex = getBufferContaining (AudioProcessor::ChannelTypeMIDI, midiSourceNodes.getUnchecked(j), 0); if (srcIndex >= 0) renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); } } } if (processor.producesMidi()) markBufferAsContaining (AudioProcessor::ChannelTypeMIDI, midiBufferToUse, node.nodeId, 0); setNodeDelay (node.nodeId, maxLatency + processor.getLatencySamples()); if (numAudioOuts == 0) totalLatency = maxLatency; renderingOps.add (new ProcessBufferOp (&node, audioChannelsToUse, totalAudioChans, cvInChannelsToUse, cvOutChannelsToUse, midiBufferToUse)); } //============================================================================== int getFreeBuffer (const AudioProcessor::ChannelType channelType) { switch (channelType) { case AudioProcessor::ChannelTypeAudio: for (int i = 1; i < audioNodeIds.size(); ++i) if (audioNodeIds.getUnchecked(i) == freeNodeID) return i; audioNodeIds.add ((uint32) freeNodeID); audioChannels.add (0); return audioNodeIds.size() - 1; case AudioProcessor::ChannelTypeCV: for (int i = 1; i < cvNodeIds.size(); ++i) if (cvNodeIds.getUnchecked(i) == freeNodeID) return i; cvNodeIds.add ((uint32) freeNodeID); cvChannels.add (0); return cvNodeIds.size() - 1; case AudioProcessor::ChannelTypeMIDI: for (int i = 1; i < midiNodeIds.size(); ++i) if (midiNodeIds.getUnchecked(i) == freeNodeID) return i; midiNodeIds.add ((uint32) freeNodeID); return midiNodeIds.size() - 1; } return -1; } int getReadOnlyEmptyBuffer() const noexcept { return 0; } int getBufferContaining (const AudioProcessor::ChannelType channelType, const uint32 nodeId, const uint outputChannel) const noexcept { switch (channelType) { case AudioProcessor::ChannelTypeAudio: for (int i = audioNodeIds.size(); --i >= 0;) if (audioNodeIds.getUnchecked(i) == nodeId && audioChannels.getUnchecked(i) == outputChannel) return i; break; case AudioProcessor::ChannelTypeCV: for (int i = cvNodeIds.size(); --i >= 0;) if (cvNodeIds.getUnchecked(i) == nodeId && cvChannels.getUnchecked(i) == outputChannel) return i; break; case AudioProcessor::ChannelTypeMIDI: for (int i = midiNodeIds.size(); --i >= 0;) { if (midiNodeIds.getUnchecked(i) == nodeId) return i; } break; } return -1; } void markAnyUnusedBuffersAsFree (const int stepIndex) { for (int i = 0; i < audioNodeIds.size(); ++i) { if (isNodeBusy (audioNodeIds.getUnchecked(i)) && ! isBufferNeededLater (AudioProcessor::ChannelTypeAudio, stepIndex, -1, audioNodeIds.getUnchecked(i), audioChannels.getUnchecked(i))) { audioNodeIds.set (i, (uint32) freeNodeID); } } // NOTE: CV skipped on purpose for (int i = 0; i < midiNodeIds.size(); ++i) { if (isNodeBusy (midiNodeIds.getUnchecked(i)) && ! isBufferNeededLater (AudioProcessor::ChannelTypeMIDI, stepIndex, -1, midiNodeIds.getUnchecked(i), 0)) { midiNodeIds.set (i, (uint32) freeNodeID); } } } bool isBufferNeededLater (const AudioProcessor::ChannelType channelType, int stepIndexToSearchFrom, uint inputChannelOfIndexToIgnore, const uint32 nodeId, const uint outputChanIndex) const { while (stepIndexToSearchFrom < orderedNodes.size()) { const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); for (uint i = 0; i < node->getProcessor()->getTotalNumInputChannels(channelType); ++i) if (i != inputChannelOfIndexToIgnore && graph.getConnectionBetween (channelType, nodeId, outputChanIndex, node->nodeId, i) != nullptr) return true; inputChannelOfIndexToIgnore = (uint)-1; ++stepIndexToSearchFrom; } return false; } void markBufferAsContaining (const AudioProcessor::ChannelType channelType, int bufferNum, uint32 nodeId, int outputIndex) { switch (channelType) { case AudioProcessor::ChannelTypeAudio: CARLA_SAFE_ASSERT_BREAK (bufferNum >= 0 && bufferNum < audioNodeIds.size()); audioNodeIds.set (bufferNum, nodeId); audioChannels.set (bufferNum, outputIndex); break; case AudioProcessor::ChannelTypeCV: CARLA_SAFE_ASSERT_BREAK (bufferNum >= 0 && bufferNum < cvNodeIds.size()); cvNodeIds.set (bufferNum, nodeId); cvChannels.set (bufferNum, outputIndex); break; case AudioProcessor::ChannelTypeMIDI: CARLA_SAFE_ASSERT_BREAK (bufferNum > 0 && bufferNum < midiNodeIds.size()); midiNodeIds.set (bufferNum, nodeId); break; } } CARLA_DECLARE_NON_COPYABLE (RenderingOpSequenceCalculator) }; //============================================================================== // Holds a fast lookup table for checking which nodes are inputs to others. class ConnectionLookupTable { public: explicit ConnectionLookupTable (const OwnedArray& connections) { for (int i = 0; i < static_cast(connections.size()); ++i) { const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i); int index; Entry* entry = findEntry (c->destNodeId, index); if (entry == nullptr) { entry = new Entry (c->destNodeId); entries.insert (index, entry); } entry->srcNodes.add (c->sourceNodeId); } } bool isAnInputTo (const uint32 possibleInputId, const uint32 possibleDestinationId) const noexcept { return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size()); } private: //============================================================================== struct Entry { explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {} const uint32 destNodeId; SortedSet srcNodes; CARLA_DECLARE_NON_COPYABLE (Entry) }; OwnedArray entries; bool isAnInputToRecursive (const uint32 possibleInputId, const uint32 possibleDestinationId, int recursionCheck) const noexcept { int index; if (const Entry* const entry = findEntry (possibleDestinationId, index)) { const SortedSet& srcNodes = entry->srcNodes; if (srcNodes.contains (possibleInputId)) return true; if (--recursionCheck >= 0) { for (int i = 0; i < srcNodes.size(); ++i) if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck)) return true; } } return false; } Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept { Entry* result = nullptr; int start = 0; int end = entries.size(); for (;;) { if (start >= end) { break; } else if (destNodeId == entries.getUnchecked (start)->destNodeId) { result = entries.getUnchecked (start); break; } else { const int halfway = (start + end) / 2; if (halfway == start) { if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) ++start; break; } else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) start = halfway; else end = halfway; } } insertIndex = start; return result; } CARLA_DECLARE_NON_COPYABLE (ConnectionLookupTable) }; //============================================================================== struct ConnectionSorter { static int compareElements (const AudioProcessorGraph::Connection* const first, const AudioProcessorGraph::Connection* const second) noexcept { if (first->sourceNodeId < second->sourceNodeId) return -1; if (first->sourceNodeId > second->sourceNodeId) return 1; if (first->destNodeId < second->destNodeId) return -1; if (first->destNodeId > second->destNodeId) return 1; if (first->sourceChannelIndex < second->sourceChannelIndex) return -1; if (first->sourceChannelIndex > second->sourceChannelIndex) return 1; if (first->destChannelIndex < second->destChannelIndex) return -1; if (first->destChannelIndex > second->destChannelIndex) return 1; return 0; } }; } //============================================================================== AudioProcessorGraph::Connection::Connection (ChannelType ct, const uint32 sourceID, const uint sourceChannel, const uint32 destID, const uint destChannel) noexcept : channelType (ct), sourceNodeId (sourceID), sourceChannelIndex (sourceChannel), destNodeId (destID), destChannelIndex (destChannel) { } //============================================================================== AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) noexcept : nodeId (nodeID), processor (p), isPrepared (false) { wassert (processor != nullptr); } void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, AudioProcessorGraph* const graph) { if (! isPrepared) { setParentGraph (graph); processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); processor->prepareToPlay (newSampleRate, newBlockSize); isPrepared = true; } } void AudioProcessorGraph::Node::unprepare() { if (isPrepared) { isPrepared = false; processor->releaseResources(); } } void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph) const { if (AudioProcessorGraph::AudioGraphIOProcessor* const ioProc = dynamic_cast (processor.get())) ioProc->setParentGraph (graph); } //============================================================================== struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers { AudioProcessorGraphBufferHelpers() noexcept : currentAudioInputBuffer (nullptr), currentCVInputBuffer (nullptr) {} void setRenderingBufferSize (int newNumAudioChannels, int newNumCVChannels, int newNumSamples) noexcept { renderingAudioBuffers.setSize (newNumAudioChannels, newNumSamples); renderingAudioBuffers.clear(); renderingCVBuffers.setSize (newNumCVChannels, newNumSamples); renderingCVBuffers.clear(); } void release() noexcept { renderingAudioBuffers.setSize (1, 1); currentAudioInputBuffer = nullptr; currentCVInputBuffer = nullptr; currentAudioOutputBuffer.setSize (1, 1); currentCVOutputBuffer.setSize (1, 1); renderingCVBuffers.setSize (1, 1); } void prepareInOutBuffers (int newNumAudioChannels, int newNumCVChannels, int newNumSamples) noexcept { currentAudioInputBuffer = nullptr; currentCVInputBuffer = nullptr; currentAudioOutputBuffer.setSize (newNumAudioChannels, newNumSamples); currentCVOutputBuffer.setSize (newNumCVChannels, newNumSamples); } AudioSampleBuffer renderingAudioBuffers; AudioSampleBuffer renderingCVBuffers; AudioSampleBuffer* currentAudioInputBuffer; const AudioSampleBuffer* currentCVInputBuffer; AudioSampleBuffer currentAudioOutputBuffer; AudioSampleBuffer currentCVOutputBuffer; }; //============================================================================== AudioProcessorGraph::AudioProcessorGraph() : lastNodeId (0), audioAndCVBuffers (new AudioProcessorGraphBufferHelpers), currentMidiInputBuffer (nullptr), isPrepared (false), needsReorder (false) { } AudioProcessorGraph::~AudioProcessorGraph() { clearRenderingSequence(); clear(); } const String AudioProcessorGraph::getName() const { return "Audio Graph"; } //============================================================================== void AudioProcessorGraph::clear() { nodes.clear(); connections.clear(); needsReorder = true; } AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const { for (int i = nodes.size(); --i >= 0;) if (nodes.getUnchecked(i)->nodeId == nodeId) return nodes.getUnchecked(i); return nullptr; } AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) { CARLA_SAFE_ASSERT_RETURN (newProcessor != nullptr && newProcessor != this, nullptr); for (int i = nodes.size(); --i >= 0;) { CARLA_SAFE_ASSERT_RETURN (nodes.getUnchecked(i)->getProcessor() != newProcessor, nullptr); } if (nodeId == 0) { nodeId = ++lastNodeId; } else { // you can't add a node with an id that already exists in the graph.. CARLA_SAFE_ASSERT_RETURN (getNodeForId (nodeId) == nullptr, nullptr); removeNode (nodeId); if (nodeId > lastNodeId) lastNodeId = nodeId; } Node* const n = new Node (nodeId, newProcessor); nodes.add (n); if (isPrepared) needsReorder = true; n->setParentGraph (this); return n; } bool AudioProcessorGraph::removeNode (const uint32 nodeId) { disconnectNode (nodeId); for (int i = nodes.size(); --i >= 0;) { if (nodes.getUnchecked(i)->nodeId == nodeId) { nodes.remove (i); if (isPrepared) needsReorder = true; return true; } } return false; } bool AudioProcessorGraph::removeNode (Node* node) { CARLA_SAFE_ASSERT_RETURN(node != nullptr, false); return removeNode (node->nodeId); } //============================================================================== const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const ChannelType ct, const uint32 sourceNodeId, const uint sourceChannelIndex, const uint32 destNodeId, const uint destChannelIndex) const { const Connection c (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex); GraphRenderingOps::ConnectionSorter sorter; return connections [connections.indexOfSorted (sorter, &c)]; } bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, const uint32 possibleDestNodeId) const { for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == possibleSourceNodeId && c->destNodeId == possibleDestNodeId) { return true; } } return false; } bool AudioProcessorGraph::canConnect (ChannelType ct, const uint32 sourceNodeId, const uint sourceChannelIndex, const uint32 destNodeId, const uint destChannelIndex) const { if (sourceNodeId == destNodeId) return false; const Node* const source = getNodeForId (sourceNodeId); if (source == nullptr || (ct != ChannelTypeMIDI && sourceChannelIndex >= source->processor->getTotalNumOutputChannels(ct)) || (ct == ChannelTypeMIDI && ! source->processor->producesMidi())) return false; const Node* const dest = getNodeForId (destNodeId); if (dest == nullptr || (ct != ChannelTypeMIDI && destChannelIndex >= dest->processor->getTotalNumInputChannels(ct)) || (ct == ChannelTypeMIDI && ! dest->processor->acceptsMidi())) return false; return getConnectionBetween (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex) == nullptr; } bool AudioProcessorGraph::addConnection (const ChannelType ct, const uint32 sourceNodeId, const uint sourceChannelIndex, const uint32 destNodeId, const uint destChannelIndex) { if (! canConnect (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) return false; GraphRenderingOps::ConnectionSorter sorter; connections.addSorted (sorter, new Connection (ct, sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)); if (isPrepared) needsReorder = true; return true; } void AudioProcessorGraph::removeConnection (const int index) { connections.remove (index); if (isPrepared) needsReorder = true; } bool AudioProcessorGraph::removeConnection (const ChannelType ct, const uint32 sourceNodeId, const uint sourceChannelIndex, const uint32 destNodeId, const uint destChannelIndex) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->channelType == ct && c->sourceNodeId == sourceNodeId && c->destNodeId == destNodeId && c->sourceChannelIndex == sourceChannelIndex && c->destChannelIndex == destChannelIndex) { removeConnection (i); doneAnything = true; } } return doneAnything; } bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { const Connection* const c = connections.getUnchecked(i); if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) { removeConnection (i); doneAnything = true; } } return doneAnything; } bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const { CARLA_SAFE_ASSERT_RETURN (c != nullptr, false); const Node* const source = getNodeForId (c->sourceNodeId); const Node* const dest = getNodeForId (c->destNodeId); return source != nullptr && dest != nullptr && (c->channelType != ChannelTypeMIDI ? (c->sourceChannelIndex < source->processor->getTotalNumOutputChannels(c->channelType)) : source->processor->producesMidi()) && (c->channelType != ChannelTypeMIDI ? (c->destChannelIndex < dest->processor->getTotalNumInputChannels(c->channelType)) : dest->processor->acceptsMidi()); } bool AudioProcessorGraph::removeIllegalConnections() { bool doneAnything = false; for (int i = connections.size(); --i >= 0;) { if (! isConnectionLegal (connections.getUnchecked(i))) { removeConnection (i); doneAnything = true; } } return doneAnything; } //============================================================================== static void deleteRenderOpArray (Array& ops) { for (int i = ops.size(); --i >= 0;) delete static_cast (ops.getUnchecked(i)); } void AudioProcessorGraph::clearRenderingSequence() { Array oldOps; { const CarlaRecursiveMutexLocker cml (getCallbackLock()); renderingOps.swapWith (oldOps); } deleteRenderOpArray (oldOps); } bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, const uint32 possibleDestinationId, const int recursionCheck) const { if (recursionCheck > 0) { for (int i = connections.size(); --i >= 0;) { const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); if (c->destNodeId == possibleDestinationId && (c->sourceNodeId == possibleInputId || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) return true; } } return false; } void AudioProcessorGraph::buildRenderingSequence() { Array newRenderingOps; int numAudioRenderingBuffersNeeded = 2; int numCVRenderingBuffersNeeded = 0; int numMidiBuffersNeeded = 1; { const CarlaRecursiveMutexLocker cml (reorderMutex); Array orderedNodes; { const GraphRenderingOps::ConnectionLookupTable table (connections); for (int i = 0; i < nodes.size(); ++i) { Node* const node = nodes.getUnchecked(i); node->prepare (getSampleRate(), getBlockSize(), this); int j = 0; for (; j < orderedNodes.size(); ++j) if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId)) break; orderedNodes.insert (j, node); } } GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); numAudioRenderingBuffersNeeded = calculator.getNumAudioBuffersNeeded(); numCVRenderingBuffersNeeded = calculator.getNumCVBuffersNeeded(); numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); } { // swap over to the new rendering sequence.. const CarlaRecursiveMutexLocker cml (getCallbackLock()); audioAndCVBuffers->setRenderingBufferSize (numAudioRenderingBuffersNeeded, numCVRenderingBuffersNeeded, getBlockSize()); for (int i = static_cast(midiBuffers.size()); --i >= 0;) midiBuffers.getUnchecked(i)->clear(); while (static_cast(midiBuffers.size()) < numMidiBuffersNeeded) midiBuffers.add (new MidiBuffer()); renderingOps.swapWith (newRenderingOps); } // delete the old ones.. deleteRenderOpArray (newRenderingOps); } //============================================================================== void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) { setRateAndBufferSizeDetails(sampleRate, estimatedSamplesPerBlock); audioAndCVBuffers->prepareInOutBuffers(jmax(1U, getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio)), jmax(1U, getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV)), estimatedSamplesPerBlock); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); clearRenderingSequence(); buildRenderingSequence(); isPrepared = true; } void AudioProcessorGraph::releaseResources() { isPrepared = false; for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); audioAndCVBuffers->release(); midiBuffers.clear(); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); } void AudioProcessorGraph::reset() { const CarlaRecursiveMutexLocker cml (getCallbackLock()); for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->getProcessor()->reset(); } void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept { const CarlaRecursiveMutexLocker cml (getCallbackLock()); AudioProcessor::setNonRealtime (isProcessingNonRealtime); for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->getProcessor()->setNonRealtime (isProcessingNonRealtime); } /* void AudioProcessorGraph::processAudio (AudioSampleBuffer& audioBuffer, MidiBuffer& midiMessages) { AudioSampleBuffer*& currentAudioInputBuffer = audioAndCVBuffers->currentAudioInputBuffer; AudioSampleBuffer& currentAudioOutputBuffer = audioAndCVBuffers->currentAudioOutputBuffer; AudioSampleBuffer& renderingAudioBuffers = audioAndCVBuffers->renderingAudioBuffers; AudioSampleBuffer& renderingCVBuffers = audioAndCVBuffers->renderingCVBuffers; const int numSamples = audioBuffer.getNumSamples(); if (! audioAndCVBuffers->currentAudioOutputBuffer.setSizeRT(numSamples)) return; if (! audioAndCVBuffers->renderingAudioBuffers.setSizeRT(numSamples)) return; if (! audioAndCVBuffers->renderingCVBuffers.setSizeRT(numSamples)) return; currentAudioInputBuffer = &audioBuffer; currentAudioOutputBuffer.clear(); currentMidiInputBuffer = &midiMessages; currentMidiOutputBuffer.clear(); for (int i = 0; i < renderingOps.size(); ++i) { GraphRenderingOps::AudioGraphRenderingOpBase* const op = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); op->perform (renderingAudioBuffers, renderingCVBuffers, midiBuffers, numSamples); } for (uint32_t i = 0; i < audioBuffer.getNumChannels(); ++i) audioBuffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); midiMessages.clear(); midiMessages.addEvents (currentMidiOutputBuffer, 0, audioBuffer.getNumSamples(), 0); } */ void AudioProcessorGraph::processAudioAndCV (AudioSampleBuffer& audioBuffer, const AudioSampleBuffer& cvInBuffer, AudioSampleBuffer& cvOutBuffer, MidiBuffer& midiMessages) { AudioSampleBuffer*& currentAudioInputBuffer = audioAndCVBuffers->currentAudioInputBuffer; const AudioSampleBuffer*& currentCVInputBuffer = audioAndCVBuffers->currentCVInputBuffer; AudioSampleBuffer& currentAudioOutputBuffer = audioAndCVBuffers->currentAudioOutputBuffer; AudioSampleBuffer& currentCVOutputBuffer = audioAndCVBuffers->currentCVOutputBuffer; AudioSampleBuffer& renderingAudioBuffers = audioAndCVBuffers->renderingAudioBuffers; AudioSampleBuffer& renderingCVBuffers = audioAndCVBuffers->renderingCVBuffers; const int numSamples = audioBuffer.getNumSamples(); if (! audioAndCVBuffers->currentAudioOutputBuffer.setSizeRT(numSamples)) return; if (! audioAndCVBuffers->currentCVOutputBuffer.setSizeRT(numSamples)) return; if (! audioAndCVBuffers->renderingAudioBuffers.setSizeRT(numSamples)) return; if (! audioAndCVBuffers->renderingCVBuffers.setSizeRT(numSamples)) return; currentAudioInputBuffer = &audioBuffer; currentCVInputBuffer = &cvInBuffer; currentMidiInputBuffer = &midiMessages; currentAudioOutputBuffer.clear(); currentCVOutputBuffer.clear(); currentMidiOutputBuffer.clear(); for (int i = 0; i < renderingOps.size(); ++i) { GraphRenderingOps::AudioGraphRenderingOpBase* const op = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); op->perform (renderingAudioBuffers, renderingCVBuffers, midiBuffers, numSamples); } for (uint32_t i = 0; i < audioBuffer.getNumChannels(); ++i) audioBuffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); for (uint32_t i = 0; i < cvOutBuffer.getNumChannels(); ++i) cvOutBuffer.copyFrom (i, 0, currentCVOutputBuffer, i, 0, numSamples); midiMessages.clear(); midiMessages.addEvents (currentMidiOutputBuffer, 0, audioBuffer.getNumSamples(), 0); } bool AudioProcessorGraph::acceptsMidi() const { return true; } bool AudioProcessorGraph::producesMidi() const { return true; } /* void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { processAudio (buffer, midiMessages); } */ void AudioProcessorGraph::processBlockWithCV (AudioSampleBuffer& audioBuffer, const AudioSampleBuffer& cvInBuffer, AudioSampleBuffer& cvOutBuffer, MidiBuffer& midiMessages) { processAudioAndCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages); } void AudioProcessorGraph::reorderNowIfNeeded() { if (needsReorder) { needsReorder = false; buildRenderingSequence(); } } const CarlaRecursiveMutex& AudioProcessorGraph::getReorderMutex() const { return reorderMutex; } //============================================================================== AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) : type (deviceType), graph (nullptr) { } AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() { } const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const { switch (type) { case audioOutputNode: return "Audio Output"; case audioInputNode: return "Audio Input"; case cvOutputNode: return "CV Output"; case cvInputNode: return "CV Input"; case midiOutputNode: return "Midi Output"; case midiInputNode: return "Midi Input"; default: break; } return String(); } void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) { CARLA_SAFE_ASSERT (graph != nullptr); } void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } void AudioProcessorGraph::AudioGraphIOProcessor::processAudioAndCV (AudioSampleBuffer& audioBuffer, const AudioSampleBuffer& cvInBuffer, AudioSampleBuffer& cvOutBuffer, MidiBuffer& midiMessages) { CARLA_SAFE_ASSERT_RETURN(graph != nullptr,); switch (type) { case audioOutputNode: { AudioSampleBuffer& currentAudioOutputBuffer = graph->audioAndCVBuffers->currentAudioOutputBuffer; for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), audioBuffer.getNumChannels()); --i >= 0;) { currentAudioOutputBuffer.addFrom (i, 0, audioBuffer, i, 0, audioBuffer.getNumSamples()); } break; } case audioInputNode: { AudioSampleBuffer*& currentAudioInputBuffer = graph->audioAndCVBuffers->currentAudioInputBuffer; for (int i = jmin (currentAudioInputBuffer->getNumChannels(), audioBuffer.getNumChannels()); --i >= 0;) { audioBuffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, audioBuffer.getNumSamples()); } break; } case cvOutputNode: { AudioSampleBuffer& currentCVOutputBuffer = graph->audioAndCVBuffers->currentCVOutputBuffer; for (int i = jmin (currentCVOutputBuffer.getNumChannels(), cvInBuffer.getNumChannels()); --i >= 0;) { currentCVOutputBuffer.addFrom (i, 0, cvInBuffer, i, 0, cvInBuffer.getNumSamples()); } break; } case cvInputNode: { const AudioSampleBuffer*& currentCVInputBuffer = graph->audioAndCVBuffers->currentCVInputBuffer; for (int i = jmin (currentCVInputBuffer->getNumChannels(), cvOutBuffer.getNumChannels()); --i >= 0;) { cvOutBuffer.copyFrom (i, 0, *currentCVInputBuffer, i, 0, cvOutBuffer.getNumSamples()); } break; } case midiOutputNode: graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, audioBuffer.getNumSamples(), 0); break; case midiInputNode: midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, audioBuffer.getNumSamples(), 0); break; default: break; } } void AudioProcessorGraph::AudioGraphIOProcessor::processBlockWithCV (AudioSampleBuffer& audioBuffer, const AudioSampleBuffer& cvInBuffer, AudioSampleBuffer& cvOutBuffer, MidiBuffer& midiMessages) { processAudioAndCV (audioBuffer, cvInBuffer, cvOutBuffer, midiMessages); } bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const { return type == midiOutputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const { return type == midiInputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == cvInputNode || type == midiInputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == cvOutputNode || type == midiOutputNode; } void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph) { graph = newGraph; if (graph != nullptr) { setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeAudio) : 0, type == audioInputNode ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeAudio) : 0, type == cvOutputNode ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeCV) : 0, type == cvInputNode ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeCV) : 0, type == midiOutputNode ? graph->getTotalNumOutputChannels(AudioProcessor::ChannelTypeMIDI) : 0, type == midiInputNode ? graph->getTotalNumInputChannels(AudioProcessor::ChannelTypeMIDI) : 0, getSampleRate(), getBlockSize()); } } }