82 const auto iter =
std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
83 return iter != array.end() && (*iter)->nodeID == nodeID ? *iter :
nullptr;
97 [&] (
auto* n) { return n->getProcessor() == newProcessor.get(); }))
104 const auto iter =
std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
106 if (iter != array.end() && (*iter)->nodeID == nodeID)
113 return array.insert ((
int)
std::distance (array.begin(), iter),
114 new Node { nodeID, std::move (newProcessor) });
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))
125 bool operator== (
const Nodes&
other)
const {
return array ==
other.array; }
126 bool operator!= (
const Nodes&
other)
const {
return array !=
other.array; }
151 static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex;
155 if (! canConnect (n, c))
166 return iter != sourcesForDestination.
cend() && iter->second.erase (c.
source) == 1;
169 bool removeIllegalConnections (
const Nodes& n)
173 for (
auto& dest : sourcesForDestination)
176 dest.second = removeIllegalConnections (n, std::move (dest.second), dest.first);
183 bool disconnectNode (
NodeID n)
189 for (
auto& pair : sourcesForDestination)
191 const auto range = equalRange (pair.second, n);
192 result |= range.first != range.second;
193 pair.second.erase (range.first, range.second);
201 const auto source = n.getNodeForId (c.
source .nodeID);
202 const auto dest = n.getNodeForId (c.
destination.nodeID);
216 ? source->getProcessor()->producesMidi()
217 :
sourceChannel < source->getProcessor()->getTotalNumOutputChannels())
220 ? dest->getProcessor()->acceptsMidi()
221 :
destChannel < dest->getProcessor()->getTotalNumInputChannels());
226 return isConnectionLegal (n, c) && ! isConnected (c);
233 return iter != sourcesForDestination.
cend()
234 && iter->second.find (c.
source) != iter->second.cend();
243 const auto [begin, end] = equalRange (pair.second, srcID);
255 for (const auto& source : pair.second)
256 result.insert (source.nodeID);
263 const auto iter = sourcesForDestination.
find (p);
271 for (
auto& pair : sourcesForDestination)
272 for (
const auto& source : pair.second)
282 return getConnectedRecursive (source, dest, {}).found;
285 bool operator== (
const Connections&
other)
const {
return sourcesForDestination ==
other.sourcesForDestination; }
286 bool operator!= (
const Connections&
other)
const {
return sourcesForDestination !=
other.sourcesForDestination; }
293 bool isSourceConnectedToDestinationNodeIgnoringChannel (
const NodeAndChannel& source,
NodeID dest,
int channel)
const
314 auto getDestinationsForSources()
const
318 for (
const auto& [destination, sources] : sourcesForDestination)
332 SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state)
const
334 state.visited.insert (dest);
336 for (
const auto& s : getSourceNodesForDestination (dest))
338 if (state.found || s == source)
339 return { std::move (state.visited),
true };
341 if (state.visited.find (s) == state.visited.cend())
342 state = getConnectedRecursive (source, s, std::move (state));
350 NodeAndChannel destination)
352 for (
auto source = sources.
cbegin(); source != sources.
cend();)
354 if (! isConnectionLegal (nodes, { *source, destination }))
355 source = sources.
erase (source);
368 Map sourcesForDestination;
375 using ProcessingPrecision = AudioProcessorGraph::ProcessingPrecision;
377 ProcessingPrecision precision = ProcessingPrecision::singlePrecision;
378 double sampleRate = 0.0;
381 auto tie()
const noexcept {
return std::tie (precision, sampleRate, blockSize); }
427 const auto result = current != next;
452 for (
const auto& node : n.getNodes())
453 node->getProcessor()->releaseResources();
455 preparedNodes.clear();
458 if (current.has_value())
460 for (
const auto& node : n.getNodes())
462 if (preparedNodes.find (node->nodeID) != preparedNodes.cend())
465 preparedNodes.insert (node->nodeID);
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);
479 void removeNode (
const NodeID n)
481 preparedNodes.erase (n);
488 preparedNodes.clear();
498template <
typename FloatType>
537 perform (
audioChunk, midiChunk, audioPlayHead);
546 currentAudioOutputBuffer.
clear();
547 currentMidiOutputBuffer.
clear();
550 const Context context { { buffer,
551 currentAudioOutputBuffer,
553 currentMidiOutputBuffer },
557 for (
const auto& op : renderOps)
558 op->process (context);
562 buffer.
copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples);
568 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4661)
570 void addClearChannelOp (
int index)
574 explicit ClearOp (
int indexIn) : index (indexIn) {}
576 void prepare (FloatType*
const* renderBuffer, MidiBuffer*)
override
578 channelBuffer = renderBuffer[index];
581 void process (
const Context& c)
override
583 FloatVectorOperations::clear (channelBuffer, c.numSamples);
586 FloatType* channelBuffer =
nullptr;
590 renderOps.
push_back (std::make_unique<ClearOp> (index));
597 explicit CopyOp (
int fromIn,
int toIn) : from (fromIn), to (toIn) {}
599 void prepare (FloatType*
const* renderBuffer, MidiBuffer*)
override
601 fromBuffer = renderBuffer[from];
602 toBuffer = renderBuffer[to];
605 void process (
const Context& c)
override
607 FloatVectorOperations::copy (toBuffer, fromBuffer, c.numSamples);
610 FloatType* fromBuffer =
nullptr;
611 FloatType* toBuffer =
nullptr;
612 int from = 0, to = 0;
622 explicit AddOp (
int fromIn,
int toIn) : from (fromIn), to (toIn) {}
624 void prepare (FloatType*
const* renderBuffer, MidiBuffer*)
override
626 fromBuffer = renderBuffer[from];
627 toBuffer = renderBuffer[to];
630 void process (
const Context& c)
override
632 FloatVectorOperations::add (toBuffer, fromBuffer, c.numSamples);
635 FloatType* fromBuffer =
nullptr;
636 FloatType* toBuffer =
nullptr;
637 int from = 0, to = 0;
643 JUCE_END_IGNORE_WARNINGS_MSVC
645 void addClearMidiBufferOp (
int index)
649 explicit ClearOp (
int indexIn) : index (indexIn) {}
651 void prepare (FloatType*
const*, MidiBuffer* buffers)
override
653 channelBuffer = buffers + index;
656 void process (
const Context&)
override
658 channelBuffer->clear();
661 MidiBuffer* channelBuffer =
nullptr;
665 renderOps.
push_back (std::make_unique<ClearOp> (index));
672 explicit CopyOp (
int fromIn,
int toIn) : from (fromIn), to (toIn) {}
674 void prepare (FloatType*
const*, MidiBuffer* buffers)
override
676 fromBuffer = buffers + from;
677 toBuffer = buffers + to;
680 void process (
const Context&)
override
682 *toBuffer = *fromBuffer;
685 MidiBuffer* fromBuffer =
nullptr;
686 MidiBuffer* toBuffer =
nullptr;
687 int from = 0, to = 0;
697 explicit AddOp (
int fromIn,
int toIn) : from (fromIn), to (toIn) {}
699 void prepare (FloatType*
const*, MidiBuffer* buffers)
override
701 fromBuffer = buffers + from;
702 toBuffer = buffers + to;
705 void process (
const Context& c)
override
707 toBuffer->addEvents (*fromBuffer, 0, c.numSamples, 0);
710 MidiBuffer* fromBuffer =
nullptr;
711 MidiBuffer* toBuffer =
nullptr;
712 int from = 0, to = 0;
722 DelayChannelOp (
int chan,
int delaySize)
723 : buffer ((
size_t) (delaySize + 1), (FloatType) 0),
725 writeIndex (delaySize)
729 void prepare (FloatType*
const* renderBuffer, MidiBuffer*)
override
731 channelBuffer = renderBuffer[channel];
734 void process (
const Context& c)
override
736 auto*
data = channelBuffer;
738 for (
int i = c.numSamples; --i >= 0;)
740 buffer[(
size_t) writeIndex] = *data;
743 if (++readIndex >= (
int) buffer.size()) readIndex = 0;
744 if (++writeIndex >= (
int) buffer.size()) writeIndex = 0;
749 FloatType* channelBuffer =
nullptr;
751 int readIndex = 0, writeIndex;
757 void addProcessOp (
const Node::Ptr& node,
764 if (
auto*
ioNode =
dynamic_cast<const AudioProcessorGraph::AudioGraphIOProcessor*
> (node->getProcessor()))
766 switch (
ioNode->getType())
788 void prepareBuffers (
int blockSize)
790 renderingBuffer.
setSize (numBuffersNeeded + 1, blockSize);
791 renderingBuffer.
clear();
792 currentAudioOutputBuffer.
setSize (numBuffersNeeded + 1, blockSize);
793 currentAudioOutputBuffer.
clear();
795 currentMidiOutputBuffer.
clear();
797 midiBuffers.clearQuick();
798 midiBuffers.resize (numMidiBuffersNeeded);
804 for (
auto&& m : midiBuffers)
807 for (
const auto& op : renderOps)
808 op->prepare (renderingBuffer.getArrayOfWritePointers(), midiBuffers.
data());
811 int numBuffersNeeded = 0, numMidiBuffersNeeded = 0;
813 AudioBuffer<FloatType> renderingBuffer, currentAudioOutputBuffer;
815 MidiBuffer currentMidiOutputBuffer;
818 MidiBuffer midiChunk;
824 virtual ~RenderOp() =
default;
825 virtual void prepare (FloatType*
const*, MidiBuffer*) = 0;
826 virtual void process (
const Context&) = 0;
829 struct NodeOp :
public RenderOp
836 processor (*n->getProcessor()),
841 while (audioChannelsToUse.size() < (
int) audioChannels.size())
842 audioChannelsToUse.add (0);
847 for (
size_t i = 0; i < audioChannels.size(); ++i)
848 audioChannels[i] =
renderBuffer[audioChannelsToUse.getUnchecked ((
int) i)];
850 midiBuffer =
buffers + midiBufferToUse;
853 void process (
const Context& c)
final
855 processor.setPlayHead (c.audioPlayHead);
859 if (
const auto*
proc = node->getProcessor())
860 if (
proc->getTotalNumInputChannels() == 0 &&
proc->getTotalNumOutputChannels() == 0)
863 return (
int) audioChannels.size();
866 AudioBuffer<FloatType> buffer { audioChannels.data(),
numAudioChannels, c.numSamples };
868 if (processor.isSuspended())
874 const auto bypass = node->isBypassed() && processor.getBypassParameter() ==
nullptr;
875 processWithBuffer (c.globalIO, bypass, buffer, *midiBuffer);
879 virtual void processWithBuffer (
const GlobalIO&,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&
midi) = 0;
882 AudioProcessor& processor;
883 MidiBuffer* midiBuffer =
nullptr;
885 Array<int> audioChannelsToUse;
887 const int midiBufferToUse;
890 struct ProcessOp
final :
public NodeOp
892 using NodeOp::NodeOp;
894 void processWithBuffer (
const GlobalIO&,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&
midi)
final
899 void callProcess (
bool bypass, AudioBuffer<float>& buffer, MidiBuffer&
midi)
901 if (this->processor.isUsingDoublePrecision())
903 tempBufferDouble.makeCopyOf (buffer,
true);
904 processImpl (bypass, this->processor, tempBufferDouble,
midi);
905 buffer.makeCopyOf (tempBufferDouble,
true);
909 processImpl (bypass, this->processor, buffer,
midi);
913 void callProcess (
bool bypass, AudioBuffer<double>& buffer, MidiBuffer&
midi)
915 if (this->processor.isUsingDoublePrecision())
917 processImpl (bypass, this->processor, buffer,
midi);
921 tempBufferFloat.makeCopyOf (buffer,
true);
922 processImpl (bypass, this->processor, tempBufferFloat,
midi);
923 buffer.makeCopyOf (tempBufferFloat,
true);
927 template <
typename Value>
936 AudioBuffer<float> tempBufferFloat, tempBufferDouble;
939 struct MidiInOp
final :
public NodeOp
941 using NodeOp::NodeOp;
943 void processWithBuffer (
const GlobalIO& g,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&
midi)
final
946 midi.addEvents (g.midiIn, 0,
audio.getNumSamples(), 0);
950 struct MidiOutOp
final :
public NodeOp
952 using NodeOp::NodeOp;
954 void processWithBuffer (
const GlobalIO& g,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&
midi)
final
957 g.midiOut.addEvents (
midi, 0,
audio.getNumSamples(), 0);
961 struct AudioInOp
final :
public NodeOp
963 using NodeOp::NodeOp;
965 void processWithBuffer (
const GlobalIO& g,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&)
final
970 for (
int i =
jmin (g.audioIn.getNumChannels(),
audio.getNumChannels()); --i >= 0;)
971 audio.copyFrom (i, 0, g.audioIn, i, 0,
audio.getNumSamples());
975 struct AudioOutOp
final :
public NodeOp
977 using NodeOp::NodeOp;
979 void processWithBuffer (
const GlobalIO& g,
bool bypass, AudioBuffer<FloatType>&
audio, MidiBuffer&)
final
984 for (
int i =
jmin (g.audioOut.getNumChannels(),
audio.getNumChannels()); --i >= 0;)
985 g.audioOut.addFrom (i, 0,
audio, i, 0,
audio.getNumSamples());
999 int latencySamples = 0;
1011 static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex;
1013 template <
typename FloatType>
1018 return { std::move (sequence), builder.totalLatency };
1025 struct AssignedBuffer
1029 static AssignedBuffer createReadOnlyEmpty()
noexcept {
return { { zeroNodeID(), 0 } }; }
1030 static AssignedBuffer createFree()
noexcept {
return { { freeNodeID(), 0 } }; }
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()); }
1036 void setFree()
noexcept { channel = { freeNodeID(), 0 }; }
1037 void setAssignedToNonExistentNode()
noexcept { channel = { anonNodeID(), 0 }; }
1040 static NodeID anonNodeID() {
return NodeID (0x7ffffffd); }
1041 static NodeID zeroNodeID() {
return NodeID (0x7ffffffe); }
1042 static NodeID freeNodeID() {
return NodeID (0x7fffffff); }
1047 enum { readOnlyEmptyBufferIndex = 0 };
1050 int totalLatency = 0;
1052 int getNodeDelay (
NodeID nodeID)
const noexcept
1054 const auto iter = delays.
find (nodeID.uid);
1055 return iter != delays.
end() ? iter->second : 0;
1060 const auto sources = c.getSourceNodesForDestination (nodeID);
1063 return jmax (acc, this->getNodeDelay (source));
1068 void getAllParentsOfNode (
const NodeID& child,
1073 for (
const auto&
parentNode : c.getSourceNodesForDestination (child))
1099 for (
auto& node : n.getNodes())
1101 const auto nodeID = node->nodeID;
1120 template <
typename RenderSequence>
1121 int findBufferForInputAudioChannel (
const Connections& c,
1132 auto sources = c.getSourcesForDestination ({ node.
nodeID,
inputChan });
1135 if (sources.
empty())
1138 return readOnlyEmptyBufferIndex;
1140 auto index = getFreeBuffer (audioBuffers);
1141 sequence.addClearChannelOp (index);
1146 if (sources.
size() == 1)
1149 auto src = *sources.
begin();
1151 int bufIndex = getBufferContaining (src);
1156 bufIndex = readOnlyEmptyBufferIndex;
1169 auto nodeDelay = getNodeDelay (src.nodeID);
1183 for (
const auto& src : sources)
1193 auto nodeDelay = getNodeDelay (src.nodeID);
1208 bufIndex = getFreeBuffer (audioBuffers);
1216 sequence.addClearChannelOp (
bufIndex);
1229 for (
const auto& src : sources)
1233 int srcIndex = getBufferContaining (src);
1237 auto nodeDelay = getNodeDelay (src.nodeID);
1265 template <
typename RenderSequence>
1266 int findBufferForInputMidiChannel (
const Connections& c,
1273 auto sources = c.getSourcesForDestination ({ node.
nodeID, midiChannelIndex });
1276 if (sources.
empty())
1278 auto midiBufferToUse = getFreeBuffer (midiBuffers);
1280 if (processor.acceptsMidi() || processor.producesMidi())
1281 sequence.addClearMidiBufferOp (midiBufferToUse);
1283 return midiBufferToUse;
1287 if (sources.
size() == 1)
1289 auto src = *sources.
begin();
1290 auto midiBufferToUse = getBufferContaining (src);
1292 if (midiBufferToUse >= 0)
1299 sequence.addCopyMidiBufferOp (midiBufferToUse,
newFreeBuffer);
1306 midiBufferToUse = getFreeBuffer (midiBuffers);
1309 return midiBufferToUse;
1313 int midiBufferToUse = -1;
1318 for (
const auto& src : sources)
1338 midiBufferToUse = getFreeBuffer (midiBuffers);
1339 jassert (midiBufferToUse >= 0);
1344 sequence.addCopyMidiBufferOp (
srcIndex, midiBufferToUse);
1346 sequence.addClearMidiBufferOp (midiBufferToUse);
1353 for (
const auto& src : sources)
1357 auto srcIndex = getBufferContaining (src);
1360 sequence.addAddMidiBufferOp (
srcIndex, midiBufferToUse);
1367 return midiBufferToUse;
1370 template <
typename RenderSequence>
1371 void createRenderingOpsForNode (
const Connections& c,
1379 auto numOuts = processor.getTotalNumOutputChannels();
1388 auto index = findBufferForInputAudioChannel (c,
1397 audioChannelsToUse.
add (index);
1405 auto index = getFreeBuffer (audioBuffers);
1407 audioChannelsToUse.
add (index);
1412 auto midiBufferToUse = findBufferForInputMidiChannel (c, reversed, sequence, node,
ourRenderingIndex);
1414 if (processor.producesMidi())
1415 midiBuffers.
getReference (midiBufferToUse).channel = { node.
nodeID, midiChannelIndex };
1423 sequence.addProcessOp (node, audioChannelsToUse,
totalChans, midiBufferToUse);
1429 for (
int i = 1; i <
buffers.size(); ++i)
1430 if (
buffers.getReference (i).isFree())
1433 buffers.add (AssignedBuffer::createFree());
1441 for (
auto& b : output.isMIDI() ? midiBuffers : audioBuffers)
1443 if (b.channel == output)
1457 if (b.isAssigned() && ! isBufferNeededLater (c,
stepIndex, -1, b.channel))
1469 if (c.isSourceConnectedToDestinationNodeIgnoringChannel (output,
1478 return c.isSourceConnectedToDestinationNodeIgnoringChannel (output, node->nodeID, -1);
1482 template <
typename RenderSequence>
1484 : orderedNodes (createOrderedNodeList (n, c))
1486 audioBuffers.
add (AssignedBuffer::createReadOnlyEmpty());
1487 midiBuffers .
add (AssignedBuffer::createReadOnlyEmpty());
1489 const auto reversed = c.getDestinationsForSources();
1491 for (
int i = 0; i < orderedNodes.
size(); ++i)
1493 createRenderingOpsForNode (c, reversed, sequence, *orderedNodes.
getUnchecked (i), i);
1494 markAnyUnusedBuffersAsFree (reversed, audioBuffers, i);
1495 markAnyUnusedBuffersAsFree (reversed, midiBuffers, i);
1498 sequence.numBuffersNeeded = audioBuffers.
size();
1499 sequence.numMidiBuffersNeeded = midiBuffers.
size();
1516 :
RenderSequence (s, s.precision == AudioProcessor::ProcessingPrecision::singlePrecision
1517 ? RenderSequenceBuilder::build<float> (n, c)
1518 : RenderSequenceBuilder::build<double> (n, c))
1522 template <
typename FloatType>
1531 int getLatencySamples()
const {
return sequence.latencySamples; }
1535 template <
typename This,
typename Callback>
1536 static void visitRenderSequence (This& t, Callback&& callback)
1544 : settings (s), sequence (std::move (
built))
1546 visitRenderSequence (*
this, [&] (
auto&
seq) {
seq.prepareBuffers (settings.blockSize); });
1562 auto tie()
const {
return std::tie (layout, latencySamples); }
1566 int latencySamples = 0;
1579 auto tie()
const {
return std::tie (settings, connections, nodes); }
1583 : settings (s), connections (c), nodes (getNodeMap (n)) {}
1593 const auto&
nodeRefs = n.getNodes();
1598 auto*
proc = node->getProcessor();
1601 proc->getLatencySamples() });
1636 mainThreadState = std::move (next);
1641 void updateAudioThreadState()
1645 if (lock.isLocked() && isNew)
1648 std::swap (mainThreadState, audioThreadState);
1654 RenderSequence* getAudioThreadState()
const {
return audioThreadState.get(); }
1657 void timerCallback()
override
1662 mainThreadState.reset();
1671AudioProcessorGraph::Connection::Connection (NodeAndChannel src, NodeAndChannel dst) noexcept
1672 : source (src), destination (dst)
1676bool AudioProcessorGraph::Connection::operator== (
const Connection& other)
const noexcept
1678 return source == other.source && destination == other.destination;
1681bool AudioProcessorGraph::Connection::operator!= (
const Connection& c)
const noexcept
1683 return ! operator== (c);
1686bool AudioProcessorGraph::Connection::operator< (
const Connection& other)
const noexcept
1688 const auto tie = [] (
auto& x)
1691 x.destination.nodeID,
1692 x.source.channelIndex,
1693 x.destination.channelIndex);
1695 return tie (*
this) <
tie (other);
1704 const auto& getNodes()
const {
return nodes.getNodes(); }
1708 if (getNodes().isEmpty())
1714 topologyChanged (updateKind);
1717 auto getNodeForId (
NodeID nodeID)
const
1719 return nodes.getNodeForId (nodeID);
1726 if (newProcessor.
get() == owner)
1732 const auto idToUse = nodeID.value_or (
NodeID { lastNodeID.uid + 1 });
1734 auto added = nodes.addNode (std::move (newProcessor), idToUse);
1736 if (added ==
nullptr)
1739 if (lastNodeID < idToUse)
1740 lastNodeID = idToUse;
1742 setParentGraph (added->getProcessor());
1744 topologyChanged (updateKind);
1750 connections.disconnectNode (nodeID);
1751 auto result = nodes.removeNode (nodeID);
1752 nodeStates.removeNode (nodeID);
1753 topologyChanged (updateKind);
1759 return connections.getConnections();
1764 return connections.isConnected (c);
1769 return connections.isConnected (srcID, destID);
1772 bool isAnInputTo (
const Node& src,
const Node& dst)
const
1779 return connections.isAnInputTo (src, dst);
1784 return connections.canConnect (nodes, c);
1789 if (! connections.addConnection (nodes, c))
1793 topologyChanged (updateKind);
1799 if (! connections.removeConnection (c))
1802 topologyChanged (updateKind);
1808 if (! connections.disconnectNode (nodeID))
1811 topologyChanged (updateKind);
1815 bool isConnectionLegal (
const Connection& c)
const
1817 return connections.isConnectionLegal (nodes, c);
1820 bool removeIllegalConnections (
UpdateKind updateKind)
1822 const auto result = connections.removeIllegalConnections (nodes);
1823 topologyChanged (updateKind);
1828 void prepareToPlay (
double sampleRate,
int estimatedSamplesPerBlock)
1830 owner->setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock);
1833 settings.precision = owner->getProcessingPrecision();
1834 settings.sampleRate = sampleRate;
1835 settings.blockSize = estimatedSamplesPerBlock;
1837 nodeStates.setState (settings);
1839 topologyChanged (UpdateKind::sync);
1842 void releaseResources()
1844 nodeStates.setState (nullopt);
1845 topologyChanged (UpdateKind::sync);
1850 if (updateKind == UpdateKind::none)
1853 if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
1854 handleAsyncUpdate();
1856 updater.triggerAsyncUpdate();
1861 for (
auto* n : getNodes())
1862 n->getProcessor()->reset();
1865 void setNonRealtime (
bool isProcessingNonRealtime)
1867 for (
auto* n : getNodes())
1868 n->getProcessor()->setNonRealtime (isProcessingNonRealtime);
1871 template <
typename Value>
1874 renderSequenceExchange.updateAudioThreadState();
1876 if (renderSequenceExchange.getAudioThreadState() ==
nullptr && MessageManager::getInstance()->isThisTheMessageThread())
1877 handleAsyncUpdate();
1879 if (owner->isNonRealtime())
1881 while (renderSequenceExchange.getAudioThreadState() ==
nullptr)
1884 renderSequenceExchange.updateAudioThreadState();
1888 auto* state = renderSequenceExchange.getAudioThreadState();
1891 if (state !=
nullptr && state->getSettings() == nodeStates.getLastRequestedSettings())
1893 state->process (audio, midi, playHead);
1903 auto* getAudioThreadState()
const {
return renderSequenceExchange.getAudioThreadState(); }
1909 ioProc->setParentGraph (owner);
1914 owner->sendChangeMessage();
1915 rebuild (updateKind);
1918 void handleAsyncUpdate()
1920 if (
const auto newSettings = nodeStates.applySettings (nodes))
1922 for (
const auto node : nodes.getNodes())
1923 setParentGraph (node->getProcessor());
1927 if (
std::exchange (lastBuiltSequence, newSignature) != newSignature)
1929 auto sequence = std::make_unique<RenderSequence> (*newSettings, nodes, connections);
1930 owner->setLatencySamples (sequence->getLatencySamples());
1931 renderSequenceExchange.set (std::move (sequence));
1936 lastBuiltSequence.reset();
1937 renderSequenceExchange.set (
nullptr);
1952AudioProcessorGraph::AudioProcessorGraph() : pimpl (
std::make_unique<
Pimpl> (*this)) {}
1999 return pimpl->removeNode (nodeID,
updateKind);
2004 if (node !=
nullptr)
2012AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (
const IODeviceType
deviceType)
2017AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() =
default;
2046 if (type == audioOutputNode && graph !=
nullptr)
2051 if (type == audioInputNode && graph !=
nullptr)
2088 return type == midiOutputNode;
2093 return type == midiInputNode;
2116 if (graph ==
nullptr)
2134 AudioProcessorGraphTests()
2135 :
UnitTest (
"AudioProcessorGraph", UnitTestCategories::audioProcessors) {}
2137 void runTest()
override
2139 const auto midiChannel = AudioProcessorGraph::midiChannelIndex;
2141 beginTest (
"isConnected returns true when two nodes are connected");
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;
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 } }));
2152 expect (graph.getConnections().empty());
2153 expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2156 expect (graph.addConnection ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2158 expect (graph.getConnections().size() == 1);
2159 expect (graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2162 expect (graph.disconnectNode (
nodeA));
2164 expect (graph.getConnections().empty());
2165 expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } }));
2169 beginTest (
"graph lookups work with a large number of connections");
2177 for (
auto i = 0; i <
numNodes; ++i)
2179 nodeIDs.
push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(),
2181 MidiOut::yes))->nodeID);
2186 expect (graph.addConnection ({ { it[0], 0 }, { it[1], 0 } }));
2187 expect (graph.addConnection ({ { it[0], 1 }, { it[1], 1 } }));
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]));
2199 const auto& nodes = graph.getNodes();
2201 expect (! graph.isAnInputTo (*nodes[0], *nodes[0]));
2206 expect (! graph.isAnInputTo (**
it, **
it));
2208 expect (graph.isAnInputTo (*nodes[0], **
it));
2209 expect (! graph.isAnInputTo (**
it, *nodes[0]));
2211 expect (graph.isAnInputTo (**
it, *nodes[nodes.size() - 1]));
2212 expect (! graph.isAnInputTo (*nodes[nodes.size() - 1], **
it));
2216 graph.addConnection ({ {
nodeIDs.back(), 0 }, {
nodeIDs.front(), 0 } });
2217 graph.addConnection ({ {
nodeIDs.back(), 1 }, {
nodeIDs.front(), 1 } });
2220 for (
const auto* node : graph.
getNodes())
2222 expect (graph.isAnInputTo (*node, *node));
2224 expect (graph.isAnInputTo (*nodes[0], *node));
2225 expect (graph.isAnInputTo (*node, *nodes[0]));
2227 expect (graph.isAnInputTo (*node, *nodes[nodes.size() - 1]));
2228 expect (graph.isAnInputTo (*nodes[nodes.size() - 1], *node));
2232 beginTest (
"rebuilding the graph recalculates overall latency");
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;
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 } }));
2245 expect (graph.getLatencySamples() == 0);
2251 expect (graph.getLatencySamples() == 0);
2253 graph.prepareToPlay (44100, 512);
2263 graph.getNodeForId (
final)->getProcessor()->setLatencySamples (
finalLatency);
2268 beginTest (
"large render sequence can be built");
2275 constexpr auto numChannels = 100;
2277 for (
auto i = 0; i <
numNodes; ++i)
2279 nodeIDs.
push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getMultichannelProperties (numChannels),
2281 MidiOut::yes))->nodeID);
2285 for (
auto channel = 0; channel < numChannels; ++channel)
2286 expect (graph.addConnection ({ { it[0], channel }, { it[1], channel } }));
2289 graph.prepareToPlay (44100.0, 512);
2291 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds> (e - b).count();
2295 logMessage (
"render sequence built in " + String (duration) +
" ms");
2300 enum class MidiIn { no, yes };
2301 enum class MidiOut { no, yes };
2306 explicit BasicProcessor (
const AudioProcessor::BusesProperties& layout, MidiIn mIn, MidiOut mOut)
2307 : AudioProcessor (layout), midiIn (mIn), midiOut (mOut) {}
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 {}
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 {}
2330 using AudioProcessor::processBlock;
2336 return std::make_unique<BasicProcessor> (layout, midiIn, midiOut);
2339 static BusesProperties getInputOnlyProperties()
2341 return BusesProperties().withInput (
"in", AudioChannelSet::stereo());
2344 static BusesProperties getStereoProperties()
2346 return BusesProperties().withInput (
"in", AudioChannelSet::stereo())
2347 .withOutput (
"out", AudioChannelSet::stereo());
2350 static BusesProperties getMultichannelProperties (
int numChannels)
2352 return BusesProperties().withInput (
"in", AudioChannelSet::discreteChannels (numChannels))
2353 .withOutput (
"out", AudioChannelSet::discreteChannels (numChannels));
Holds a resizable array of primitive or copy-by-value objects.
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
int size() const noexcept
Returns the current number of elements in the array.
void insert(int indexToInsertAt, ParameterType newElement)
Inserts a new element into the array at a given position.
ElementType * begin() noexcept
Returns a pointer to the first element in the array.
ElementType * end() noexcept
Returns a pointer to the element which follows the last element in the array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
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.
AudioProcessor()
Constructor.
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.
String version
The version.
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.
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.
This is a base class for classes that perform a unit test.
T emplace_back(T... args)
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...
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
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.