2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
23 ==============================================================================
29 static void updateOnMessageThread (AsyncUpdater
& updater
)
31 if (MessageManager::getInstance()->isThisTheMessageThread())
32 updater
.handleAsyncUpdate();
34 updater
.triggerAsyncUpdate();
37 template <typename FloatType
>
38 struct GraphRenderSequence
42 FloatType
** audioBuffers
;
43 MidiBuffer
* midiBuffers
;
44 AudioPlayHead
* audioPlayHead
;
48 void perform (AudioBuffer
<FloatType
>& buffer
, MidiBuffer
& midiMessages
, AudioPlayHead
* audioPlayHead
)
50 auto numSamples
= buffer
.getNumSamples();
51 auto maxSamples
= renderingBuffer
.getNumSamples();
53 if (numSamples
> maxSamples
)
55 // Being asked to render more samples than our buffers have, so divide the buffer into chunks
56 int chunkStartSample
= 0;
57 while (chunkStartSample
< numSamples
)
59 auto chunkSize
= jmin (maxSamples
, numSamples
- chunkStartSample
);
61 AudioBuffer
<FloatType
> audioChunk (buffer
.getArrayOfWritePointers(), buffer
.getNumChannels(), chunkStartSample
, chunkSize
);
63 midiChunk
.addEvents (midiMessages
, chunkStartSample
, chunkSize
, -chunkStartSample
);
65 // Splitting up the buffer like this will cause the play head and host time to be
66 // invalid for all but the first chunk...
67 perform (audioChunk
, midiChunk
, audioPlayHead
);
69 chunkStartSample
+= maxSamples
;
75 currentAudioInputBuffer
= &buffer
;
76 currentAudioOutputBuffer
.setSize (jmax (1, buffer
.getNumChannels()), numSamples
);
77 currentAudioOutputBuffer
.clear();
78 currentMidiInputBuffer
= &midiMessages
;
79 currentMidiOutputBuffer
.clear();
82 const Context context
{ renderingBuffer
.getArrayOfWritePointers(), midiBuffers
.begin(), audioPlayHead
, numSamples
};
84 for (auto* op
: renderOps
)
85 op
->perform (context
);
88 for (int i
= 0; i
< buffer
.getNumChannels(); ++i
)
89 buffer
.copyFrom (i
, 0, currentAudioOutputBuffer
, i
, 0, numSamples
);
92 midiMessages
.addEvents (currentMidiOutputBuffer
, 0, buffer
.getNumSamples(), 0);
93 currentAudioInputBuffer
= nullptr;
96 void addClearChannelOp (int index
)
98 createOp ([=] (const Context
& c
) { FloatVectorOperations::clear (c
.audioBuffers
[index
], c
.numSamples
); });
101 void addCopyChannelOp (int srcIndex
, int dstIndex
)
103 createOp ([=] (const Context
& c
) { FloatVectorOperations::copy (c
.audioBuffers
[dstIndex
],
104 c
.audioBuffers
[srcIndex
],
108 void addAddChannelOp (int srcIndex
, int dstIndex
)
110 createOp ([=] (const Context
& c
) { FloatVectorOperations::add (c
.audioBuffers
[dstIndex
],
111 c
.audioBuffers
[srcIndex
],
115 void addClearMidiBufferOp (int index
)
117 createOp ([=] (const Context
& c
) { c
.midiBuffers
[index
].clear(); });
120 void addCopyMidiBufferOp (int srcIndex
, int dstIndex
)
122 createOp ([=] (const Context
& c
) { c
.midiBuffers
[dstIndex
] = c
.midiBuffers
[srcIndex
]; });
125 void addAddMidiBufferOp (int srcIndex
, int dstIndex
)
127 createOp ([=] (const Context
& c
) { c
.midiBuffers
[dstIndex
].addEvents (c
.midiBuffers
[srcIndex
],
128 0, c
.numSamples
, 0); });
131 void addDelayChannelOp (int chan
, int delaySize
)
133 renderOps
.add (new DelayChannelOp (chan
, delaySize
));
136 void addProcessOp (const AudioProcessorGraph::Node::Ptr
& node
,
137 const Array
<int>& audioChannelsUsed
, int totalNumChans
, int midiBuffer
)
139 renderOps
.add (new ProcessOp (node
, audioChannelsUsed
, totalNumChans
, midiBuffer
));
142 void prepareBuffers (int blockSize
)
144 renderingBuffer
.setSize (numBuffersNeeded
+ 1, blockSize
);
145 renderingBuffer
.clear();
146 currentAudioOutputBuffer
.setSize (numBuffersNeeded
+ 1, blockSize
);
147 currentAudioOutputBuffer
.clear();
149 currentAudioInputBuffer
= nullptr;
150 currentMidiInputBuffer
= nullptr;
151 currentMidiOutputBuffer
.clear();
153 midiBuffers
.clearQuick();
154 midiBuffers
.resize (numMidiBuffersNeeded
);
156 const int defaultMIDIBufferSize
= 512;
158 midiChunk
.ensureSize (defaultMIDIBufferSize
);
160 for (auto&& m
: midiBuffers
)
161 m
.ensureSize (defaultMIDIBufferSize
);
164 void releaseBuffers()
166 renderingBuffer
.setSize (1, 1);
167 currentAudioOutputBuffer
.setSize (1, 1);
168 currentAudioInputBuffer
= nullptr;
169 currentMidiInputBuffer
= nullptr;
170 currentMidiOutputBuffer
.clear();
174 int numBuffersNeeded
= 0, numMidiBuffersNeeded
= 0;
176 AudioBuffer
<FloatType
> renderingBuffer
, currentAudioOutputBuffer
;
177 AudioBuffer
<FloatType
>* currentAudioInputBuffer
= nullptr;
179 MidiBuffer
* currentMidiInputBuffer
= nullptr;
180 MidiBuffer currentMidiOutputBuffer
;
182 Array
<MidiBuffer
> midiBuffers
;
183 MidiBuffer midiChunk
;
186 //==============================================================================
189 RenderingOp() noexcept
{}
190 virtual ~RenderingOp() {}
191 virtual void perform (const Context
&) = 0;
193 JUCE_LEAK_DETECTOR (RenderingOp
)
196 OwnedArray
<RenderingOp
> renderOps
;
198 //==============================================================================
199 template <typename LambdaType
,
200 std::enable_if_t
<std::is_rvalue_reference
<LambdaType
&&>::value
, int> = 0>
201 void createOp (LambdaType
&& fn
)
203 struct LambdaOp
: public RenderingOp
205 LambdaOp (LambdaType
&& f
) : function (std::forward
<LambdaType
> (f
)) {}
206 void perform (const Context
& c
) override
{ function (c
); }
211 renderOps
.add (new LambdaOp (std::forward
<LambdaType
> (fn
)));
214 //==============================================================================
215 struct DelayChannelOp
: public RenderingOp
217 DelayChannelOp (int chan
, int delaySize
)
219 bufferSize (delaySize
+ 1),
220 writeIndex (delaySize
)
222 buffer
.calloc ((size_t) bufferSize
);
225 void perform (const Context
& c
) override
227 auto* data
= c
.audioBuffers
[channel
];
229 for (int i
= c
.numSamples
; --i
>= 0;)
231 buffer
[writeIndex
] = *data
;
232 *data
++ = buffer
[readIndex
];
234 if (++readIndex
>= bufferSize
) readIndex
= 0;
235 if (++writeIndex
>= bufferSize
) writeIndex
= 0;
239 HeapBlock
<FloatType
> buffer
;
240 const int channel
, bufferSize
;
241 int readIndex
= 0, writeIndex
;
243 JUCE_DECLARE_NON_COPYABLE (DelayChannelOp
)
246 //==============================================================================
247 struct ProcessOp
: public RenderingOp
249 ProcessOp (const AudioProcessorGraph::Node::Ptr
& n
,
250 const Array
<int>& audioChannelsUsed
,
251 int totalNumChans
, int midiBuffer
)
253 processor (*n
->getProcessor()),
254 audioChannelsToUse (audioChannelsUsed
),
255 totalChans (jmax (1, totalNumChans
)),
256 midiBufferToUse (midiBuffer
)
258 audioChannels
.calloc ((size_t) totalChans
);
260 while (audioChannelsToUse
.size() < totalChans
)
261 audioChannelsToUse
.add (0);
264 void perform (const Context
& c
) override
266 processor
.setPlayHead (c
.audioPlayHead
);
268 for (int i
= 0; i
< totalChans
; ++i
)
269 audioChannels
[i
] = c
.audioBuffers
[audioChannelsToUse
.getUnchecked (i
)];
271 auto numAudioChannels
= [this]
273 if (const auto* proc
= node
->getProcessor())
274 if (proc
->getTotalNumInputChannels() == 0 && proc
->getTotalNumOutputChannels() == 0)
280 AudioBuffer
<FloatType
> buffer (audioChannels
, numAudioChannels
, c
.numSamples
);
282 const ScopedLock
lock (processor
.getCallbackLock());
284 if (processor
.isSuspended())
287 callProcess (buffer
, c
.midiBuffers
[midiBufferToUse
]);
290 void callProcess (AudioBuffer
<float>& buffer
, MidiBuffer
& midiMessages
)
292 if (processor
.isUsingDoublePrecision())
294 tempBufferDouble
.makeCopyOf (buffer
, true);
296 if (node
->isBypassed())
297 node
->processBlockBypassed (tempBufferDouble
, midiMessages
);
299 node
->processBlock (tempBufferDouble
, midiMessages
);
301 buffer
.makeCopyOf (tempBufferDouble
, true);
305 if (node
->isBypassed())
306 node
->processBlockBypassed (buffer
, midiMessages
);
308 node
->processBlock (buffer
, midiMessages
);
312 void callProcess (AudioBuffer
<double>& buffer
, MidiBuffer
& midiMessages
)
314 if (processor
.isUsingDoublePrecision())
316 if (node
->isBypassed())
317 node
->processBlockBypassed (buffer
, midiMessages
);
319 node
->processBlock (buffer
, midiMessages
);
323 tempBufferFloat
.makeCopyOf (buffer
, true);
325 if (node
->isBypassed())
326 node
->processBlockBypassed (tempBufferFloat
, midiMessages
);
328 node
->processBlock (tempBufferFloat
, midiMessages
);
330 buffer
.makeCopyOf (tempBufferFloat
, true);
334 const AudioProcessorGraph::Node::Ptr node
;
335 AudioProcessor
& processor
;
337 Array
<int> audioChannelsToUse
;
338 HeapBlock
<FloatType
*> audioChannels
;
339 AudioBuffer
<float> tempBufferFloat
, tempBufferDouble
;
340 const int totalChans
, midiBufferToUse
;
342 JUCE_DECLARE_NON_COPYABLE (ProcessOp
)
346 //==============================================================================
347 //==============================================================================
348 template <typename RenderSequence
>
349 struct RenderSequenceBuilder
351 RenderSequenceBuilder (AudioProcessorGraph
& g
, RenderSequence
& s
)
352 : graph (g
), sequence (s
), orderedNodes (createOrderedNodeList (graph
))
354 audioBuffers
.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros
355 midiBuffers
.add (AssignedBuffer::createReadOnlyEmpty());
357 for (int i
= 0; i
< orderedNodes
.size(); ++i
)
359 createRenderingOpsForNode (*orderedNodes
.getUnchecked(i
), i
);
360 markAnyUnusedBuffersAsFree (audioBuffers
, i
);
361 markAnyUnusedBuffersAsFree (midiBuffers
, i
);
364 graph
.setLatencySamples (totalLatency
);
366 s
.numBuffersNeeded
= audioBuffers
.size();
367 s
.numMidiBuffersNeeded
= midiBuffers
.size();
370 //==============================================================================
371 using Node
= AudioProcessorGraph::Node
;
372 using NodeID
= AudioProcessorGraph::NodeID
;
374 AudioProcessorGraph
& graph
;
375 RenderSequence
& sequence
;
377 const Array
<Node
*> orderedNodes
;
379 struct AssignedBuffer
381 AudioProcessorGraph::NodeAndChannel channel
;
383 static AssignedBuffer
createReadOnlyEmpty() noexcept
{ return { { zeroNodeID(), 0 } }; }
384 static AssignedBuffer
createFree() noexcept
{ return { { freeNodeID(), 0 } }; }
386 bool isReadOnlyEmpty() const noexcept
{ return channel
.nodeID
== zeroNodeID(); }
387 bool isFree() const noexcept
{ return channel
.nodeID
== freeNodeID(); }
388 bool isAssigned() const noexcept
{ return ! (isReadOnlyEmpty() || isFree()); }
390 void setFree() noexcept
{ channel
= { freeNodeID(), 0 }; }
391 void setAssignedToNonExistentNode() noexcept
{ channel
= { anonNodeID(), 0 }; }
394 static NodeID
anonNodeID() { return NodeID (0x7ffffffd); }
395 static NodeID
zeroNodeID() { return NodeID (0x7ffffffe); }
396 static NodeID
freeNodeID() { return NodeID (0x7fffffff); }
399 Array
<AssignedBuffer
> audioBuffers
, midiBuffers
;
401 enum { readOnlyEmptyBufferIndex
= 0 };
409 HashMap
<uint32
, int> delays
;
410 int totalLatency
= 0;
412 int getNodeDelay (NodeID nodeID
) const noexcept
414 return delays
[nodeID
.uid
];
417 int getInputLatencyForNode (NodeID nodeID
) const
421 for (auto&& c
: graph
.getConnections())
422 if (c
.destination
.nodeID
== nodeID
)
423 maxLatency
= jmax (maxLatency
, getNodeDelay (c
.source
.nodeID
));
428 //==============================================================================
429 static void getAllParentsOfNode (const Node
* child
,
430 std::unordered_set
<Node
*>& parents
,
431 const std::unordered_map
<Node
*, std::unordered_set
<Node
*>>& otherParents
)
433 for (auto&& i
: child
->inputs
)
435 auto* parentNode
= i
.otherNode
;
437 if (parentNode
== child
)
440 if (parents
.insert (parentNode
).second
)
442 auto parentParents
= otherParents
.find (parentNode
);
444 if (parentParents
!= otherParents
.end())
446 parents
.insert (parentParents
->second
.begin(), parentParents
->second
.end());
450 getAllParentsOfNode (i
.otherNode
, parents
, otherParents
);
455 static auto createOrderedNodeList (const AudioProcessorGraph
& graph
)
459 std::unordered_map
<Node
*, std::unordered_set
<Node
*>> nodeParents
;
461 for (auto* node
: graph
.getNodes())
463 int insertionIndex
= 0;
465 for (; insertionIndex
< result
.size(); ++insertionIndex
)
467 auto& parents
= nodeParents
[result
.getUnchecked (insertionIndex
)];
469 if (parents
.find (node
) != parents
.end())
473 result
.insert (insertionIndex
, node
);
474 getAllParentsOfNode (node
, nodeParents
[node
], nodeParents
);
480 int findBufferForInputAudioChannel (Node
& node
, const int inputChan
,
481 const int ourRenderingIndex
, const int maxLatency
)
483 auto& processor
= *node
.getProcessor();
484 auto numOuts
= processor
.getTotalNumOutputChannels();
486 auto sources
= getSourcesForChannel (node
, inputChan
);
488 // Handle an unconnected input channel...
489 if (sources
.isEmpty())
491 if (inputChan
>= numOuts
)
492 return readOnlyEmptyBufferIndex
;
494 auto index
= getFreeBuffer (audioBuffers
);
495 sequence
.addClearChannelOp (index
);
499 // Handle an input from a single source..
500 if (sources
.size() == 1)
502 // channel with a straightforward single input..
503 auto src
= sources
.getUnchecked(0);
505 int bufIndex
= getBufferContaining (src
);
509 // if not found, this is probably a feedback loop
510 bufIndex
= readOnlyEmptyBufferIndex
;
511 jassert (bufIndex
>= 0);
514 if (inputChan
< numOuts
515 && isBufferNeededLater (ourRenderingIndex
, inputChan
, src
))
517 // can't mess up this channel because it's needed later by another node,
518 // so we need to use a copy of it..
519 auto newFreeBuffer
= getFreeBuffer (audioBuffers
);
520 sequence
.addCopyChannelOp (bufIndex
, newFreeBuffer
);
521 bufIndex
= newFreeBuffer
;
524 auto nodeDelay
= getNodeDelay (src
.nodeID
);
526 if (nodeDelay
< maxLatency
)
527 sequence
.addDelayChannelOp (bufIndex
, maxLatency
- nodeDelay
);
532 // Handle a mix of several outputs coming into this input..
533 int reusableInputIndex
= -1;
536 for (int i
= 0; i
< sources
.size(); ++i
)
538 auto src
= sources
.getReference(i
);
539 auto sourceBufIndex
= getBufferContaining (src
);
541 if (sourceBufIndex
>= 0 && ! isBufferNeededLater (ourRenderingIndex
, inputChan
, src
))
543 // we've found one of our input chans that can be re-used..
544 reusableInputIndex
= i
;
545 bufIndex
= sourceBufIndex
;
547 auto nodeDelay
= getNodeDelay (src
.nodeID
);
549 if (nodeDelay
< maxLatency
)
550 sequence
.addDelayChannelOp (bufIndex
, maxLatency
- nodeDelay
);
556 if (reusableInputIndex
< 0)
558 // can't re-use any of our input chans, so get a new one and copy everything into it..
559 bufIndex
= getFreeBuffer (audioBuffers
);
560 jassert (bufIndex
!= 0);
562 audioBuffers
.getReference (bufIndex
).setAssignedToNonExistentNode();
564 auto srcIndex
= getBufferContaining (sources
.getFirst());
567 sequence
.addClearChannelOp (bufIndex
); // if not found, this is probably a feedback loop
569 sequence
.addCopyChannelOp (srcIndex
, bufIndex
);
571 reusableInputIndex
= 0;
572 auto nodeDelay
= getNodeDelay (sources
.getFirst().nodeID
);
574 if (nodeDelay
< maxLatency
)
575 sequence
.addDelayChannelOp (bufIndex
, maxLatency
- nodeDelay
);
578 for (int i
= 0; i
< sources
.size(); ++i
)
580 if (i
!= reusableInputIndex
)
582 auto src
= sources
.getReference(i
);
583 int srcIndex
= getBufferContaining (src
);
587 auto nodeDelay
= getNodeDelay (src
.nodeID
);
589 if (nodeDelay
< maxLatency
)
591 if (! isBufferNeededLater (ourRenderingIndex
, inputChan
, src
))
593 sequence
.addDelayChannelOp (srcIndex
, maxLatency
- nodeDelay
);
595 else // buffer is reused elsewhere, can't be delayed
597 auto bufferToDelay
= getFreeBuffer (audioBuffers
);
598 sequence
.addCopyChannelOp (srcIndex
, bufferToDelay
);
599 sequence
.addDelayChannelOp (bufferToDelay
, maxLatency
- nodeDelay
);
600 srcIndex
= bufferToDelay
;
604 sequence
.addAddChannelOp (srcIndex
, bufIndex
);
612 int findBufferForInputMidiChannel (Node
& node
, int ourRenderingIndex
)
614 auto& processor
= *node
.getProcessor();
615 auto sources
= getSourcesForChannel (node
, AudioProcessorGraph::midiChannelIndex
);
618 if (sources
.isEmpty())
620 auto midiBufferToUse
= getFreeBuffer (midiBuffers
); // need to pick a buffer even if the processor doesn't use midi
622 if (processor
.acceptsMidi() || processor
.producesMidi())
623 sequence
.addClearMidiBufferOp (midiBufferToUse
);
625 return midiBufferToUse
;
629 if (sources
.size() == 1)
631 auto src
= sources
.getReference (0);
632 auto midiBufferToUse
= getBufferContaining (src
);
634 if (midiBufferToUse
>= 0)
636 if (isBufferNeededLater (ourRenderingIndex
, AudioProcessorGraph::midiChannelIndex
, src
))
638 // can't mess up this channel because it's needed later by another node, so we
639 // need to use a copy of it..
640 auto newFreeBuffer
= getFreeBuffer (midiBuffers
);
641 sequence
.addCopyMidiBufferOp (midiBufferToUse
, newFreeBuffer
);
642 midiBufferToUse
= newFreeBuffer
;
647 // probably a feedback loop, so just use an empty one..
648 midiBufferToUse
= getFreeBuffer (midiBuffers
); // need to pick a buffer even if the processor doesn't use midi
651 return midiBufferToUse
;
654 // Multiple midi inputs..
655 int midiBufferToUse
= -1;
656 int reusableInputIndex
= -1;
658 for (int i
= 0; i
< sources
.size(); ++i
)
660 auto src
= sources
.getReference (i
);
661 auto sourceBufIndex
= getBufferContaining (src
);
663 if (sourceBufIndex
>= 0
664 && ! isBufferNeededLater (ourRenderingIndex
, AudioProcessorGraph::midiChannelIndex
, src
))
666 // we've found one of our input buffers that can be re-used..
667 reusableInputIndex
= i
;
668 midiBufferToUse
= sourceBufIndex
;
673 if (reusableInputIndex
< 0)
675 // can't re-use any of our input buffers, so get a new one and copy everything into it..
676 midiBufferToUse
= getFreeBuffer (midiBuffers
);
677 jassert (midiBufferToUse
>= 0);
679 auto srcIndex
= getBufferContaining (sources
.getUnchecked(0));
682 sequence
.addCopyMidiBufferOp (srcIndex
, midiBufferToUse
);
684 sequence
.addClearMidiBufferOp (midiBufferToUse
);
686 reusableInputIndex
= 0;
689 for (int i
= 0; i
< sources
.size(); ++i
)
691 if (i
!= reusableInputIndex
)
693 auto srcIndex
= getBufferContaining (sources
.getUnchecked(i
));
696 sequence
.addAddMidiBufferOp (srcIndex
, midiBufferToUse
);
700 return midiBufferToUse
;
703 void createRenderingOpsForNode (Node
& node
, const int ourRenderingIndex
)
705 auto& processor
= *node
.getProcessor();
706 auto numIns
= processor
.getTotalNumInputChannels();
707 auto numOuts
= processor
.getTotalNumOutputChannels();
708 auto totalChans
= jmax (numIns
, numOuts
);
710 Array
<int> audioChannelsToUse
;
711 auto maxLatency
= getInputLatencyForNode (node
.nodeID
);
713 for (int inputChan
= 0; inputChan
< numIns
; ++inputChan
)
715 // get a list of all the inputs to this node
716 auto index
= findBufferForInputAudioChannel (node
, inputChan
, ourRenderingIndex
, maxLatency
);
717 jassert (index
>= 0);
719 audioChannelsToUse
.add (index
);
721 if (inputChan
< numOuts
)
722 audioBuffers
.getReference (index
).channel
= { node
.nodeID
, inputChan
};
725 for (int outputChan
= numIns
; outputChan
< numOuts
; ++outputChan
)
727 auto index
= getFreeBuffer (audioBuffers
);
728 jassert (index
!= 0);
729 audioChannelsToUse
.add (index
);
731 audioBuffers
.getReference (index
).channel
= { node
.nodeID
, outputChan
};
734 auto midiBufferToUse
= findBufferForInputMidiChannel (node
, ourRenderingIndex
);
736 if (processor
.producesMidi())
737 midiBuffers
.getReference (midiBufferToUse
).channel
= { node
.nodeID
, AudioProcessorGraph::midiChannelIndex
};
739 delays
.set (node
.nodeID
.uid
, maxLatency
+ processor
.getLatencySamples());
742 totalLatency
= maxLatency
;
744 sequence
.addProcessOp (node
, audioChannelsToUse
, totalChans
, midiBufferToUse
);
747 //==============================================================================
748 Array
<AudioProcessorGraph::NodeAndChannel
> getSourcesForChannel (Node
& node
, int inputChannelIndex
)
750 Array
<AudioProcessorGraph::NodeAndChannel
> results
;
751 AudioProcessorGraph::NodeAndChannel nc
{ node
.nodeID
, inputChannelIndex
};
753 for (auto&& c
: graph
.getConnections())
754 if (c
.destination
== nc
)
755 results
.add (c
.source
);
760 static int getFreeBuffer (Array
<AssignedBuffer
>& buffers
)
762 for (int i
= 1; i
< buffers
.size(); ++i
)
763 if (buffers
.getReference (i
).isFree())
766 buffers
.add (AssignedBuffer::createFree());
767 return buffers
.size() - 1;
770 int getBufferContaining (AudioProcessorGraph::NodeAndChannel output
) const noexcept
774 for (auto& b
: output
.isMIDI() ? midiBuffers
: audioBuffers
)
776 if (b
.channel
== output
)
785 void markAnyUnusedBuffersAsFree (Array
<AssignedBuffer
>& buffers
, const int stepIndex
)
787 for (auto& b
: buffers
)
788 if (b
.isAssigned() && ! isBufferNeededLater (stepIndex
, -1, b
.channel
))
792 bool isBufferNeededLater (int stepIndexToSearchFrom
,
793 int inputChannelOfIndexToIgnore
,
794 AudioProcessorGraph::NodeAndChannel output
) const
796 while (stepIndexToSearchFrom
< orderedNodes
.size())
798 auto* node
= orderedNodes
.getUnchecked (stepIndexToSearchFrom
);
802 if (inputChannelOfIndexToIgnore
!= AudioProcessorGraph::midiChannelIndex
803 && graph
.isConnected ({ { output
.nodeID
, AudioProcessorGraph::midiChannelIndex
},
804 { node
->nodeID
, AudioProcessorGraph::midiChannelIndex
} }))
809 for (int i
= 0; i
< node
->getProcessor()->getTotalNumInputChannels(); ++i
)
810 if (i
!= inputChannelOfIndexToIgnore
&& graph
.isConnected ({ output
, { node
->nodeID
, i
} }))
814 inputChannelOfIndexToIgnore
= -1;
815 ++stepIndexToSearchFrom
;
821 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderSequenceBuilder
)
824 //==============================================================================
825 AudioProcessorGraph::Connection::Connection (NodeAndChannel src
, NodeAndChannel dst
) noexcept
826 : source (src
), destination (dst
)
830 bool AudioProcessorGraph::Connection::operator== (const Connection
& other
) const noexcept
832 return source
== other
.source
&& destination
== other
.destination
;
835 bool AudioProcessorGraph::Connection::operator!= (const Connection
& c
) const noexcept
837 return ! operator== (c
);
840 bool AudioProcessorGraph::Connection::operator< (const Connection
& other
) const noexcept
842 if (source
.nodeID
!= other
.source
.nodeID
)
843 return source
.nodeID
< other
.source
.nodeID
;
845 if (destination
.nodeID
!= other
.destination
.nodeID
)
846 return destination
.nodeID
< other
.destination
.nodeID
;
848 if (source
.channelIndex
!= other
.source
.channelIndex
)
849 return source
.channelIndex
< other
.source
.channelIndex
;
851 return destination
.channelIndex
< other
.destination
.channelIndex
;
854 //==============================================================================
855 AudioProcessorGraph::Node::Node (NodeID n
, std::unique_ptr
<AudioProcessor
> p
) noexcept
856 : nodeID (n
), processor (std::move (p
))
858 jassert (processor
!= nullptr);
861 void AudioProcessorGraph::Node::prepare (double newSampleRate
, int newBlockSize
,
862 AudioProcessorGraph
* graph
, ProcessingPrecision precision
)
864 const ScopedLock
lock (processorLock
);
868 setParentGraph (graph
);
870 // try to align the precision of the processor and the graph
871 processor
->setProcessingPrecision (processor
->supportsDoublePrecisionProcessing() ? precision
874 processor
->setRateAndBufferSizeDetails (newSampleRate
, newBlockSize
);
875 processor
->prepareToPlay (newSampleRate
, newBlockSize
);
877 // This may be checked from other threads that haven't taken the processorLock,
878 // so we need to leave it until the processor has been completely prepared
883 void AudioProcessorGraph::Node::unprepare()
885 const ScopedLock
lock (processorLock
);
890 processor
->releaseResources();
894 void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph
* const graph
) const
896 const ScopedLock
lock (processorLock
);
898 if (auto* ioProc
= dynamic_cast<AudioProcessorGraph::AudioGraphIOProcessor
*> (processor
.get()))
899 ioProc
->setParentGraph (graph
);
902 bool AudioProcessorGraph::Node::Connection::operator== (const Connection
& other
) const noexcept
904 return otherNode
== other
.otherNode
905 && thisChannel
== other
.thisChannel
906 && otherChannel
== other
.otherChannel
;
909 //==============================================================================
910 bool AudioProcessorGraph::Node::isBypassed() const noexcept
912 if (processor
!= nullptr)
914 if (auto* bypassParam
= processor
->getBypassParameter())
915 return (bypassParam
->getValue() != 0.0f
);
921 void AudioProcessorGraph::Node::setBypassed (bool shouldBeBypassed
) noexcept
923 if (processor
!= nullptr)
925 if (auto* bypassParam
= processor
->getBypassParameter())
926 bypassParam
->setValueNotifyingHost (shouldBeBypassed
? 1.0f
: 0.0f
);
929 bypassed
= shouldBeBypassed
;
932 //==============================================================================
933 struct AudioProcessorGraph::RenderSequenceFloat
: public GraphRenderSequence
<float> {};
934 struct AudioProcessorGraph::RenderSequenceDouble
: public GraphRenderSequence
<double> {};
936 //==============================================================================
937 AudioProcessorGraph::AudioProcessorGraph()
941 AudioProcessorGraph::~AudioProcessorGraph()
943 cancelPendingUpdate();
944 clearRenderingSequence();
948 const String
AudioProcessorGraph::getName() const
950 return "Audio Graph";
953 //==============================================================================
954 void AudioProcessorGraph::topologyChanged()
959 updateOnMessageThread (*this);
962 void AudioProcessorGraph::clear()
964 const ScopedLock
sl (getCallbackLock());
973 AudioProcessorGraph::Node
* AudioProcessorGraph::getNodeForId (NodeID nodeID
) const
975 for (auto* n
: nodes
)
976 if (n
->nodeID
== nodeID
)
982 AudioProcessorGraph::Node::Ptr
AudioProcessorGraph::addNode (std::unique_ptr
<AudioProcessor
> newProcessor
, NodeID nodeID
)
984 if (newProcessor
== nullptr || newProcessor
.get() == this)
990 if (nodeID
== NodeID())
991 nodeID
.uid
= ++(lastNodeID
.uid
);
993 for (auto* n
: nodes
)
995 if (n
->getProcessor() == newProcessor
.get() || n
->nodeID
== nodeID
)
997 jassertfalse
; // Cannot add two copies of the same processor, or duplicate node IDs!
1002 if (lastNodeID
< nodeID
)
1003 lastNodeID
= nodeID
;
1005 newProcessor
->setPlayHead (getPlayHead());
1007 Node::Ptr
n (new Node (nodeID
, std::move (newProcessor
)));
1010 const ScopedLock
sl (getCallbackLock());
1011 nodes
.add (n
.get());
1014 n
->setParentGraph (this);
1019 AudioProcessorGraph::Node::Ptr
AudioProcessorGraph::removeNode (NodeID nodeId
)
1021 const ScopedLock
sl (getCallbackLock());
1023 for (int i
= nodes
.size(); --i
>= 0;)
1025 if (nodes
.getUnchecked (i
)->nodeID
== nodeId
)
1027 disconnectNode (nodeId
);
1028 auto node
= nodes
.removeAndReturn (i
);
1037 AudioProcessorGraph::Node::Ptr
AudioProcessorGraph::removeNode (Node
* node
)
1039 if (node
!= nullptr)
1040 return removeNode (node
->nodeID
);
1046 //==============================================================================
1047 void AudioProcessorGraph::getNodeConnections (Node
& node
, std::vector
<Connection
>& connections
)
1049 for (auto& i
: node
.inputs
)
1050 connections
.push_back ({ { i
.otherNode
->nodeID
, i
.otherChannel
}, { node
.nodeID
, i
.thisChannel
} });
1052 for (auto& o
: node
.outputs
)
1053 connections
.push_back ({ { node
.nodeID
, o
.thisChannel
}, { o
.otherNode
->nodeID
, o
.otherChannel
} });
1056 std::vector
<AudioProcessorGraph::Connection
> AudioProcessorGraph::getConnections() const
1058 std::vector
<Connection
> connections
;
1060 for (auto& n
: nodes
)
1061 getNodeConnections (*n
, connections
);
1063 std::sort (connections
.begin(), connections
.end());
1064 auto last
= std::unique (connections
.begin(), connections
.end());
1065 connections
.erase (last
, connections
.end());
1070 bool AudioProcessorGraph::isConnected (Node
* source
, int sourceChannel
, Node
* dest
, int destChannel
) const noexcept
1072 for (auto& o
: source
->outputs
)
1073 if (o
.otherNode
== dest
&& o
.thisChannel
== sourceChannel
&& o
.otherChannel
== destChannel
)
1079 bool AudioProcessorGraph::isConnected (const Connection
& c
) const noexcept
1081 if (auto* source
= getNodeForId (c
.source
.nodeID
))
1082 if (auto* dest
= getNodeForId (c
.destination
.nodeID
))
1083 return isConnected (source
, c
.source
.channelIndex
,
1084 dest
, c
.destination
.channelIndex
);
1089 bool AudioProcessorGraph::isConnected (NodeID srcID
, NodeID destID
) const noexcept
1091 if (auto* source
= getNodeForId (srcID
))
1092 if (auto* dest
= getNodeForId (destID
))
1093 for (auto& out
: source
->outputs
)
1094 if (out
.otherNode
== dest
)
1100 bool AudioProcessorGraph::isAnInputTo (Node
& src
, Node
& dst
) const noexcept
1102 jassert (nodes
.contains (&src
));
1103 jassert (nodes
.contains (&dst
));
1105 return isAnInputTo (src
, dst
, nodes
.size());
1108 bool AudioProcessorGraph::isAnInputTo (Node
& src
, Node
& dst
, int recursionCheck
) const noexcept
1110 for (auto&& i
: dst
.inputs
)
1111 if (i
.otherNode
== &src
)
1114 if (recursionCheck
> 0)
1115 for (auto&& i
: dst
.inputs
)
1116 if (isAnInputTo (src
, *i
.otherNode
, recursionCheck
- 1))
1122 bool AudioProcessorGraph::canConnect (Node
* source
, int sourceChannel
, Node
* dest
, int destChannel
) const noexcept
1124 bool sourceIsMIDI
= sourceChannel
== midiChannelIndex
;
1125 bool destIsMIDI
= destChannel
== midiChannelIndex
;
1127 if (sourceChannel
< 0
1130 || sourceIsMIDI
!= destIsMIDI
)
1133 if (source
== nullptr
1134 || (! sourceIsMIDI
&& sourceChannel
>= source
->processor
->getTotalNumOutputChannels())
1135 || (sourceIsMIDI
&& ! source
->processor
->producesMidi()))
1139 || (! destIsMIDI
&& destChannel
>= dest
->processor
->getTotalNumInputChannels())
1140 || (destIsMIDI
&& ! dest
->processor
->acceptsMidi()))
1143 return ! isConnected (source
, sourceChannel
, dest
, destChannel
);
1146 bool AudioProcessorGraph::canConnect (const Connection
& c
) const
1148 if (auto* source
= getNodeForId (c
.source
.nodeID
))
1149 if (auto* dest
= getNodeForId (c
.destination
.nodeID
))
1150 return canConnect (source
, c
.source
.channelIndex
,
1151 dest
, c
.destination
.channelIndex
);
1156 bool AudioProcessorGraph::addConnection (const Connection
& c
)
1158 if (auto* source
= getNodeForId (c
.source
.nodeID
))
1160 if (auto* dest
= getNodeForId (c
.destination
.nodeID
))
1162 auto sourceChan
= c
.source
.channelIndex
;
1163 auto destChan
= c
.destination
.channelIndex
;
1165 if (canConnect (source
, sourceChan
, dest
, destChan
))
1167 source
->outputs
.add ({ dest
, destChan
, sourceChan
});
1168 dest
->inputs
.add ({ source
, sourceChan
, destChan
});
1169 jassert (isConnected (c
));
1179 bool AudioProcessorGraph::removeConnection (const Connection
& c
)
1181 if (auto* source
= getNodeForId (c
.source
.nodeID
))
1183 if (auto* dest
= getNodeForId (c
.destination
.nodeID
))
1185 auto sourceChan
= c
.source
.channelIndex
;
1186 auto destChan
= c
.destination
.channelIndex
;
1188 if (isConnected (source
, sourceChan
, dest
, destChan
))
1190 source
->outputs
.removeAllInstancesOf ({ dest
, destChan
, sourceChan
});
1191 dest
->inputs
.removeAllInstancesOf ({ source
, sourceChan
, destChan
});
1201 bool AudioProcessorGraph::disconnectNode (NodeID nodeID
)
1203 if (auto* node
= getNodeForId (nodeID
))
1205 std::vector
<Connection
> connections
;
1206 getNodeConnections (*node
, connections
);
1208 if (! connections
.empty())
1210 for (auto c
: connections
)
1211 removeConnection (c
);
1220 bool AudioProcessorGraph::isLegal (Node
* source
, int sourceChannel
, Node
* dest
, int destChannel
) const noexcept
1222 return (sourceChannel
== midiChannelIndex
? source
->processor
->producesMidi()
1223 : isPositiveAndBelow (sourceChannel
, source
->processor
->getTotalNumOutputChannels()))
1224 && (destChannel
== midiChannelIndex
? dest
->processor
->acceptsMidi()
1225 : isPositiveAndBelow (destChannel
, dest
->processor
->getTotalNumInputChannels()));
1228 bool AudioProcessorGraph::isConnectionLegal (const Connection
& c
) const
1230 if (auto* source
= getNodeForId (c
.source
.nodeID
))
1231 if (auto* dest
= getNodeForId (c
.destination
.nodeID
))
1232 return isLegal (source
, c
.source
.channelIndex
, dest
, c
.destination
.channelIndex
);
1237 bool AudioProcessorGraph::removeIllegalConnections()
1239 bool anyRemoved
= false;
1241 for (auto* node
: nodes
)
1243 std::vector
<Connection
> connections
;
1244 getNodeConnections (*node
, connections
);
1246 for (auto c
: connections
)
1247 if (! isConnectionLegal (c
))
1248 anyRemoved
= removeConnection (c
) || anyRemoved
;
1254 //==============================================================================
1255 void AudioProcessorGraph::clearRenderingSequence()
1257 std::unique_ptr
<RenderSequenceFloat
> oldSequenceF
;
1258 std::unique_ptr
<RenderSequenceDouble
> oldSequenceD
;
1261 const ScopedLock
sl (getCallbackLock());
1262 std::swap (renderSequenceFloat
, oldSequenceF
);
1263 std::swap (renderSequenceDouble
, oldSequenceD
);
1267 bool AudioProcessorGraph::anyNodesNeedPreparing() const noexcept
1269 for (auto* node
: nodes
)
1270 if (! node
->isPrepared
)
1276 void AudioProcessorGraph::buildRenderingSequence()
1278 auto newSequenceF
= std::make_unique
<RenderSequenceFloat
>();
1279 auto newSequenceD
= std::make_unique
<RenderSequenceDouble
>();
1281 RenderSequenceBuilder
<RenderSequenceFloat
> builderF (*this, *newSequenceF
);
1282 RenderSequenceBuilder
<RenderSequenceDouble
> builderD (*this, *newSequenceD
);
1284 const ScopedLock
sl (getCallbackLock());
1286 const auto currentBlockSize
= getBlockSize();
1287 newSequenceF
->prepareBuffers (currentBlockSize
);
1288 newSequenceD
->prepareBuffers (currentBlockSize
);
1290 if (anyNodesNeedPreparing())
1292 renderSequenceFloat
.reset();
1293 renderSequenceDouble
.reset();
1295 for (auto* node
: nodes
)
1296 node
->prepare (getSampleRate(), currentBlockSize
, this, getProcessingPrecision());
1301 std::swap (renderSequenceFloat
, newSequenceF
);
1302 std::swap (renderSequenceDouble
, newSequenceD
);
1305 void AudioProcessorGraph::handleAsyncUpdate()
1307 buildRenderingSequence();
1310 //==============================================================================
1311 void AudioProcessorGraph::prepareToPlay (double sampleRate
, int estimatedSamplesPerBlock
)
1314 const ScopedLock
sl (getCallbackLock());
1315 setRateAndBufferSizeDetails (sampleRate
, estimatedSamplesPerBlock
);
1317 const auto newPrepareSettings
= [&]
1319 PrepareSettings settings
;
1320 settings
.precision
= getProcessingPrecision();
1321 settings
.sampleRate
= sampleRate
;
1322 settings
.blockSize
= estimatedSamplesPerBlock
;
1323 settings
.valid
= true;
1327 if (prepareSettings
!= newPrepareSettings
)
1330 prepareSettings
= newPrepareSettings
;
1334 clearRenderingSequence();
1336 updateOnMessageThread (*this);
1339 bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const
1344 void AudioProcessorGraph::unprepare()
1346 prepareSettings
.valid
= false;
1350 for (auto* n
: nodes
)
1354 void AudioProcessorGraph::releaseResources()
1356 const ScopedLock
sl (getCallbackLock());
1358 cancelPendingUpdate();
1362 if (renderSequenceFloat
!= nullptr)
1363 renderSequenceFloat
->releaseBuffers();
1365 if (renderSequenceDouble
!= nullptr)
1366 renderSequenceDouble
->releaseBuffers();
1369 void AudioProcessorGraph::reset()
1371 const ScopedLock
sl (getCallbackLock());
1373 for (auto* n
: nodes
)
1374 n
->getProcessor()->reset();
1377 void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime
) noexcept
1379 const ScopedLock
sl (getCallbackLock());
1381 AudioProcessor::setNonRealtime (isProcessingNonRealtime
);
1383 for (auto* n
: nodes
)
1384 n
->getProcessor()->setNonRealtime (isProcessingNonRealtime
);
1387 double AudioProcessorGraph::getTailLengthSeconds() const { return 0; }
1388 bool AudioProcessorGraph::acceptsMidi() const { return true; }
1389 bool AudioProcessorGraph::producesMidi() const { return true; }
1390 void AudioProcessorGraph::getStateInformation (MemoryBlock
&) {}
1391 void AudioProcessorGraph::setStateInformation (const void*, int) {}
1393 template <typename FloatType
, typename SequenceType
>
1394 static void processBlockForBuffer (AudioBuffer
<FloatType
>& buffer
, MidiBuffer
& midiMessages
,
1395 AudioProcessorGraph
& graph
,
1396 std::unique_ptr
<SequenceType
>& renderSequence
,
1397 std::atomic
<bool>& isPrepared
)
1399 if (graph
.isNonRealtime())
1401 while (! isPrepared
)
1404 const ScopedLock
sl (graph
.getCallbackLock());
1406 if (renderSequence
!= nullptr)
1407 renderSequence
->perform (buffer
, midiMessages
, graph
.getPlayHead());
1411 const ScopedLock
sl (graph
.getCallbackLock());
1415 if (renderSequence
!= nullptr)
1416 renderSequence
->perform (buffer
, midiMessages
, graph
.getPlayHead());
1421 midiMessages
.clear();
1426 void AudioProcessorGraph::processBlock (AudioBuffer
<float>& buffer
, MidiBuffer
& midiMessages
)
1428 if ((! isPrepared
) && MessageManager::getInstance()->isThisTheMessageThread())
1429 handleAsyncUpdate();
1431 processBlockForBuffer
<float> (buffer
, midiMessages
, *this, renderSequenceFloat
, isPrepared
);
1434 void AudioProcessorGraph::processBlock (AudioBuffer
<double>& buffer
, MidiBuffer
& midiMessages
)
1436 if ((! isPrepared
) && MessageManager::getInstance()->isThisTheMessageThread())
1437 handleAsyncUpdate();
1439 processBlockForBuffer
<double> (buffer
, midiMessages
, *this, renderSequenceDouble
, isPrepared
);
1442 //==============================================================================
1443 AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType
)
1448 AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor()
1452 const String
AudioProcessorGraph::AudioGraphIOProcessor::getName() const
1456 case audioOutputNode
: return "Audio Output";
1457 case audioInputNode
: return "Audio Input";
1458 case midiOutputNode
: return "MIDI Output";
1459 case midiInputNode
: return "MIDI Input";
1466 void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription
& d
) const
1469 d
.category
= "I/O devices";
1470 d
.pluginFormatName
= "Internal";
1471 d
.manufacturerName
= "JUCE";
1473 d
.isInstrument
= false;
1475 d
.deprecatedUid
= d
.uniqueId
= d
.name
.hashCode();
1477 d
.numInputChannels
= getTotalNumInputChannels();
1479 if (type
== audioOutputNode
&& graph
!= nullptr)
1480 d
.numInputChannels
= graph
->getTotalNumInputChannels();
1482 d
.numOutputChannels
= getTotalNumOutputChannels();
1484 if (type
== audioInputNode
&& graph
!= nullptr)
1485 d
.numOutputChannels
= graph
->getTotalNumOutputChannels();
1488 void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int)
1490 jassert (graph
!= nullptr);
1493 void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources()
1497 bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessing() const
1502 template <typename FloatType
, typename SequenceType
>
1503 static void processIOBlock (AudioProcessorGraph::AudioGraphIOProcessor
& io
, SequenceType
& sequence
,
1504 AudioBuffer
<FloatType
>& buffer
, MidiBuffer
& midiMessages
)
1506 switch (io
.getType())
1508 case AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode
:
1510 auto&& currentAudioOutputBuffer
= sequence
.currentAudioOutputBuffer
;
1512 for (int i
= jmin (currentAudioOutputBuffer
.getNumChannels(), buffer
.getNumChannels()); --i
>= 0;)
1513 currentAudioOutputBuffer
.addFrom (i
, 0, buffer
, i
, 0, buffer
.getNumSamples());
1518 case AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode
:
1520 auto* currentInputBuffer
= sequence
.currentAudioInputBuffer
;
1522 for (int i
= jmin (currentInputBuffer
->getNumChannels(), buffer
.getNumChannels()); --i
>= 0;)
1523 buffer
.copyFrom (i
, 0, *currentInputBuffer
, i
, 0, buffer
.getNumSamples());
1528 case AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode
:
1529 sequence
.currentMidiOutputBuffer
.addEvents (midiMessages
, 0, buffer
.getNumSamples(), 0);
1532 case AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode
:
1533 midiMessages
.addEvents (*sequence
.currentMidiInputBuffer
, 0, buffer
.getNumSamples(), 0);
1541 void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer
<float>& buffer
, MidiBuffer
& midiMessages
)
1543 jassert (graph
!= nullptr);
1544 processIOBlock (*this, *graph
->renderSequenceFloat
, buffer
, midiMessages
);
1547 void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer
<double>& buffer
, MidiBuffer
& midiMessages
)
1549 jassert (graph
!= nullptr);
1550 processIOBlock (*this, *graph
->renderSequenceDouble
, buffer
, midiMessages
);
1553 double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const
1558 bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const
1560 return type
== midiOutputNode
;
1563 bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const
1565 return type
== midiInputNode
;
1568 bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept
{ return type
== audioInputNode
|| type
== midiInputNode
; }
1569 bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept
{ return type
== audioOutputNode
|| type
== midiOutputNode
; }
1571 #if ! JUCE_AUDIOPROCESSOR_NO_GUI
1572 bool AudioProcessorGraph::AudioGraphIOProcessor::hasEditor() const { return false; }
1573 AudioProcessorEditor
* AudioProcessorGraph::AudioGraphIOProcessor::createEditor() { return nullptr; }
1576 int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; }
1577 int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; }
1578 void AudioProcessorGraph::AudioGraphIOProcessor::setCurrentProgram (int) { }
1580 const String
AudioProcessorGraph::AudioGraphIOProcessor::getProgramName (int) { return {}; }
1581 void AudioProcessorGraph::AudioGraphIOProcessor::changeProgramName (int, const String
&) {}
1583 void AudioProcessorGraph::AudioGraphIOProcessor::getStateInformation (MemoryBlock
&) {}
1584 void AudioProcessorGraph::AudioGraphIOProcessor::setStateInformation (const void*, int) {}
1586 void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph
* const newGraph
)
1590 if (graph
!= nullptr)
1592 setPlayConfigDetails (type
== audioOutputNode
? graph
->getTotalNumOutputChannels() : 0,
1593 type
== audioInputNode
? graph
->getTotalNumInputChannels() : 0,
1597 updateHostDisplay();