| @@ -316,7 +316,14 @@ public: | |||
| { | |||
| auto sequence = std::make_unique<RenderSequence>(); | |||
| const RenderSequenceBuilder builder (g, *sequence); | |||
| return sequence; | |||
| struct SequenceAndLatency | |||
| { | |||
| std::unique_ptr<RenderSequence> sequence; | |||
| int latencySamples = 0; | |||
| }; | |||
| return SequenceAndLatency { std::move (sequence), builder.totalLatency }; | |||
| } | |||
| private: | |||
| @@ -324,8 +331,6 @@ private: | |||
| using Node = AudioProcessorGraph::Node; | |||
| using NodeID = AudioProcessorGraph::NodeID; | |||
| AudioProcessorGraph& graph; | |||
| const Array<Node*> orderedNodes; | |||
| struct AssignedBuffer | |||
| @@ -366,7 +371,7 @@ private: | |||
| return delays[nodeID.uid]; | |||
| } | |||
| int getInputLatencyForNode (NodeID nodeID) const | |||
| int getInputLatencyForNode (const AudioProcessorGraph& graph, NodeID nodeID) const | |||
| { | |||
| int maxLatency = 0; | |||
| @@ -430,13 +435,17 @@ private: | |||
| } | |||
| template <typename RenderSequence> | |||
| int findBufferForInputAudioChannel (RenderSequence& sequence, Node& node, const int inputChan, | |||
| const int ourRenderingIndex, const int maxLatency) | |||
| int findBufferForInputAudioChannel (const AudioProcessorGraph& graph, | |||
| RenderSequence& sequence, | |||
| Node& node, | |||
| const int inputChan, | |||
| const int ourRenderingIndex, | |||
| const int maxLatency) | |||
| { | |||
| auto& processor = *node.getProcessor(); | |||
| auto numOuts = processor.getTotalNumOutputChannels(); | |||
| auto sources = getSourcesForChannel (node, inputChan); | |||
| auto sources = getSourcesForChannel (graph, node, inputChan); | |||
| // Handle an unconnected input channel... | |||
| if (sources.isEmpty()) | |||
| @@ -465,7 +474,7 @@ private: | |||
| } | |||
| if (inputChan < numOuts | |||
| && isBufferNeededLater (ourRenderingIndex, inputChan, src)) | |||
| && isBufferNeededLater (graph, ourRenderingIndex, inputChan, src)) | |||
| { | |||
| // can't mess up this channel because it's needed later by another node, | |||
| // so we need to use a copy of it.. | |||
| @@ -491,7 +500,7 @@ private: | |||
| auto src = sources.getReference(i); | |||
| auto sourceBufIndex = getBufferContaining (src); | |||
| if (sourceBufIndex >= 0 && ! isBufferNeededLater (ourRenderingIndex, inputChan, src)) | |||
| if (sourceBufIndex >= 0 && ! isBufferNeededLater (graph, ourRenderingIndex, inputChan, src)) | |||
| { | |||
| // we've found one of our input chans that can be re-used.. | |||
| reusableInputIndex = i; | |||
| @@ -541,7 +550,7 @@ private: | |||
| if (nodeDelay < maxLatency) | |||
| { | |||
| if (! isBufferNeededLater (ourRenderingIndex, inputChan, src)) | |||
| if (! isBufferNeededLater (graph, ourRenderingIndex, inputChan, src)) | |||
| { | |||
| sequence.addDelayChannelOp (srcIndex, maxLatency - nodeDelay); | |||
| } | |||
| @@ -563,10 +572,13 @@ private: | |||
| } | |||
| template <typename RenderSequence> | |||
| int findBufferForInputMidiChannel (RenderSequence& sequence, Node& node, int ourRenderingIndex) | |||
| int findBufferForInputMidiChannel (const AudioProcessorGraph& graph, | |||
| RenderSequence& sequence, | |||
| Node& node, | |||
| int ourRenderingIndex) | |||
| { | |||
| auto& processor = *node.getProcessor(); | |||
| auto sources = getSourcesForChannel (node, AudioProcessorGraph::midiChannelIndex); | |||
| auto sources = getSourcesForChannel (graph, node, AudioProcessorGraph::midiChannelIndex); | |||
| // No midi inputs.. | |||
| if (sources.isEmpty()) | |||
| @@ -587,7 +599,7 @@ private: | |||
| if (midiBufferToUse >= 0) | |||
| { | |||
| if (isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) | |||
| if (isBufferNeededLater (graph, ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) | |||
| { | |||
| // can't mess up this channel because it's needed later by another node, so we | |||
| // need to use a copy of it.. | |||
| @@ -615,7 +627,7 @@ private: | |||
| auto sourceBufIndex = getBufferContaining (src); | |||
| if (sourceBufIndex >= 0 | |||
| && ! isBufferNeededLater (ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) | |||
| && ! isBufferNeededLater (graph, ourRenderingIndex, AudioProcessorGraph::midiChannelIndex, src)) | |||
| { | |||
| // we've found one of our input buffers that can be re-used.. | |||
| reusableInputIndex = i; | |||
| @@ -655,7 +667,10 @@ private: | |||
| } | |||
| template <typename RenderSequence> | |||
| void createRenderingOpsForNode (RenderSequence& sequence, Node& node, const int ourRenderingIndex) | |||
| void createRenderingOpsForNode (const AudioProcessorGraph& graph, | |||
| RenderSequence& sequence, | |||
| Node& node, | |||
| const int ourRenderingIndex) | |||
| { | |||
| auto& processor = *node.getProcessor(); | |||
| auto numIns = processor.getTotalNumInputChannels(); | |||
| @@ -663,12 +678,17 @@ private: | |||
| auto totalChans = jmax (numIns, numOuts); | |||
| Array<int> audioChannelsToUse; | |||
| auto maxLatency = getInputLatencyForNode (node.nodeID); | |||
| auto maxLatency = getInputLatencyForNode (graph, node.nodeID); | |||
| for (int inputChan = 0; inputChan < numIns; ++inputChan) | |||
| { | |||
| // get a list of all the inputs to this node | |||
| auto index = findBufferForInputAudioChannel (sequence, node, inputChan, ourRenderingIndex, maxLatency); | |||
| auto index = findBufferForInputAudioChannel (graph, | |||
| sequence, | |||
| node, | |||
| inputChan, | |||
| ourRenderingIndex, | |||
| maxLatency); | |||
| jassert (index >= 0); | |||
| audioChannelsToUse.add (index); | |||
| @@ -686,7 +706,7 @@ private: | |||
| audioBuffers.getReference (index).channel = { node.nodeID, outputChan }; | |||
| } | |||
| auto midiBufferToUse = findBufferForInputMidiChannel (sequence, node, ourRenderingIndex); | |||
| auto midiBufferToUse = findBufferForInputMidiChannel (graph, sequence, node, ourRenderingIndex); | |||
| if (processor.producesMidi()) | |||
| midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, AudioProcessorGraph::midiChannelIndex }; | |||
| @@ -700,7 +720,9 @@ private: | |||
| } | |||
| //============================================================================== | |||
| Array<AudioProcessorGraph::NodeAndChannel> getSourcesForChannel (Node& node, int inputChannelIndex) | |||
| Array<AudioProcessorGraph::NodeAndChannel> getSourcesForChannel (const AudioProcessorGraph& graph, | |||
| Node& node, | |||
| int inputChannelIndex) | |||
| { | |||
| Array<AudioProcessorGraph::NodeAndChannel> results; | |||
| AudioProcessorGraph::NodeAndChannel nc { node.nodeID, inputChannelIndex }; | |||
| @@ -737,14 +759,17 @@ private: | |||
| return -1; | |||
| } | |||
| void markAnyUnusedBuffersAsFree (Array<AssignedBuffer>& buffers, const int stepIndex) | |||
| void markAnyUnusedBuffersAsFree (const AudioProcessorGraph& graph, | |||
| Array<AssignedBuffer>& buffers, | |||
| const int stepIndex) | |||
| { | |||
| for (auto& b : buffers) | |||
| if (b.isAssigned() && ! isBufferNeededLater (stepIndex, -1, b.channel)) | |||
| if (b.isAssigned() && ! isBufferNeededLater (graph, stepIndex, -1, b.channel)) | |||
| b.setFree(); | |||
| } | |||
| bool isBufferNeededLater (int stepIndexToSearchFrom, | |||
| bool isBufferNeededLater (const AudioProcessorGraph& graph, | |||
| int stepIndexToSearchFrom, | |||
| int inputChannelOfIndexToIgnore, | |||
| AudioProcessorGraph::NodeAndChannel output) const | |||
| { | |||
| @@ -774,21 +799,19 @@ private: | |||
| } | |||
| template <typename RenderSequence> | |||
| RenderSequenceBuilder (AudioProcessorGraph& g, RenderSequence& sequence) | |||
| : graph (g), orderedNodes (createOrderedNodeList (graph)) | |||
| RenderSequenceBuilder (const AudioProcessorGraph& graph, RenderSequence& sequence) | |||
| : orderedNodes (createOrderedNodeList (graph)) | |||
| { | |||
| audioBuffers.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros | |||
| midiBuffers .add (AssignedBuffer::createReadOnlyEmpty()); | |||
| for (int i = 0; i < orderedNodes.size(); ++i) | |||
| { | |||
| createRenderingOpsForNode (sequence, *orderedNodes.getUnchecked(i), i); | |||
| markAnyUnusedBuffersAsFree (audioBuffers, i); | |||
| markAnyUnusedBuffersAsFree (midiBuffers, i); | |||
| createRenderingOpsForNode (graph, sequence, *orderedNodes.getUnchecked(i), i); | |||
| markAnyUnusedBuffersAsFree (graph, audioBuffers, i); | |||
| markAnyUnusedBuffersAsFree (graph, midiBuffers, i); | |||
| } | |||
| graph.setLatencySamples (totalLatency); | |||
| sequence.numBuffersNeeded = audioBuffers.size(); | |||
| sequence.numMidiBuffersNeeded = midiBuffers.size(); | |||
| } | |||
| @@ -1249,8 +1272,11 @@ void AudioProcessorGraph::handleAsyncUpdate() | |||
| const ScopedLock sl (getCallbackLock()); | |||
| const auto currentBlockSize = getBlockSize(); | |||
| newSequenceF->prepareBuffers (currentBlockSize); | |||
| newSequenceD->prepareBuffers (currentBlockSize); | |||
| newSequenceF.sequence->prepareBuffers (currentBlockSize); | |||
| newSequenceD.sequence->prepareBuffers (currentBlockSize); | |||
| jassert (newSequenceF.latencySamples == newSequenceD.latencySamples); | |||
| setLatencySamples (newSequenceF.latencySamples); | |||
| if (anyNodesNeedPreparing()) | |||
| { | |||
| @@ -1263,8 +1289,8 @@ void AudioProcessorGraph::handleAsyncUpdate() | |||
| isPrepared = 1; | |||
| std::swap (renderSequenceFloat, newSequenceF); | |||
| std::swap (renderSequenceDouble, newSequenceD); | |||
| renderSequenceFloat = std::move (newSequenceF.sequence); | |||
| renderSequenceDouble = std::move (newSequenceD.sequence); | |||
| } | |||
| //============================================================================== | |||