JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AudioProcessorGraph.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26// Implementation notes:
27// On macOS, calling AudioUnitInitialize will internally call AudioObjectGetPropertyData, which
28// takes a mutex.
29// This same mutex is taken on the audio thread, before calling the audio device's IO callback.
30// This is a property of the CoreAudio implementation - we can't remove or interact directly
31// with these locks in JUCE.
32//
33// AudioProcessor instances expect that their callback lock will be taken before calling
34// processBlock or processBlockBypassed.
35// This means that, to avoid deadlocks, we *always* need to make sure that the CoreAudio mutex
36// is locked before taking the callback lock.
37// Given that we can't interact with the CoreAudio mutex directly, on the main thread we can't
38// call any function that might internally interact with CoreAudio while the callback lock is
39// taken.
40// In particular, be careful not to call `prepareToPlay` on a hosted AudioUnit from the main
41// thread while the callback lock is taken.
42// The graph implementation currently makes sure to call prepareToPlay on the main thread,
43// without taking the graph's callback lock.
44
45namespace juce
46{
47
48/* Provides a comparison function for various types that have an associated NodeID,
49 for use with equal_range, lower_bound etc.
50*/
52{
53public:
57
58 ImplicitNode (NodeID x) : node (x) {}
59 ImplicitNode (NodeAndChannel x) : ImplicitNode (x.nodeID) {}
60 ImplicitNode (const Node* x) : ImplicitNode (x->nodeID) {}
62
63 /* This is the comparison function. */
64 static bool compare (ImplicitNode a, ImplicitNode b) { return a.node < b.node; }
65
66private:
67 NodeID node;
68};
69
70//==============================================================================
71/* A copyable type holding all the nodes, and allowing fast lookup by id. */
72class Nodes
73{
74public:
77
78 const ReferenceCountedArray<Node>& getNodes() const { return array; }
79
80 Node::Ptr getNodeForId (NodeID nodeID) const
81 {
82 const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
83 return iter != array.end() && (*iter)->nodeID == nodeID ? *iter : nullptr;
84 }
85
87 {
88 if (newProcessor == nullptr)
89 {
90 // Cannot add a null audio processor!
92 return {};
93 }
94
95 if (std::any_of (array.begin(),
96 array.end(),
97 [&] (auto* n) { return n->getProcessor() == newProcessor.get(); }))
98 {
99 // This audio processor has already been added to the graph!
101 return {};
102 }
103
104 const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
105
106 if (iter != array.end() && (*iter)->nodeID == nodeID)
107 {
108 // This nodeID has already been used for a node in the graph!
110 return {};
111 }
112
113 return array.insert ((int) std::distance (array.begin(), iter),
114 new Node { nodeID, std::move (newProcessor) });
115 }
116
117 Node::Ptr removeNode (NodeID nodeID)
118 {
119 const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
120 return iter != array.end() && (*iter)->nodeID == nodeID
121 ? array.removeAndReturn ((int) std::distance (array.begin(), iter))
122 : nullptr;
123 }
124
125 bool operator== (const Nodes& other) const { return array == other.array; }
126 bool operator!= (const Nodes& other) const { return array != other.array; }
127
128private:
130};
131
132//==============================================================================
133/* A value type holding a full set of graph connections. */
135{
136public:
141
142private:
143 static auto equalRange (const std::set<NodeAndChannel>& pins, const NodeID node)
144 {
145 return std::equal_range (pins.cbegin(), pins.cend(), node, ImplicitNode::compare);
146 }
147
149
150public:
151 static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex;
152
153 bool addConnection (const Nodes& n, const Connection& c)
154 {
155 if (! canConnect (n, c))
156 return false;
157
158 sourcesForDestination[c.destination].insert (c.source);
159 jassert (isConnected (c));
160 return true;
161 }
162
163 bool removeConnection (const Connection& c)
164 {
165 const auto iter = sourcesForDestination.find (c.destination);
166 return iter != sourcesForDestination.cend() && iter->second.erase (c.source) == 1;
167 }
168
169 bool removeIllegalConnections (const Nodes& n)
170 {
171 auto anyRemoved = false;
172
173 for (auto& dest : sourcesForDestination)
174 {
175 const auto initialSize = dest.second.size();
176 dest.second = removeIllegalConnections (n, std::move (dest.second), dest.first);
177 anyRemoved |= (dest.second.size() != initialSize);
178 }
179
180 return anyRemoved;
181 }
182
183 bool disconnectNode (NodeID n)
184 {
185 const auto matchingDestinations = getMatchingDestinations (n);
186 auto result = matchingDestinations.first != matchingDestinations.second;
187 sourcesForDestination.erase (matchingDestinations.first, matchingDestinations.second);
188
189 for (auto& pair : sourcesForDestination)
190 {
191 const auto range = equalRange (pair.second, n);
192 result |= range.first != range.second;
193 pair.second.erase (range.first, range.second);
194 }
195
196 return result;
197 }
198
199 static bool isConnectionLegal (const Nodes& n, Connection c)
200 {
201 const auto source = n.getNodeForId (c.source .nodeID);
202 const auto dest = n.getNodeForId (c.destination.nodeID);
203
204 const auto sourceChannel = c.source .channelIndex;
205 const auto destChannel = c.destination.channelIndex;
206
207 const auto sourceIsMIDI = AudioProcessorGraph::midiChannelIndex == sourceChannel;
208 const auto destIsMIDI = AudioProcessorGraph::midiChannelIndex == destChannel;
209
210 return sourceChannel >= 0
211 && destChannel >= 0
212 && source != dest
214 && source != nullptr
215 && (sourceIsMIDI
216 ? source->getProcessor()->producesMidi()
217 : sourceChannel < source->getProcessor()->getTotalNumOutputChannels())
218 && dest != nullptr
219 && (destIsMIDI
220 ? dest->getProcessor()->acceptsMidi()
221 : destChannel < dest->getProcessor()->getTotalNumInputChannels());
222 }
223
224 bool canConnect (const Nodes& n, Connection c) const
225 {
226 return isConnectionLegal (n, c) && ! isConnected (c);
227 }
228
229 bool isConnected (Connection c) const
230 {
231 const auto iter = sourcesForDestination.find (c.destination);
232
233 return iter != sourcesForDestination.cend()
234 && iter->second.find (c.source) != iter->second.cend();
235 }
236
237 bool isConnected (NodeID srcID, NodeID destID) const
238 {
239 const auto matchingDestinations = getMatchingDestinations (destID);
240
241 return std::any_of (matchingDestinations.first, matchingDestinations.second, [srcID] (const auto& pair)
242 {
243 const auto [begin, end] = equalRange (pair.second, srcID);
244 return begin != end;
245 });
246 }
247
248 std::set<NodeID> getSourceNodesForDestination (NodeID destID) const
249 {
250 const auto matchingDestinations = getMatchingDestinations (destID);
251
252 std::set<NodeID> result;
253 std::for_each (matchingDestinations.first, matchingDestinations.second, [&] (const auto& pair)
254 {
255 for (const auto& source : pair.second)
256 result.insert (source.nodeID);
257 });
258 return result;
259 }
260
261 std::set<NodeAndChannel> getSourcesForDestination (const NodeAndChannel& p) const
262 {
263 const auto iter = sourcesForDestination.find (p);
264 return iter != sourcesForDestination.cend() ? iter->second : std::set<NodeAndChannel>{};
265 }
266
267 std::vector<Connection> getConnections() const
268 {
270
271 for (auto& pair : sourcesForDestination)
272 for (const auto& source : pair.second)
273 result.emplace_back (source, pair.first);
274
275 std::sort (result.begin(), result.end());
276 result.erase (std::unique (result.begin(), result.end()), result.end());
277 return result;
278 }
279
280 bool isAnInputTo (NodeID source, NodeID dest) const
281 {
282 return getConnectedRecursive (source, dest, {}).found;
283 }
284
285 bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; }
286 bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; }
287
289 {
290 public:
291 explicit DestinationsForSources (Map m) : map (std::move (m)) {}
292
293 bool isSourceConnectedToDestinationNodeIgnoringChannel (const NodeAndChannel& source, NodeID dest, int channel) const
294 {
295 if (const auto destIter = map.find (source); destIter != map.cend())
296 {
297 const auto [begin, end] = equalRange (destIter->second, dest);
298 return std::any_of (begin, end, [&] (const NodeAndChannel& nodeAndChannel)
299 {
300 return nodeAndChannel != NodeAndChannel { dest, channel };
301 });
302 }
303
304 return false;
305 }
306
307 private:
308 Map map;
309 };
310
311 /* Reverses the graph, to allow fast lookup by source.
312 This is expensive, don't call this more than necessary!
313 */
314 auto getDestinationsForSources() const
315 {
317
318 for (const auto& [destination, sources] : sourcesForDestination)
319 for (const auto& source : sources)
320 destinationsForSources[source].insert (destination);
321
323 }
324
325private:
326 struct SearchState
327 {
328 std::set<NodeID> visited;
329 bool found = false;
330 };
331
332 SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const
333 {
334 state.visited.insert (dest);
335
336 for (const auto& s : getSourceNodesForDestination (dest))
337 {
338 if (state.found || s == source)
339 return { std::move (state.visited), true };
340
341 if (state.visited.find (s) == state.visited.cend())
342 state = getConnectedRecursive (source, s, std::move (state));
343 }
344
345 return state;
346 }
347
348 static std::set<NodeAndChannel> removeIllegalConnections (const Nodes& nodes,
350 NodeAndChannel destination)
351 {
352 for (auto source = sources.cbegin(); source != sources.cend();)
353 {
354 if (! isConnectionLegal (nodes, { *source, destination }))
355 source = sources.erase (source);
356 else
357 ++source;
358 }
359
360 return sources;
361 }
362
363 std::pair<Map::const_iterator, Map::const_iterator> getMatchingDestinations (NodeID destID) const
364 {
365 return std::equal_range (sourcesForDestination.cbegin(), sourcesForDestination.cend(), destID, ImplicitNode::compare);
366 }
367
368 Map sourcesForDestination;
369};
370
371//==============================================================================
372/* Settings used to prepare a node for playback. */
374{
375 using ProcessingPrecision = AudioProcessorGraph::ProcessingPrecision;
376
377 ProcessingPrecision precision = ProcessingPrecision::singlePrecision;
378 double sampleRate = 0.0;
379 int blockSize = 0;
380
381 auto tie() const noexcept { return std::tie (precision, sampleRate, blockSize); }
382
383 bool operator== (const PrepareSettings& other) const { return tie() == other.tie(); }
384 bool operator!= (const PrepareSettings& other) const { return tie() != other.tie(); }
385};
386
387//==============================================================================
388/* Keeps track of the PrepareSettings applied to each node. */
390{
391public:
394
395 /* Called from prepareToPlay and releaseResources with the PrepareSettings that should be
396 used next time the graph is rebuilt.
397 */
399 {
400 const std::lock_guard<std::mutex> lock (mutex);
401 next = newSettings;
402 }
403
404 /* Call from the audio thread only. */
405 std::optional<PrepareSettings> getLastRequestedSettings() const { return next; }
406
407 /* Call from the main thread only!
408
409 Called after updating the graph topology to prepare any currently-unprepared nodes.
410
411 To ensure that all nodes are initialised with the same sample rate, buffer size, etc. as
412 the enclosing graph, we must ensure that any operation that uses these details (preparing
413 individual nodes) is synchronized with prepare-to-play and release-resources on the
414 enclosing graph.
415
416 If the new PrepareSettings are different to the last-seen settings, all nodes will
417 be prepared/unprepared as necessary. If the PrepareSettings have not changed, then only
418 new nodes will be prepared/unprepared.
419
420 Returns the settings that were applied to the nodes.
421 */
422 std::optional<PrepareSettings> applySettings (const Nodes& n)
423 {
424 const auto settingsChanged = [this]
425 {
426 const std::lock_guard<std::mutex> lock (mutex);
427 const auto result = current != next;
428 current = next;
429 return result;
430 }();
431
432 // It may look like releaseResources and prepareToPlay could race with calls to processBlock
433 // here, because applySettings is called from the main thread, processBlock is called from
434 // the audio thread (normally), and there's no explicit mutex ensuring that the calls don't
435 // overlap.
436 // However, it is part of the AudioProcessor contract that users shall not call
437 // processBlock, prepareToPlay, and/or releaseResources concurrently. That is, there's an
438 // implied mutex synchronising these functions on each AudioProcessor.
439 //
440 // Inside processBlock, we always ensure that the current RenderSequence's PrepareSettings
441 // match the graph's settings before attempting to call processBlock on any of the graph
442 // nodes; as a result, it's impossible to start calling processBlock on a node on the audio
443 // thread while a render sequence rebuild (including prepareToPlay/releaseResources calls)
444 // is already in progress here.
445 //
446 // Due to the implied mutex between prepareToPlay/releaseResources/processBlock, it's also
447 // impossible to receive new PrepareSettings and to start a new RenderSequence rebuild while
448 // a processBlock call is in progress.
449
450 if (settingsChanged)
451 {
452 for (const auto& node : n.getNodes())
453 node->getProcessor()->releaseResources();
454
455 preparedNodes.clear();
456 }
457
458 if (current.has_value())
459 {
460 for (const auto& node : n.getNodes())
461 {
462 if (preparedNodes.find (node->nodeID) != preparedNodes.cend())
463 continue;
464
465 preparedNodes.insert (node->nodeID);
466
467 node->getProcessor()->setProcessingPrecision (node->getProcessor()->supportsDoublePrecisionProcessing() ? current->precision
468 : AudioProcessor::singlePrecision);
469 node->getProcessor()->setRateAndBufferSizeDetails (current->sampleRate, current->blockSize);
470 node->getProcessor()->prepareToPlay (current->sampleRate, current->blockSize);
471 }
472 }
473
474 return current;
475 }
476
477 /* Call from the main thread to indicate that a node has been removed from the graph.
478 */
479 void removeNode (const NodeID n)
480 {
481 preparedNodes.erase (n);
482 }
483
484 /* Call from the main thread to indicate that all nodes have been removed from the graph.
485 */
486 void clear()
487 {
488 preparedNodes.clear();
489 }
490
491private:
492 std::mutex mutex;
493 std::set<NodeID> preparedNodes;
494 std::optional<PrepareSettings> current, next;
495};
496
497//==============================================================================
498template <typename FloatType>
500{
502
503 struct GlobalIO
504 {
505 AudioBuffer<FloatType>& audioIn;
506 AudioBuffer<FloatType>& audioOut;
507 MidiBuffer& midiIn;
508 MidiBuffer& midiOut;
509 };
510
511 struct Context
512 {
513 GlobalIO globalIO;
514 AudioPlayHead* audioPlayHead;
515 int numSamples;
516 };
517
518 void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead)
519 {
520 auto numSamples = buffer.getNumSamples();
521 auto maxSamples = renderingBuffer.getNumSamples();
522
523 if (numSamples > maxSamples)
524 {
525 // Being asked to render more samples than our buffers have, so divide the buffer into chunks
526 int chunkStartSample = 0;
527 while (chunkStartSample < numSamples)
528 {
529 auto chunkSize = jmin (maxSamples, numSamples - chunkStartSample);
530
532 midiChunk.clear();
534
535 // Splitting up the buffer like this will cause the play head and host time to be
536 // invalid for all but the first chunk...
537 perform (audioChunk, midiChunk, audioPlayHead);
538
540 }
541
542 return;
543 }
544
545 currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples);
546 currentAudioOutputBuffer.clear();
547 currentMidiOutputBuffer.clear();
548
549 {
550 const Context context { { buffer,
551 currentAudioOutputBuffer,
553 currentMidiOutputBuffer },
554 audioPlayHead,
555 numSamples };
556
557 for (const auto& op : renderOps)
558 op->process (context);
559 }
560
561 for (int i = 0; i < buffer.getNumChannels(); ++i)
562 buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples);
563
564 midiMessages.clear();
565 midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0);
566 }
567
568 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661)
569
570 void addClearChannelOp (int index)
571 {
572 struct ClearOp final : public RenderOp
573 {
574 explicit ClearOp (int indexIn) : index (indexIn) {}
575
576 void prepare (FloatType* const* renderBuffer, MidiBuffer*) override
577 {
578 channelBuffer = renderBuffer[index];
579 }
580
581 void process (const Context& c) override
582 {
583 FloatVectorOperations::clear (channelBuffer, c.numSamples);
584 }
585
586 FloatType* channelBuffer = nullptr;
587 int index = 0;
588 };
589
590 renderOps.push_back (std::make_unique<ClearOp> (index));
591 }
592
593 void addCopyChannelOp (int srcIndex, int dstIndex)
594 {
595 struct CopyOp final : public RenderOp
596 {
597 explicit CopyOp (int fromIn, int toIn) : from (fromIn), to (toIn) {}
598
599 void prepare (FloatType* const* renderBuffer, MidiBuffer*) override
600 {
601 fromBuffer = renderBuffer[from];
602 toBuffer = renderBuffer[to];
603 }
604
605 void process (const Context& c) override
606 {
607 FloatVectorOperations::copy (toBuffer, fromBuffer, c.numSamples);
608 }
609
610 FloatType* fromBuffer = nullptr;
611 FloatType* toBuffer = nullptr;
612 int from = 0, to = 0;
613 };
614
615 renderOps.push_back (std::make_unique<CopyOp> (srcIndex, dstIndex));
616 }
617
618 void addAddChannelOp (int srcIndex, int dstIndex)
619 {
620 struct AddOp final : public RenderOp
621 {
622 explicit AddOp (int fromIn, int toIn) : from (fromIn), to (toIn) {}
623
624 void prepare (FloatType* const* renderBuffer, MidiBuffer*) override
625 {
626 fromBuffer = renderBuffer[from];
627 toBuffer = renderBuffer[to];
628 }
629
630 void process (const Context& c) override
631 {
632 FloatVectorOperations::add (toBuffer, fromBuffer, c.numSamples);
633 }
634
635 FloatType* fromBuffer = nullptr;
636 FloatType* toBuffer = nullptr;
637 int from = 0, to = 0;
638 };
639
640 renderOps.push_back (std::make_unique<AddOp> (srcIndex, dstIndex));
641 }
642
643 JUCE_END_IGNORE_WARNINGS_MSVC
644
645 void addClearMidiBufferOp (int index)
646 {
647 struct ClearOp final : public RenderOp
648 {
649 explicit ClearOp (int indexIn) : index (indexIn) {}
650
651 void prepare (FloatType* const*, MidiBuffer* buffers) override
652 {
653 channelBuffer = buffers + index;
654 }
655
656 void process (const Context&) override
657 {
658 channelBuffer->clear();
659 }
660
661 MidiBuffer* channelBuffer = nullptr;
662 int index = 0;
663 };
664
665 renderOps.push_back (std::make_unique<ClearOp> (index));
666 }
667
668 void addCopyMidiBufferOp (int srcIndex, int dstIndex)
669 {
670 struct CopyOp final : public RenderOp
671 {
672 explicit CopyOp (int fromIn, int toIn) : from (fromIn), to (toIn) {}
673
674 void prepare (FloatType* const*, MidiBuffer* buffers) override
675 {
676 fromBuffer = buffers + from;
677 toBuffer = buffers + to;
678 }
679
680 void process (const Context&) override
681 {
682 *toBuffer = *fromBuffer;
683 }
684
685 MidiBuffer* fromBuffer = nullptr;
686 MidiBuffer* toBuffer = nullptr;
687 int from = 0, to = 0;
688 };
689
690 renderOps.push_back (std::make_unique<CopyOp> (srcIndex, dstIndex));
691 }
692
693 void addAddMidiBufferOp (int srcIndex, int dstIndex)
694 {
695 struct AddOp final : public RenderOp
696 {
697 explicit AddOp (int fromIn, int toIn) : from (fromIn), to (toIn) {}
698
699 void prepare (FloatType* const*, MidiBuffer* buffers) override
700 {
701 fromBuffer = buffers + from;
702 toBuffer = buffers + to;
703 }
704
705 void process (const Context& c) override
706 {
707 toBuffer->addEvents (*fromBuffer, 0, c.numSamples, 0);
708 }
709
710 MidiBuffer* fromBuffer = nullptr;
711 MidiBuffer* toBuffer = nullptr;
712 int from = 0, to = 0;
713 };
714
715 renderOps.push_back (std::make_unique<AddOp> (srcIndex, dstIndex));
716 }
717
718 void addDelayChannelOp (int chan, int delaySize)
719 {
720 struct DelayChannelOp final : public RenderOp
721 {
722 DelayChannelOp (int chan, int delaySize)
723 : buffer ((size_t) (delaySize + 1), (FloatType) 0),
724 channel (chan),
725 writeIndex (delaySize)
726 {
727 }
728
729 void prepare (FloatType* const* renderBuffer, MidiBuffer*) override
730 {
731 channelBuffer = renderBuffer[channel];
732 }
733
734 void process (const Context& c) override
735 {
736 auto* data = channelBuffer;
737
738 for (int i = c.numSamples; --i >= 0;)
739 {
740 buffer[(size_t) writeIndex] = *data;
741 *data++ = buffer[(size_t) readIndex];
742
743 if (++readIndex >= (int) buffer.size()) readIndex = 0;
744 if (++writeIndex >= (int) buffer.size()) writeIndex = 0;
745 }
746 }
747
749 FloatType* channelBuffer = nullptr;
750 const int channel;
751 int readIndex = 0, writeIndex;
752 };
753
754 renderOps.push_back (std::make_unique<DelayChannelOp> (chan, delaySize));
755 }
756
757 void addProcessOp (const Node::Ptr& node,
758 const Array<int>& audioChannelsUsed,
759 int totalNumChans,
760 int midiBuffer)
761 {
762 auto op = [&]() -> std::unique_ptr<NodeOp>
763 {
764 if (auto* ioNode = dynamic_cast<const AudioProcessorGraph::AudioGraphIOProcessor*> (node->getProcessor()))
765 {
766 switch (ioNode->getType())
767 {
769 return std::make_unique<AudioInOp> (node, audioChannelsUsed, totalNumChans, midiBuffer);
770
772 return std::make_unique<AudioOutOp> (node, audioChannelsUsed, totalNumChans, midiBuffer);
773
775 return std::make_unique<MidiInOp> (node, audioChannelsUsed, totalNumChans, midiBuffer);
776
778 return std::make_unique<MidiOutOp> (node, audioChannelsUsed, totalNumChans, midiBuffer);
779 }
780 }
781
782 return std::make_unique<ProcessOp> (node, audioChannelsUsed, totalNumChans, midiBuffer);
783 }();
784
785 renderOps.push_back (std::move (op));
786 }
787
788 void prepareBuffers (int blockSize)
789 {
790 renderingBuffer.setSize (numBuffersNeeded + 1, blockSize);
791 renderingBuffer.clear();
792 currentAudioOutputBuffer.setSize (numBuffersNeeded + 1, blockSize);
793 currentAudioOutputBuffer.clear();
794
795 currentMidiOutputBuffer.clear();
796
797 midiBuffers.clearQuick();
798 midiBuffers.resize (numMidiBuffersNeeded);
799
800 const int defaultMIDIBufferSize = 512;
801
803
804 for (auto&& m : midiBuffers)
805 m.ensureSize (defaultMIDIBufferSize);
806
807 for (const auto& op : renderOps)
808 op->prepare (renderingBuffer.getArrayOfWritePointers(), midiBuffers.data());
809 }
810
811 int numBuffersNeeded = 0, numMidiBuffersNeeded = 0;
812
813 AudioBuffer<FloatType> renderingBuffer, currentAudioOutputBuffer;
814
815 MidiBuffer currentMidiOutputBuffer;
816
817 Array<MidiBuffer> midiBuffers;
818 MidiBuffer midiChunk;
819
820private:
821 //==============================================================================
822 struct RenderOp
823 {
824 virtual ~RenderOp() = default;
825 virtual void prepare (FloatType* const*, MidiBuffer*) = 0;
826 virtual void process (const Context&) = 0;
827 };
828
829 struct NodeOp : public RenderOp
830 {
831 NodeOp (const Node::Ptr& n,
832 const Array<int>& audioChannelsUsed,
833 int totalNumChans,
834 int midiBufferIndex)
835 : node (n),
836 processor (*n->getProcessor()),
837 audioChannelsToUse (audioChannelsUsed),
838 audioChannels ((size_t) jmax (1, totalNumChans), nullptr),
839 midiBufferToUse (midiBufferIndex)
840 {
841 while (audioChannelsToUse.size() < (int) audioChannels.size())
842 audioChannelsToUse.add (0);
843 }
844
845 void prepare (FloatType* const* renderBuffer, MidiBuffer* buffers) final
846 {
847 for (size_t i = 0; i < audioChannels.size(); ++i)
848 audioChannels[i] = renderBuffer[audioChannelsToUse.getUnchecked ((int) i)];
849
850 midiBuffer = buffers + midiBufferToUse;
851 }
852
853 void process (const Context& c) final
854 {
855 processor.setPlayHead (c.audioPlayHead);
856
857 auto numAudioChannels = [this]
858 {
859 if (const auto* proc = node->getProcessor())
860 if (proc->getTotalNumInputChannels() == 0 && proc->getTotalNumOutputChannels() == 0)
861 return 0;
862
863 return (int) audioChannels.size();
864 }();
865
866 AudioBuffer<FloatType> buffer { audioChannels.data(), numAudioChannels, c.numSamples };
867
868 if (processor.isSuspended())
869 {
870 buffer.clear();
871 }
872 else
873 {
874 const auto bypass = node->isBypassed() && processor.getBypassParameter() == nullptr;
875 processWithBuffer (c.globalIO, bypass, buffer, *midiBuffer);
876 }
877 }
878
879 virtual void processWithBuffer (const GlobalIO&, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer& midi) = 0;
880
881 const Node::Ptr node;
882 AudioProcessor& processor;
883 MidiBuffer* midiBuffer = nullptr;
884
885 Array<int> audioChannelsToUse;
886 std::vector<FloatType*> audioChannels;
887 const int midiBufferToUse;
888 };
889
890 struct ProcessOp final : public NodeOp
891 {
892 using NodeOp::NodeOp;
893
894 void processWithBuffer (const GlobalIO&, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer& midi) final
895 {
896 callProcess (bypass, audio, midi);
897 }
898
899 void callProcess (bool bypass, AudioBuffer<float>& buffer, MidiBuffer& midi)
900 {
901 if (this->processor.isUsingDoublePrecision())
902 {
903 tempBufferDouble.makeCopyOf (buffer, true);
904 processImpl (bypass, this->processor, tempBufferDouble, midi);
905 buffer.makeCopyOf (tempBufferDouble, true);
906 }
907 else
908 {
909 processImpl (bypass, this->processor, buffer, midi);
910 }
911 }
912
913 void callProcess (bool bypass, AudioBuffer<double>& buffer, MidiBuffer& midi)
914 {
915 if (this->processor.isUsingDoublePrecision())
916 {
917 processImpl (bypass, this->processor, buffer, midi);
918 }
919 else
920 {
921 tempBufferFloat.makeCopyOf (buffer, true);
922 processImpl (bypass, this->processor, tempBufferFloat, midi);
923 buffer.makeCopyOf (tempBufferFloat, true);
924 }
925 }
926
927 template <typename Value>
928 static void processImpl (bool bypass, AudioProcessor& p, AudioBuffer<Value>& audio, MidiBuffer& midi)
929 {
930 if (bypass)
931 p.processBlockBypassed (audio, midi);
932 else
933 p.processBlock (audio, midi);
934 }
935
936 AudioBuffer<float> tempBufferFloat, tempBufferDouble;
937 };
938
939 struct MidiInOp final : public NodeOp
940 {
941 using NodeOp::NodeOp;
942
943 void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer& midi) final
944 {
945 if (! bypass)
946 midi.addEvents (g.midiIn, 0, audio.getNumSamples(), 0);
947 }
948 };
949
950 struct MidiOutOp final : public NodeOp
951 {
952 using NodeOp::NodeOp;
953
954 void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer& midi) final
955 {
956 if (! bypass)
957 g.midiOut.addEvents (midi, 0, audio.getNumSamples(), 0);
958 }
959 };
960
961 struct AudioInOp final : public NodeOp
962 {
963 using NodeOp::NodeOp;
964
965 void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer&) final
966 {
967 if (bypass)
968 return;
969
970 for (int i = jmin (g.audioIn.getNumChannels(), audio.getNumChannels()); --i >= 0;)
971 audio.copyFrom (i, 0, g.audioIn, i, 0, audio.getNumSamples());
972 }
973 };
974
975 struct AudioOutOp final : public NodeOp
976 {
977 using NodeOp::NodeOp;
978
979 void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer<FloatType>& audio, MidiBuffer&) final
980 {
981 if (bypass)
982 return;
983
984 for (int i = jmin (g.audioOut.getNumChannels(), audio.getNumChannels()); --i >= 0;)
985 g.audioOut.addFrom (i, 0, audio, i, 0, audio.getNumSamples());
986 }
987 };
988
990};
991
992//==============================================================================
994{
997
998 RenderSequenceVariant sequence;
999 int latencySamples = 0;
1000};
1001
1002//==============================================================================
1004{
1005public:
1010
1011 static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex;
1012
1013 template <typename FloatType>
1014 static SequenceAndLatency build (const Nodes& n, const Connections& c)
1015 {
1017 const RenderSequenceBuilder builder (n, c, sequence);
1018 return { std::move (sequence), builder.totalLatency };
1019 }
1020
1021private:
1022 //==============================================================================
1023 const Array<Node*> orderedNodes;
1024
1025 struct AssignedBuffer
1026 {
1027 NodeAndChannel channel;
1028
1029 static AssignedBuffer createReadOnlyEmpty() noexcept { return { { zeroNodeID(), 0 } }; }
1030 static AssignedBuffer createFree() noexcept { return { { freeNodeID(), 0 } }; }
1031
1032 bool isReadOnlyEmpty() const noexcept { return channel.nodeID == zeroNodeID(); }
1033 bool isFree() const noexcept { return channel.nodeID == freeNodeID(); }
1034 bool isAssigned() const noexcept { return ! (isReadOnlyEmpty() || isFree()); }
1035
1036 void setFree() noexcept { channel = { freeNodeID(), 0 }; }
1037 void setAssignedToNonExistentNode() noexcept { channel = { anonNodeID(), 0 }; }
1038
1039 private:
1040 static NodeID anonNodeID() { return NodeID (0x7ffffffd); }
1041 static NodeID zeroNodeID() { return NodeID (0x7ffffffe); }
1042 static NodeID freeNodeID() { return NodeID (0x7fffffff); }
1043 };
1044
1045 Array<AssignedBuffer> audioBuffers, midiBuffers;
1046
1047 enum { readOnlyEmptyBufferIndex = 0 };
1048
1050 int totalLatency = 0;
1051
1052 int getNodeDelay (NodeID nodeID) const noexcept
1053 {
1054 const auto iter = delays.find (nodeID.uid);
1055 return iter != delays.end() ? iter->second : 0;
1056 }
1057
1058 int getInputLatencyForNode (const Connections& c, NodeID nodeID) const
1059 {
1060 const auto sources = c.getSourceNodesForDestination (nodeID);
1061 return std::accumulate (sources.cbegin(), sources.cend(), 0, [this] (auto acc, auto source)
1062 {
1063 return jmax (acc, this->getNodeDelay (source));
1064 });
1065 }
1066
1067 //==============================================================================
1068 void getAllParentsOfNode (const NodeID& child,
1071 const Connections& c)
1072 {
1073 for (const auto& parentNode : c.getSourceNodesForDestination (child))
1074 {
1075 if (parentNode == child)
1076 continue;
1077
1078 if (parents.insert (parentNode).second)
1079 {
1080 const auto parentParents = otherParents.find (parentNode);
1081
1082 if (parentParents != otherParents.end())
1083 {
1084 parents.insert (parentParents->second.begin(), parentParents->second.end());
1085 continue;
1086 }
1087
1088 getAllParentsOfNode (parentNode, parents, otherParents, c);
1089 }
1090 }
1091 }
1092
1093 Array<Node*> createOrderedNodeList (const Nodes& n, const Connections& c)
1094 {
1095 Array<Node*> result;
1096
1098
1099 for (auto& node : n.getNodes())
1100 {
1101 const auto nodeID = node->nodeID;
1102 int insertionIndex = 0;
1103
1104 for (; insertionIndex < result.size(); ++insertionIndex)
1105 {
1106 auto& parents = nodeParents[result.getUnchecked (insertionIndex)->nodeID];
1107
1108 if (parents.find (nodeID) != parents.end())
1109 break;
1110 }
1111
1112 result.insert (insertionIndex, node);
1113 getAllParentsOfNode (nodeID, nodeParents[node->nodeID], nodeParents, c);
1114 }
1115
1116 return result;
1117 }
1118
1119 //==============================================================================
1120 template <typename RenderSequence>
1121 int findBufferForInputAudioChannel (const Connections& c,
1123 RenderSequence& sequence,
1124 Node& node,
1125 const int inputChan,
1126 const int ourRenderingIndex,
1127 const int maxLatency)
1128 {
1129 auto& processor = *node.getProcessor();
1130 auto numOuts = processor.getTotalNumOutputChannels();
1131
1132 auto sources = c.getSourcesForDestination ({ node.nodeID, inputChan });
1133
1134 // Handle an unconnected input channel...
1135 if (sources.empty())
1136 {
1137 if (inputChan >= numOuts)
1138 return readOnlyEmptyBufferIndex;
1139
1140 auto index = getFreeBuffer (audioBuffers);
1141 sequence.addClearChannelOp (index);
1142 return index;
1143 }
1144
1145 // Handle an input from a single source..
1146 if (sources.size() == 1)
1147 {
1148 // channel with a straightforward single input..
1149 auto src = *sources.begin();
1150
1151 int bufIndex = getBufferContaining (src);
1152
1153 if (bufIndex < 0)
1154 {
1155 // if not found, this is probably a feedback loop
1156 bufIndex = readOnlyEmptyBufferIndex;
1157 jassert (bufIndex >= 0);
1158 }
1159
1160 if (inputChan < numOuts && isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src))
1161 {
1162 // can't mess up this channel because it's needed later by another node,
1163 // so we need to use a copy of it..
1164 auto newFreeBuffer = getFreeBuffer (audioBuffers);
1165 sequence.addCopyChannelOp (bufIndex, newFreeBuffer);
1167 }
1168
1169 auto nodeDelay = getNodeDelay (src.nodeID);
1170
1171 if (nodeDelay < maxLatency)
1172 sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay);
1173
1174 return bufIndex;
1175 }
1176
1177 // Handle a mix of several outputs coming into this input..
1178 int reusableInputIndex = -1;
1179 int bufIndex = -1;
1180
1181 {
1182 auto i = 0;
1183 for (const auto& src : sources)
1184 {
1185 auto sourceBufIndex = getBufferContaining (src);
1186
1187 if (sourceBufIndex >= 0 && ! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src))
1188 {
1189 // we've found one of our input chans that can be re-used..
1192
1193 auto nodeDelay = getNodeDelay (src.nodeID);
1194
1195 if (nodeDelay < maxLatency)
1196 sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay);
1197
1198 break;
1199 }
1200
1201 ++i;
1202 }
1203 }
1204
1205 if (reusableInputIndex < 0)
1206 {
1207 // can't re-use any of our input chans, so get a new one and copy everything into it..
1208 bufIndex = getFreeBuffer (audioBuffers);
1209 jassert (bufIndex != 0);
1210
1211 audioBuffers.getReference (bufIndex).setAssignedToNonExistentNode();
1212
1213 auto srcIndex = getBufferContaining (*sources.begin());
1214
1215 if (srcIndex < 0)
1216 sequence.addClearChannelOp (bufIndex); // if not found, this is probably a feedback loop
1217 else
1218 sequence.addCopyChannelOp (srcIndex, bufIndex);
1219
1221 auto nodeDelay = getNodeDelay (sources.begin()->nodeID);
1222
1223 if (nodeDelay < maxLatency)
1224 sequence.addDelayChannelOp (bufIndex, maxLatency - nodeDelay);
1225 }
1226
1227 {
1228 auto i = 0;
1229 for (const auto& src : sources)
1230 {
1231 if (i != reusableInputIndex)
1232 {
1233 int srcIndex = getBufferContaining (src);
1234
1235 if (srcIndex >= 0)
1236 {
1237 auto nodeDelay = getNodeDelay (src.nodeID);
1238
1239 if (nodeDelay < maxLatency)
1240 {
1241 if (! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src))
1242 {
1243 sequence.addDelayChannelOp (srcIndex, maxLatency - nodeDelay);
1244 }
1245 else // buffer is reused elsewhere, can't be delayed
1246 {
1247 auto bufferToDelay = getFreeBuffer (audioBuffers);
1248 sequence.addCopyChannelOp (srcIndex, bufferToDelay);
1249 sequence.addDelayChannelOp (bufferToDelay, maxLatency - nodeDelay);
1251 }
1252 }
1253
1254 sequence.addAddChannelOp (srcIndex, bufIndex);
1255 }
1256 }
1257
1258 ++i;
1259 }
1260 }
1261
1262 return bufIndex;
1263 }
1264
1265 template <typename RenderSequence>
1266 int findBufferForInputMidiChannel (const Connections& c,
1268 RenderSequence& sequence,
1269 Node& node,
1271 {
1272 auto& processor = *node.getProcessor();
1273 auto sources = c.getSourcesForDestination ({ node.nodeID, midiChannelIndex });
1274
1275 // No midi inputs..
1276 if (sources.empty())
1277 {
1278 auto midiBufferToUse = getFreeBuffer (midiBuffers); // need to pick a buffer even if the processor doesn't use midi
1279
1280 if (processor.acceptsMidi() || processor.producesMidi())
1281 sequence.addClearMidiBufferOp (midiBufferToUse);
1282
1283 return midiBufferToUse;
1284 }
1285
1286 // One midi input..
1287 if (sources.size() == 1)
1288 {
1289 auto src = *sources.begin();
1290 auto midiBufferToUse = getBufferContaining (src);
1291
1292 if (midiBufferToUse >= 0)
1293 {
1294 if (isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src))
1295 {
1296 // can't mess up this channel because it's needed later by another node, so we
1297 // need to use a copy of it..
1298 auto newFreeBuffer = getFreeBuffer (midiBuffers);
1299 sequence.addCopyMidiBufferOp (midiBufferToUse, newFreeBuffer);
1300 midiBufferToUse = newFreeBuffer;
1301 }
1302 }
1303 else
1304 {
1305 // probably a feedback loop, so just use an empty one..
1306 midiBufferToUse = getFreeBuffer (midiBuffers); // need to pick a buffer even if the processor doesn't use midi
1307 }
1308
1309 return midiBufferToUse;
1310 }
1311
1312 // Multiple midi inputs..
1313 int midiBufferToUse = -1;
1314 int reusableInputIndex = -1;
1315
1316 {
1317 auto i = 0;
1318 for (const auto& src : sources)
1319 {
1320 auto sourceBufIndex = getBufferContaining (src);
1321
1322 if (sourceBufIndex >= 0
1323 && ! isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src))
1324 {
1325 // we've found one of our input buffers that can be re-used..
1327 midiBufferToUse = sourceBufIndex;
1328 break;
1329 }
1330
1331 ++i;
1332 }
1333 }
1334
1335 if (reusableInputIndex < 0)
1336 {
1337 // can't re-use any of our input buffers, so get a new one and copy everything into it..
1338 midiBufferToUse = getFreeBuffer (midiBuffers);
1339 jassert (midiBufferToUse >= 0);
1340
1341 auto srcIndex = getBufferContaining (*sources.begin());
1342
1343 if (srcIndex >= 0)
1344 sequence.addCopyMidiBufferOp (srcIndex, midiBufferToUse);
1345 else
1346 sequence.addClearMidiBufferOp (midiBufferToUse);
1347
1349 }
1350
1351 {
1352 auto i = 0;
1353 for (const auto& src : sources)
1354 {
1355 if (i != reusableInputIndex)
1356 {
1357 auto srcIndex = getBufferContaining (src);
1358
1359 if (srcIndex >= 0)
1360 sequence.addAddMidiBufferOp (srcIndex, midiBufferToUse);
1361 }
1362
1363 ++i;
1364 }
1365 }
1366
1367 return midiBufferToUse;
1368 }
1369
1370 template <typename RenderSequence>
1371 void createRenderingOpsForNode (const Connections& c,
1373 RenderSequence& sequence,
1374 Node& node,
1375 const int ourRenderingIndex)
1376 {
1377 auto& processor = *node.getProcessor();
1378 auto numIns = processor.getTotalNumInputChannels();
1379 auto numOuts = processor.getTotalNumOutputChannels();
1380 auto totalChans = jmax (numIns, numOuts);
1381
1382 Array<int> audioChannelsToUse;
1383 const auto maxInputLatency = getInputLatencyForNode (c, node.nodeID);
1384
1385 for (int inputChan = 0; inputChan < numIns; ++inputChan)
1386 {
1387 // get a list of all the inputs to this node
1388 auto index = findBufferForInputAudioChannel (c,
1389 reversed,
1390 sequence,
1391 node,
1392 inputChan,
1395 jassert (index >= 0);
1396
1397 audioChannelsToUse.add (index);
1398
1399 if (inputChan < numOuts)
1400 audioBuffers.getReference (index).channel = { node.nodeID, inputChan };
1401 }
1402
1404 {
1405 auto index = getFreeBuffer (audioBuffers);
1406 jassert (index != 0);
1407 audioChannelsToUse.add (index);
1408
1409 audioBuffers.getReference (index).channel = { node.nodeID, outputChan };
1410 }
1411
1412 auto midiBufferToUse = findBufferForInputMidiChannel (c, reversed, sequence, node, ourRenderingIndex);
1413
1414 if (processor.producesMidi())
1415 midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, midiChannelIndex };
1416
1417 const auto thisNodeLatency = maxInputLatency + processor.getLatencySamples();
1418 delays[node.nodeID.uid] = thisNodeLatency;
1419
1420 if (numOuts == 0)
1421 totalLatency = jmax (totalLatency, thisNodeLatency);
1422
1423 sequence.addProcessOp (node, audioChannelsToUse, totalChans, midiBufferToUse);
1424 }
1425
1426 //==============================================================================
1427 static int getFreeBuffer (Array<AssignedBuffer>& buffers)
1428 {
1429 for (int i = 1; i < buffers.size(); ++i)
1430 if (buffers.getReference (i).isFree())
1431 return i;
1432
1433 buffers.add (AssignedBuffer::createFree());
1434 return buffers.size() - 1;
1435 }
1436
1437 int getBufferContaining (NodeAndChannel output) const noexcept
1438 {
1439 int i = 0;
1440
1441 for (auto& b : output.isMIDI() ? midiBuffers : audioBuffers)
1442 {
1443 if (b.channel == output)
1444 return i;
1445
1446 ++i;
1447 }
1448
1449 return -1;
1450 }
1451
1452 void markAnyUnusedBuffersAsFree (const Connections::DestinationsForSources& c,
1454 const int stepIndex)
1455 {
1456 for (auto& b : buffers)
1457 if (b.isAssigned() && ! isBufferNeededLater (c, stepIndex, -1, b.channel))
1458 b.setFree();
1459 }
1460
1461 bool isBufferNeededLater (const Connections::DestinationsForSources& c,
1462 const int stepIndexToSearchFrom,
1464 const NodeAndChannel output) const
1465 {
1466 if (orderedNodes.size() <= stepIndexToSearchFrom)
1467 return false;
1468
1469 if (c.isSourceConnectedToDestinationNodeIgnoringChannel (output,
1470 orderedNodes.getUnchecked (stepIndexToSearchFrom)->nodeID,
1472 {
1473 return true;
1474 }
1475
1476 return std::any_of (orderedNodes.begin() + stepIndexToSearchFrom + 1, orderedNodes.end(), [&] (const auto* node)
1477 {
1478 return c.isSourceConnectedToDestinationNodeIgnoringChannel (output, node->nodeID, -1);
1479 });
1480 }
1481
1482 template <typename RenderSequence>
1483 RenderSequenceBuilder (const Nodes& n, const Connections& c, RenderSequence& sequence)
1484 : orderedNodes (createOrderedNodeList (n, c))
1485 {
1486 audioBuffers.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros
1487 midiBuffers .add (AssignedBuffer::createReadOnlyEmpty());
1488
1489 const auto reversed = c.getDestinationsForSources();
1490
1491 for (int i = 0; i < orderedNodes.size(); ++i)
1492 {
1493 createRenderingOpsForNode (c, reversed, sequence, *orderedNodes.getUnchecked (i), i);
1494 markAnyUnusedBuffersAsFree (reversed, audioBuffers, i);
1495 markAnyUnusedBuffersAsFree (reversed, midiBuffers, i);
1496 }
1497
1498 sequence.numBuffersNeeded = audioBuffers.size();
1499 sequence.numMidiBuffersNeeded = midiBuffers.size();
1500 }
1501};
1502
1503//==============================================================================
1504/* A full graph of audio processors, ready to process at a particular sample rate, block size,
1505 and precision.
1506
1507 Instances of this class will be created on the main thread, and then passed over to the audio
1508 thread for processing.
1509*/
1511{
1512public:
1514
1515 RenderSequence (const PrepareSettings s, const Nodes& n, const Connections& c)
1516 : RenderSequence (s, s.precision == AudioProcessor::ProcessingPrecision::singlePrecision
1517 ? RenderSequenceBuilder::build<float> (n, c)
1518 : RenderSequenceBuilder::build<double> (n, c))
1519 {
1520 }
1521
1522 template <typename FloatType>
1523 void process (AudioBuffer<FloatType>& audio, MidiBuffer& midi, AudioPlayHead* playHead)
1524 {
1525 if (auto* s = std::get_if<GraphRenderSequence<FloatType>> (&sequence.sequence))
1526 s->perform (audio, midi, playHead);
1527 else
1528 jassertfalse; // Not prepared for this audio format!
1529 }
1530
1531 int getLatencySamples() const { return sequence.latencySamples; }
1532 PrepareSettings getSettings() const { return settings; }
1533
1534private:
1535 template <typename This, typename Callback>
1536 static void visitRenderSequence (This& t, Callback&& callback)
1537 {
1538 if (auto* sequence = std::get_if<GraphRenderSequence<float>> (&t.sequence.sequence)) return callback (*sequence);
1539 if (auto* sequence = std::get_if<GraphRenderSequence<double>> (&t.sequence.sequence)) return callback (*sequence);
1541 }
1542
1544 : settings (s), sequence (std::move (built))
1545 {
1546 visitRenderSequence (*this, [&] (auto& seq) { seq.prepareBuffers (settings.blockSize); });
1547 }
1548
1549 PrepareSettings settings;
1550 SequenceAndLatency sequence;
1551};
1552
1553//==============================================================================
1554/* Holds information about the properties of a graph node at the point it was prepared.
1555
1556 If the bus layout or latency of a given node changes, the graph should be rebuilt so
1557 that channel connections are ordered correctly, and the graph's internal delay lines have
1558 the correct delay.
1559*/
1561{
1562 auto tie() const { return std::tie (layout, latencySamples); }
1563
1564public:
1566 int latencySamples = 0;
1567
1568 bool operator== (const NodeAttributes& other) const { return tie() == other.tie(); }
1569 bool operator!= (const NodeAttributes& other) const { return tie() != other.tie(); }
1570};
1571
1572//==============================================================================
1573/* Holds information about a particular graph configuration, without sharing ownership of any
1574 graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see
1575 whether two graph configurations match.
1576*/
1578{
1579 auto tie() const { return std::tie (settings, connections, nodes); }
1580
1581public:
1582 RenderSequenceSignature (const PrepareSettings s, const Nodes& n, const Connections& c)
1583 : settings (s), connections (c), nodes (getNodeMap (n)) {}
1584
1585 bool operator== (const RenderSequenceSignature& other) const { return tie() == other.tie(); }
1586 bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); }
1587
1588private:
1590
1591 static NodeMap getNodeMap (const Nodes& n)
1592 {
1593 const auto& nodeRefs = n.getNodes();
1594 NodeMap result;
1595
1596 for (const auto& node : nodeRefs)
1597 {
1598 auto* proc = node->getProcessor();
1599 result.emplace (node->nodeID,
1600 NodeAttributes { proc->getBusesLayout(),
1601 proc->getLatencySamples() });
1602 }
1603
1604 return result;
1605 }
1606
1607 PrepareSettings settings;
1608 Connections connections;
1609 NodeMap nodes;
1610};
1611
1612//==============================================================================
1613/* Facilitates wait-free render-sequence updates.
1614
1615 Topology updates always happen on the main thread (or synchronised with the main thread).
1616 After updating the graph, the 'baked' graph is passed to RenderSequenceExchange::set.
1617 At the top of the audio callback, RenderSequenceExchange::updateAudioThreadState will
1618 attempt to install the most-recently-baked graph, if there's one waiting.
1619*/
1620class RenderSequenceExchange final : private Timer
1621{
1622public:
1624 {
1625 startTimer (500);
1626 }
1627
1628 ~RenderSequenceExchange() override
1629 {
1630 stopTimer();
1631 }
1632
1633 void set (std::unique_ptr<RenderSequence>&& next)
1634 {
1635 const SpinLock::ScopedLockType lock (mutex);
1636 mainThreadState = std::move (next);
1637 isNew = true;
1638 }
1639
1640 /* Call from the audio thread only. */
1641 void updateAudioThreadState()
1642 {
1643 const SpinLock::ScopedTryLockType lock (mutex);
1644
1645 if (lock.isLocked() && isNew)
1646 {
1647 // Swap pointers rather than assigning to avoid calling delete here
1648 std::swap (mainThreadState, audioThreadState);
1649 isNew = false;
1650 }
1651 }
1652
1653 /* Call from the audio thread only. */
1654 RenderSequence* getAudioThreadState() const { return audioThreadState.get(); }
1655
1656private:
1657 void timerCallback() override
1658 {
1659 const SpinLock::ScopedLockType lock (mutex);
1660
1661 if (! isNew)
1662 mainThreadState.reset();
1663 }
1664
1665 SpinLock mutex;
1666 std::unique_ptr<RenderSequence> mainThreadState, audioThreadState;
1667 bool isNew = false;
1668};
1669
1670//==============================================================================
1671AudioProcessorGraph::Connection::Connection (NodeAndChannel src, NodeAndChannel dst) noexcept
1672 : source (src), destination (dst)
1673{
1674}
1675
1676bool AudioProcessorGraph::Connection::operator== (const Connection& other) const noexcept
1677{
1678 return source == other.source && destination == other.destination;
1679}
1680
1681bool AudioProcessorGraph::Connection::operator!= (const Connection& c) const noexcept
1682{
1683 return ! operator== (c);
1684}
1685
1686bool AudioProcessorGraph::Connection::operator< (const Connection& other) const noexcept
1687{
1688 const auto tie = [] (auto& x)
1689 {
1690 return std::tie (x.source.nodeID,
1691 x.destination.nodeID,
1692 x.source.channelIndex,
1693 x.destination.channelIndex);
1694 };
1695 return tie (*this) < tie (other);
1696}
1697
1698//==============================================================================
1700{
1701public:
1702 explicit Pimpl (AudioProcessorGraph& o) : owner (&o) {}
1703
1704 const auto& getNodes() const { return nodes.getNodes(); }
1705
1706 void clear (UpdateKind updateKind)
1707 {
1708 if (getNodes().isEmpty())
1709 return;
1710
1711 nodes = Nodes{};
1712 connections = Connections{};
1713 nodeStates.clear();
1714 topologyChanged (updateKind);
1715 }
1716
1717 auto getNodeForId (NodeID nodeID) const
1718 {
1719 return nodes.getNodeForId (nodeID);
1720 }
1721
1722 Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor,
1723 std::optional<NodeID> nodeID,
1724 UpdateKind updateKind)
1725 {
1726 if (newProcessor.get() == owner)
1727 {
1729 return nullptr;
1730 }
1731
1732 const auto idToUse = nodeID.value_or (NodeID { lastNodeID.uid + 1 });
1733
1734 auto added = nodes.addNode (std::move (newProcessor), idToUse);
1735
1736 if (added == nullptr)
1737 return nullptr;
1738
1739 if (lastNodeID < idToUse)
1740 lastNodeID = idToUse;
1741
1742 setParentGraph (added->getProcessor());
1743
1744 topologyChanged (updateKind);
1745 return added;
1746 }
1747
1748 Node::Ptr removeNode (NodeID nodeID, UpdateKind updateKind)
1749 {
1750 connections.disconnectNode (nodeID);
1751 auto result = nodes.removeNode (nodeID);
1752 nodeStates.removeNode (nodeID);
1753 topologyChanged (updateKind);
1754 return result;
1755 }
1756
1757 std::vector<Connection> getConnections() const
1758 {
1759 return connections.getConnections();
1760 }
1761
1762 bool isConnected (const Connection& c) const
1763 {
1764 return connections.isConnected (c);
1765 }
1766
1767 bool isConnected (NodeID srcID, NodeID destID) const
1768 {
1769 return connections.isConnected (srcID, destID);
1770 }
1771
1772 bool isAnInputTo (const Node& src, const Node& dst) const
1773 {
1774 return isAnInputTo (src.nodeID, dst.nodeID);
1775 }
1776
1777 bool isAnInputTo (NodeID src, NodeID dst) const
1778 {
1779 return connections.isAnInputTo (src, dst);
1780 }
1781
1782 bool canConnect (const Connection& c) const
1783 {
1784 return connections.canConnect (nodes, c);
1785 }
1786
1787 bool addConnection (const Connection& c, UpdateKind updateKind)
1788 {
1789 if (! connections.addConnection (nodes, c))
1790 return false;
1791
1792 jassert (isConnected (c));
1793 topologyChanged (updateKind);
1794 return true;
1795 }
1796
1797 bool removeConnection (const Connection& c, UpdateKind updateKind)
1798 {
1799 if (! connections.removeConnection (c))
1800 return false;
1801
1802 topologyChanged (updateKind);
1803 return true;
1804 }
1805
1806 bool disconnectNode (NodeID nodeID, UpdateKind updateKind)
1807 {
1808 if (! connections.disconnectNode (nodeID))
1809 return false;
1810
1811 topologyChanged (updateKind);
1812 return true;
1813 }
1814
1815 bool isConnectionLegal (const Connection& c) const
1816 {
1817 return connections.isConnectionLegal (nodes, c);
1818 }
1819
1820 bool removeIllegalConnections (UpdateKind updateKind)
1821 {
1822 const auto result = connections.removeIllegalConnections (nodes);
1823 topologyChanged (updateKind);
1824 return result;
1825 }
1826
1827 //==============================================================================
1828 void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock)
1829 {
1830 owner->setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock);
1831
1832 PrepareSettings settings;
1833 settings.precision = owner->getProcessingPrecision();
1834 settings.sampleRate = sampleRate;
1835 settings.blockSize = estimatedSamplesPerBlock;
1836
1837 nodeStates.setState (settings);
1838
1839 topologyChanged (UpdateKind::sync);
1840 }
1841
1842 void releaseResources()
1843 {
1844 nodeStates.setState (nullopt);
1845 topologyChanged (UpdateKind::sync);
1846 }
1847
1848 void rebuild (UpdateKind updateKind)
1849 {
1850 if (updateKind == UpdateKind::none)
1851 return;
1852
1853 if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
1854 handleAsyncUpdate();
1855 else
1856 updater.triggerAsyncUpdate();
1857 }
1858
1859 void reset()
1860 {
1861 for (auto* n : getNodes())
1862 n->getProcessor()->reset();
1863 }
1864
1865 void setNonRealtime (bool isProcessingNonRealtime)
1866 {
1867 for (auto* n : getNodes())
1868 n->getProcessor()->setNonRealtime (isProcessingNonRealtime);
1869 }
1870
1871 template <typename Value>
1872 void processBlock (AudioBuffer<Value>& audio, MidiBuffer& midi, AudioPlayHead* playHead)
1873 {
1874 renderSequenceExchange.updateAudioThreadState();
1875
1876 if (renderSequenceExchange.getAudioThreadState() == nullptr && MessageManager::getInstance()->isThisTheMessageThread())
1877 handleAsyncUpdate();
1878
1879 if (owner->isNonRealtime())
1880 {
1881 while (renderSequenceExchange.getAudioThreadState() == nullptr)
1882 {
1883 Thread::sleep (1);
1884 renderSequenceExchange.updateAudioThreadState();
1885 }
1886 }
1887
1888 auto* state = renderSequenceExchange.getAudioThreadState();
1889
1890 // Only process if the graph has the correct blockSize, sampleRate etc.
1891 if (state != nullptr && state->getSettings() == nodeStates.getLastRequestedSettings())
1892 {
1893 state->process (audio, midi, playHead);
1894 }
1895 else
1896 {
1897 audio.clear();
1898 midi.clear();
1899 }
1900 }
1901
1902 /* Call from the audio thread only. */
1903 auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); }
1904
1905private:
1906 void setParentGraph (AudioProcessor* p) const
1907 {
1908 if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (p))
1909 ioProc->setParentGraph (owner);
1910 }
1911
1912 void topologyChanged (UpdateKind updateKind)
1913 {
1914 owner->sendChangeMessage();
1915 rebuild (updateKind);
1916 }
1917
1918 void handleAsyncUpdate()
1919 {
1920 if (const auto newSettings = nodeStates.applySettings (nodes))
1921 {
1922 for (const auto node : nodes.getNodes())
1923 setParentGraph (node->getProcessor());
1924
1925 const RenderSequenceSignature newSignature (*newSettings, nodes, connections);
1926
1927 if (std::exchange (lastBuiltSequence, newSignature) != newSignature)
1928 {
1929 auto sequence = std::make_unique<RenderSequence> (*newSettings, nodes, connections);
1930 owner->setLatencySamples (sequence->getLatencySamples());
1931 renderSequenceExchange.set (std::move (sequence));
1932 }
1933 }
1934 else
1935 {
1936 lastBuiltSequence.reset();
1937 renderSequenceExchange.set (nullptr);
1938 }
1939 }
1940
1941 AudioProcessorGraph* owner = nullptr;
1942 Nodes nodes;
1943 Connections connections;
1944 NodeStates nodeStates;
1945 RenderSequenceExchange renderSequenceExchange;
1946 NodeID lastNodeID;
1948 LockingAsyncUpdater updater { [this] { handleAsyncUpdate(); } };
1949};
1950
1951//==============================================================================
1952AudioProcessorGraph::AudioProcessorGraph() : pimpl (std::make_unique<Pimpl> (*this)) {}
1954
1955const String AudioProcessorGraph::getName() const { return "Audio Graph"; }
1958bool AudioProcessorGraph::acceptsMidi() const { return true; }
1959bool AudioProcessorGraph::producesMidi() const { return true; }
1962
1966bool AudioProcessorGraph::addConnection (const Connection& c, UpdateKind updateKind) { return pimpl->addConnection (c, updateKind); }
1967bool AudioProcessorGraph::removeConnection (const Connection& c, UpdateKind updateKind) { return pimpl->removeConnection (c, updateKind); }
1968void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) { return pimpl->prepareToPlay (sampleRate, estimatedSamplesPerBlock); }
1971AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID x) const { return pimpl->getNodeForId (x).get(); }
1972bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind) { return pimpl->disconnectNode (nodeID, updateKind); }
1973void AudioProcessorGraph::releaseResources() { return pimpl->releaseResources(); }
1974bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind) { return pimpl->removeIllegalConnections (updateKind); }
1975void AudioProcessorGraph::rebuild() { return pimpl->rebuild (UpdateKind::sync); }
1976void AudioProcessorGraph::reset() { return pimpl->reset(); }
1977bool AudioProcessorGraph::canConnect (const Connection& c) const { return pimpl->canConnect (c); }
1978bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { return pimpl->isConnected (c); }
1979bool AudioProcessorGraph::isConnected (NodeID a, NodeID b) const noexcept { return pimpl->isConnected (a, b); }
1980bool AudioProcessorGraph::isConnectionLegal (const Connection& c) const { return pimpl->isConnectionLegal (c); }
1981bool AudioProcessorGraph::isAnInputTo (const Node& source, const Node& destination) const noexcept { return pimpl->isAnInputTo (source, destination); }
1982bool AudioProcessorGraph::isAnInputTo (NodeID source, NodeID destination) const noexcept { return pimpl->isAnInputTo (source, destination); }
1983
1990
1996
1998{
1999 return pimpl->removeNode (nodeID, updateKind);
2000}
2001
2003{
2004 if (node != nullptr)
2005 return removeNode (node->nodeID, updateKind);
2006
2008 return {};
2009}
2010
2011//==============================================================================
2012AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType)
2013 : type (deviceType)
2014{
2015}
2016
2017AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() = default;
2018
2020{
2021 switch (type)
2022 {
2023 case audioOutputNode: return "Audio Output";
2024 case audioInputNode: return "Audio Input";
2025 case midiOutputNode: return "MIDI Output";
2026 case midiInputNode: return "MIDI Input";
2027 default: break;
2028 }
2029
2030 return {};
2031}
2032
2034{
2035 d.name = getName();
2036 d.category = "I/O devices";
2037 d.pluginFormatName = "Internal";
2038 d.manufacturerName = "JUCE";
2039 d.version = "1.0";
2040 d.isInstrument = false;
2041
2042 d.deprecatedUid = d.uniqueId = d.name.hashCode();
2043
2045
2046 if (type == audioOutputNode && graph != nullptr)
2047 d.numInputChannels = graph->getTotalNumInputChannels();
2048
2050
2051 if (type == audioInputNode && graph != nullptr)
2052 d.numOutputChannels = graph->getTotalNumOutputChannels();
2053}
2054
2056{
2057 jassert (graph != nullptr);
2058}
2059
2063
2068
2070{
2071 // The graph should never call this!
2073}
2074
2076{
2077 // The graph should never call this!
2079}
2080
2085
2087{
2088 return type == midiOutputNode;
2089}
2090
2092{
2093 return type == midiInputNode;
2094}
2095
2096bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; }
2097bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; }
2098
2101
2105
2108
2111
2112void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph)
2113{
2114 graph = newGraph;
2115
2116 if (graph == nullptr)
2117 return;
2118
2119 setPlayConfigDetails (type == audioOutputNode ? newGraph->getTotalNumOutputChannels() : 0,
2120 type == audioInputNode ? newGraph->getTotalNumInputChannels() : 0,
2121 getSampleRate(),
2122 getBlockSize());
2123
2125}
2126
2127//==============================================================================
2128//==============================================================================
2129#if JUCE_UNIT_TESTS
2130
2132{
2133public:
2134 AudioProcessorGraphTests()
2135 : UnitTest ("AudioProcessorGraph", UnitTestCategories::audioProcessors) {}
2136
2137 void runTest() override
2138 {
2139 const auto midiChannel = AudioProcessorGraph::midiChannelIndex;
2140
2141 beginTest ("isConnected returns true when two nodes are connected");
2142 {
2143 AudioProcessorGraph graph;
2144 const auto nodeA = graph.addNode (BasicProcessor::make ({}, MidiIn::no, MidiOut::yes))->nodeID;
2145 const auto nodeB = graph.addNode (BasicProcessor::make ({}, MidiIn::yes, MidiOut::no))->nodeID;
2146
2147 expect (graph.canConnect ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2148 expect (! graph.canConnect ({ { nodeB, midiChannel }, { nodeA, midiChannel } }));
2149 expect (! graph.canConnect ({ { nodeA, midiChannel }, { nodeA, midiChannel } }));
2150 expect (! graph.canConnect ({ { nodeB, midiChannel }, { nodeB, midiChannel } }));
2151
2152 expect (graph.getConnections().empty());
2153 expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2154 expect (! graph.isConnected (nodeA, nodeB));
2155
2156 expect (graph.addConnection ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2157
2158 expect (graph.getConnections().size() == 1);
2159 expect (graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2160 expect (graph.isConnected (nodeA, nodeB));
2161
2162 expect (graph.disconnectNode (nodeA));
2163
2164 expect (graph.getConnections().empty());
2165 expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2166 expect (! graph.isConnected (nodeA, nodeB));
2167 }
2168
2169 beginTest ("graph lookups work with a large number of connections");
2170 {
2171 AudioProcessorGraph graph;
2172
2174
2175 constexpr auto numNodes = 100;
2176
2177 for (auto i = 0; i < numNodes; ++i)
2178 {
2179 nodeIDs.push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(),
2180 MidiIn::yes,
2181 MidiOut::yes))->nodeID);
2182 }
2183
2184 for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it)
2185 {
2186 expect (graph.addConnection ({ { it[0], 0 }, { it[1], 0 } }));
2187 expect (graph.addConnection ({ { it[0], 1 }, { it[1], 1 } }));
2188 }
2189
2190 // Check whether isConnected reports correct results when called
2191 // with both connections and nodes
2192 for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it)
2193 {
2194 expect (graph.isConnected ({ { it[0], 0 }, { it[1], 0 } }));
2195 expect (graph.isConnected ({ { it[0], 1 }, { it[1], 1 } }));
2196 expect (graph.isConnected (it[0], it[1]));
2197 }
2198
2199 const auto& nodes = graph.getNodes();
2200
2201 expect (! graph.isAnInputTo (*nodes[0], *nodes[0]));
2202
2203 // Check whether isAnInputTo behaves correctly for a non-cyclic graph
2204 for (auto it = std::next (nodes.begin()); it != std::prev (nodes.end()); ++it)
2205 {
2206 expect (! graph.isAnInputTo (**it, **it));
2207
2208 expect (graph.isAnInputTo (*nodes[0], **it));
2209 expect (! graph.isAnInputTo (**it, *nodes[0]));
2210
2211 expect (graph.isAnInputTo (**it, *nodes[nodes.size() - 1]));
2212 expect (! graph.isAnInputTo (*nodes[nodes.size() - 1], **it));
2213 }
2214
2215 // Make the graph cyclic
2216 graph.addConnection ({ { nodeIDs.back(), 0 }, { nodeIDs.front(), 0 } });
2217 graph.addConnection ({ { nodeIDs.back(), 1 }, { nodeIDs.front(), 1 } });
2218
2219 // Check whether isAnInputTo behaves correctly for a cyclic graph
2220 for (const auto* node : graph.getNodes())
2221 {
2222 expect (graph.isAnInputTo (*node, *node));
2223
2224 expect (graph.isAnInputTo (*nodes[0], *node));
2225 expect (graph.isAnInputTo (*node, *nodes[0]));
2226
2227 expect (graph.isAnInputTo (*node, *nodes[nodes.size() - 1]));
2228 expect (graph.isAnInputTo (*nodes[nodes.size() - 1], *node));
2229 }
2230 }
2231
2232 beginTest ("rebuilding the graph recalculates overall latency");
2233 {
2234 AudioProcessorGraph graph;
2235
2236 const auto nodeA = graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(), MidiIn::no, MidiOut::no))->nodeID;
2237 const auto nodeB = graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(), MidiIn::no, MidiOut::no))->nodeID;
2238 const auto final = graph.addNode (BasicProcessor::make (BasicProcessor::getInputOnlyProperties(), MidiIn::no, MidiOut::no))->nodeID;
2239
2240 expect (graph.addConnection ({ { nodeA, 0 }, { nodeB, 0 } }));
2241 expect (graph.addConnection ({ { nodeA, 1 }, { nodeB, 1 } }));
2242 expect (graph.addConnection ({ { nodeB, 0 }, { final, 0 } }));
2243 expect (graph.addConnection ({ { nodeB, 1 }, { final, 1 } }));
2244
2245 expect (graph.getLatencySamples() == 0);
2246
2247 // Graph isn't built, latency is 0 if prepareToPlay hasn't been called yet
2248 const auto nodeALatency = 100;
2249 graph.getNodeForId (nodeA)->getProcessor()->setLatencySamples (nodeALatency);
2250 graph.rebuild();
2251 expect (graph.getLatencySamples() == 0);
2252
2253 graph.prepareToPlay (44100, 512);
2254
2255 expect (graph.getLatencySamples() == nodeALatency);
2256
2257 const auto nodeBLatency = 200;
2258 graph.getNodeForId (nodeB)->getProcessor()->setLatencySamples (nodeBLatency);
2259 graph.rebuild();
2260 expect (graph.getLatencySamples() == nodeALatency + nodeBLatency);
2261
2262 const auto finalLatency = 300;
2263 graph.getNodeForId (final)->getProcessor()->setLatencySamples (finalLatency);
2264 graph.rebuild();
2265 expect (graph.getLatencySamples() == nodeALatency + nodeBLatency + finalLatency);
2266 }
2267
2268 beginTest ("large render sequence can be built");
2269 {
2270 AudioProcessorGraph graph;
2271
2273
2274 constexpr auto numNodes = 1000;
2275 constexpr auto numChannels = 100;
2276
2277 for (auto i = 0; i < numNodes; ++i)
2278 {
2279 nodeIDs.push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getMultichannelProperties (numChannels),
2280 MidiIn::yes,
2281 MidiOut::yes))->nodeID);
2282 }
2283
2284 for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it)
2285 for (auto channel = 0; channel < numChannels; ++channel)
2286 expect (graph.addConnection ({ { it[0], channel }, { it[1], channel } }));
2287
2288 const auto b = std::chrono::steady_clock::now();
2289 graph.prepareToPlay (44100.0, 512);
2290 const auto e = std::chrono::steady_clock::now();
2291 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds> (e - b).count();
2292
2293 // No test here, but older versions of the graph would take forever to complete building
2294 // this graph, so we just want to make sure that we finish the test without timing out.
2295 logMessage ("render sequence built in " + String (duration) + " ms");
2296 }
2297 }
2298
2299private:
2300 enum class MidiIn { no, yes };
2301 enum class MidiOut { no, yes };
2302
2303 class BasicProcessor final : public AudioProcessor
2304 {
2305 public:
2306 explicit BasicProcessor (const AudioProcessor::BusesProperties& layout, MidiIn mIn, MidiOut mOut)
2307 : AudioProcessor (layout), midiIn (mIn), midiOut (mOut) {}
2308
2309 const String getName() const override { return "Basic Processor"; }
2310 double getTailLengthSeconds() const override { return {}; }
2311 bool acceptsMidi() const override { return midiIn == MidiIn ::yes; }
2312 bool producesMidi() const override { return midiOut == MidiOut::yes; }
2313 AudioProcessorEditor* createEditor() override { return {}; }
2314 bool hasEditor() const override { return {}; }
2315 int getNumPrograms() override { return 1; }
2316 int getCurrentProgram() override { return {}; }
2317 void setCurrentProgram (int) override {}
2318 const String getProgramName (int) override { return {}; }
2319 void changeProgramName (int, const String&) override {}
2320 void getStateInformation (juce::MemoryBlock&) override {}
2321 void setStateInformation (const void*, int) override {}
2322 void prepareToPlay (double, int) override {}
2323 void releaseResources() override {}
2324 void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
2325 bool supportsDoublePrecisionProcessing() const override { return true; }
2326 bool isMidiEffect() const override { return {}; }
2327 void reset() override {}
2328 void setNonRealtime (bool) noexcept override {}
2329
2330 using AudioProcessor::processBlock;
2331
2332 static std::unique_ptr<AudioProcessor> make (const BusesProperties& layout,
2333 MidiIn midiIn,
2334 MidiOut midiOut)
2335 {
2336 return std::make_unique<BasicProcessor> (layout, midiIn, midiOut);
2337 }
2338
2339 static BusesProperties getInputOnlyProperties()
2340 {
2341 return BusesProperties().withInput ("in", AudioChannelSet::stereo());
2342 }
2343
2344 static BusesProperties getStereoProperties()
2345 {
2346 return BusesProperties().withInput ("in", AudioChannelSet::stereo())
2347 .withOutput ("out", AudioChannelSet::stereo());
2348 }
2349
2350 static BusesProperties getMultichannelProperties (int numChannels)
2351 {
2352 return BusesProperties().withInput ("in", AudioChannelSet::discreteChannels (numChannels))
2353 .withOutput ("out", AudioChannelSet::discreteChannels (numChannels));
2354 }
2355
2356 private:
2357 MidiIn midiIn;
2358 MidiOut midiOut;
2359 };
2360};
2361
2363
2364#endif
2365
2366} // namespace juce
T accumulate(T... args)
T any_of(T... args)
T begin(T... args)
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:252
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:215
void insert(int indexToInsertAt, ParameterType newElement)
Inserts a new element into the array at a given position.
Definition juce_Array.h:462
ElementType * begin() noexcept
Returns a pointer to the first element in the array.
Definition juce_Array.h:328
ElementType * end() noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:344
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:267
A multi-channel buffer containing floating point audio samples.
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Changes the buffer's size or number of channels.
int getNumChannels() const noexcept
Returns the number of channels of audio data that this buffer contains.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer's channels.
void clear() noexcept
Clears all the samples in all channels and marks the buffer as cleared.
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
Copies samples from another buffer to this one.
Type *const * getArrayOfWritePointers() noexcept
Returns an array of pointers to the channels in the buffer.
A subclass of AudioPlayHead can supply information about the position and status of a moving play hea...
Base class for the component that acts as the GUI for an AudioProcessor.
A special type of AudioProcessor that can live inside an AudioProcessorGraph in order to use the audi...
bool isInput() const noexcept
True if this is an audio or midi input.
void getStateInformation(juce::MemoryBlock &destData) override
The host will call this method when it wants to save the processor's internal state.
void prepareToPlay(double newSampleRate, int estimatedSamplesPerBlock) override
Called before playback starts, to let the processor prepare itself.
bool isOutput() const noexcept
True if this is an audio or midi output.
void changeProgramName(int, const String &) override
Called by the host to rename a program.
bool hasEditor() const override
Your processor subclass must override this and return true if it can create an editor component.
@ midiInputNode
In this mode, the processor has a midi output which delivers the same midi data that is arriving at i...
@ audioInputNode
In this mode, the processor has output channels representing all the audio input channels that are co...
@ audioOutputNode
In this mode, the processor has input channels representing all the audio output channels that are go...
@ midiOutputNode
In this mode, the processor has a midi input and any data sent to it will be passed out of the parent...
bool supportsDoublePrecisionProcessing() const override
Returns true if the Audio processor supports double precision floating point processing.
const String getName() const override
Returns the name of this processor.
int getCurrentProgram() override
Returns the number of the currently active program.
void setCurrentProgram(int) override
Called by the host to change the current program.
void setStateInformation(const void *data, int sizeInBytes) override
This must restore the processor's state from a block of data previously created using getStateInforma...
void fillInPluginDescription(PluginDescription &) const override
Fills-in the appropriate parts of this plugin description object.
bool producesMidi() const override
Returns true if the processor produces MIDI messages.
double getTailLengthSeconds() const override
Returns the length of the processor's tail, in seconds.
bool acceptsMidi() const override
Returns true if the processor wants MIDI messages.
void processBlock(AudioBuffer< float > &, MidiBuffer &) override
Renders the next block.
AudioProcessorEditor * createEditor() override
Creates the processor's GUI.
const String getProgramName(int) override
Must return the name of a given program.
void releaseResources() override
Called after playback has stopped, to let the object free up any resources it no longer needs.
int getNumPrograms() override
Returns the number of preset programs the processor supports.
Represents an input or output channel of a node in an AudioProcessorGraph.
Represents one of the nodes, or processors, in an AudioProcessorGraph.
ReferenceCountedObjectPtr< Node > Ptr
A convenient typedef for referring to a pointer to a node object.
AudioProcessor * getProcessor() const noexcept
The actual processor object that this node represents.
const NodeID nodeID
The ID number assigned to this node.
A type of AudioProcessor which plays back a graph of other AudioProcessors.
void reset() override
A plugin can override this to be told when it should reset any playing voices.
void releaseResources() override
Called after playback has stopped, to let the object free up any resources it no longer needs.
bool supportsDoublePrecisionProcessing() const override
Returns true if the Audio processor supports double precision floating point processing.
bool removeConnection(const Connection &, UpdateKind=UpdateKind::sync)
Deletes the given connection.
bool producesMidi() const override
Returns true if the processor produces MIDI messages.
void clear(UpdateKind=UpdateKind::sync)
Deletes all nodes and connections from this graph.
bool isAnInputTo(const Node &source, const Node &destination) const noexcept
Does a recursive check to see if there's a direct or indirect series of connections between these two...
bool disconnectNode(NodeID, UpdateKind=UpdateKind::sync)
Removes all connections from the specified node.
bool isConnectionLegal(const Connection &) const
Returns true if the given connection's channel numbers map on to valid channels at each end.
bool isConnected(const Connection &) const noexcept
Returns true if the given connection exists.
bool canConnect(const Connection &) const
Returns true if it would be legal to connect the specified points.
void processBlock(AudioBuffer< float > &, MidiBuffer &) override
Renders the next block.
bool acceptsMidi() const override
Returns true if the processor wants MIDI messages.
double getTailLengthSeconds() const override
Returns the length of the processor's tail, in seconds.
AudioProcessorGraph()
Creates an empty graph.
~AudioProcessorGraph() override
Destructor.
const String getName() const override
Returns the name of this processor.
bool removeIllegalConnections(UpdateKind=UpdateKind::sync)
Performs a sanity checks of all the connections.
Node * getNodeForId(NodeID) const
Searches the graph for a node with the given ID number and returns it.
void setNonRealtime(bool) noexcept override
Called by the host to tell this processor whether it's being used in a non-realtime capacity for offl...
UpdateKind
Indicates how the graph should be updated after a change.
@ sync
Graph should be rebuilt immediately after modification.
void setStateInformation(const void *data, int sizeInBytes) override
This must restore the processor's state from a block of data previously created using getStateInforma...
const ReferenceCountedArray< Node > & getNodes() const noexcept
Returns the array of nodes in the graph.
Node::Ptr addNode(std::unique_ptr< AudioProcessor > newProcessor, std::optional< NodeID > nodeId=std::nullopt, UpdateKind=UpdateKind::sync)
Adds a node to the graph.
Node::Ptr removeNode(NodeID, UpdateKind=UpdateKind::sync)
Deletes a node within the graph which has the specified ID.
void rebuild()
Rebuilds the graph if necessary.
void prepareToPlay(double, int) override
Called before playback starts, to let the processor prepare itself.
std::vector< Connection > getConnections() const
Returns the list of connections in the graph.
bool addConnection(const Connection &, UpdateKind=UpdateKind::sync)
Attempts to connect two specified channels of two nodes.
void getStateInformation(juce::MemoryBlock &) override
The host will call this method when it wants to save the processor's internal state.
Base class for audio processing classes or plugins.
int getTotalNumInputChannels() const noexcept
Returns the total number of input channels.
void updateHostDisplay(const ChangeDetails &details=ChangeDetails::getDefaultFlags())
The processor can call this when something (apart from a parameter value) has changed.
virtual void setNonRealtime(bool isNonRealtime) noexcept
Called by the host to tell this processor whether it's being used in a non-realtime capacity for offl...
void setPlayConfigDetails(int numIns, int numOuts, double sampleRate, int blockSize)
This is called by the processor to specify its details before being played.
double getSampleRate() const noexcept
Returns the current sample rate.
int getTotalNumOutputChannels() const noexcept
Returns the total number of output channels.
AudioPlayHead * getPlayHead() const noexcept
Returns the current AudioPlayHead object that should be used to find out the state and position of th...
int getBlockSize() const noexcept
Returns the current typical block size that is being used.
Automatically locks and unlocks a mutex object.
Automatically locks and unlocks a mutex object.
A bit like an AsyncUpdater, but guarantees that after cancelPendingUpdate() returns,...
A class to hold a resizable block of raw data.
Holds a sequence of time-stamped midi events.
void ensureSize(size_t minimumNumBytes)
Preallocates some memory for the buffer to use.
void clear() noexcept
Removes all events from the buffer.
void addEvents(const MidiBuffer &otherBuffer, int startSample, int numSamples, int sampleDeltaToAdd)
Adds some events from another buffer to this one.
A small class to represent some facts about a particular type of plug-in.
int numOutputChannels
The number of outputs.
String pluginFormatName
The plug-in format, e.g.
bool isInstrument
True if the plug-in identifies itself as a synthesiser.
String category
A category, such as "Dynamics", "Reverbs", etc.
int numInputChannels
The number of inputs.
String name
The name of the plug-in.
String manufacturerName
The manufacturer.
int deprecatedUid
Deprecated: New projects should use uniqueId instead.
int uniqueId
A unique ID for the plug-in.
Holds a list of objects derived from ReferenceCountedObject, or which implement basic reference-count...
A smart-pointer class which points to a reference-counted object.
A simple spin-lock class that can be used as a simple, low-overhead mutex for uncontended situations.
The JUCE String class!
Definition juce_String.h:53
int hashCode() const noexcept
Generates a probably-unique 32-bit hashcode from this string.
Makes repeated callbacks to a virtual method at a specified time interval.
Definition juce_Timer.h:52
This is a base class for classes that perform a unit test.
T data(T... args)
T distance(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T cend(T... args)
T equal_range(T... args)
T erase(T... args)
T exchange(T... args)
T find(T... args)
T for_each(T... args)
T get(T... args)
T get_if(T... args)
T insert(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define jassertfalse
This will always cause an assertion failure.
T lower_bound(T... args)
JUCE Namespace.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
T next(T... args)
T prev(T... args)
T push_back(T... args)
T reset(T... args)
T size(T... args)
T sort(T... args)
Represents a connection between two channels of two nodes in an AudioProcessorGraph.
NodeAndChannel source
The channel and node which is the input source for this connection.
NodeAndChannel destination
The channel and node which is the input source for this connection.
Each node in the graph has a UID of this type.
Represents the bus layout state of a plug-in.
T swap(T... args)
typedef size_t
T tie(T... args)
T unique(T... args)