1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Graph: Pimpl-ify

This commit is contained in:
reuk 2022-08-09 13:08:22 +01:00
parent 6c762f74d1
commit e6c8857351
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
2 changed files with 481 additions and 429 deletions

View file

@ -26,13 +26,157 @@
namespace juce
{
void AudioProcessorGraph::NodeStates::setState (Optional<PrepareSettings> newSettings)
/* Provides a comparison function for various types that have an associated NodeID,
for use with equal_range, lower_bound etc.
*/
class ImplicitNode
{
public:
using Node = AudioProcessorGraph::Node;
using NodeID = AudioProcessorGraph::NodeID;
using NodeAndChannel = AudioProcessorGraph::NodeAndChannel;
ImplicitNode (NodeID x) : node (x) {}
ImplicitNode (NodeAndChannel x) : ImplicitNode (x.nodeID) {}
ImplicitNode (const Node* x) : ImplicitNode (x->nodeID) {}
ImplicitNode (const std::pair<const NodeAndChannel, std::set<NodeAndChannel>>& x) : ImplicitNode (x.first) {}
/* This is the comparison function. */
static bool compare (ImplicitNode a, ImplicitNode b) { return a.node < b.node; }
private:
NodeID node;
};
//==============================================================================
/* A copyable type holding all the nodes, and allowing fast lookup by id. */
class Nodes
{
public:
using Node = AudioProcessorGraph::Node;
using NodeID = AudioProcessorGraph::NodeID;
const ReferenceCountedArray<Node>& getNodes() const { return array; }
Node::Ptr getNodeForId (NodeID x) const;
Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor, const NodeID nodeID);
Node::Ptr removeNode (NodeID nodeID);
bool operator== (const Nodes& other) const { return array == other.array; }
bool operator!= (const Nodes& other) const { return array != other.array; }
private:
ReferenceCountedArray<Node> array;
};
//==============================================================================
/* A value type holding a full set of graph connections. */
class Connections
{
public:
using Node = AudioProcessorGraph::Node;
using NodeID = AudioProcessorGraph::NodeID;
using Connection = AudioProcessorGraph::Connection;
using NodeAndChannel = AudioProcessorGraph::NodeAndChannel;
bool addConnection (const Nodes& n, const Connection& c);
bool removeConnection (const Connection& c);
bool removeIllegalConnections (const Nodes& n);
bool disconnectNode (NodeID n);
static bool isConnectionLegal (const Nodes& n, Connection c);
bool canConnect (const Nodes& n, Connection c) const;
bool isConnected (Connection c) const;
bool isConnected (NodeID srcID, NodeID destID) const;
std::set<NodeID> getSourceNodesForDestination (NodeID destID) const;
std::set<NodeAndChannel> getSourcesForDestination (const NodeAndChannel& p) const;
std::vector<AudioProcessorGraph::Connection> getConnections() const;
bool isAnInputTo (NodeID src, NodeID dst) const;
bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; }
bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; }
private:
using Map = std::map<NodeAndChannel, std::set<NodeAndChannel>>;
struct SearchState
{
std::set<NodeID> visited;
bool found = false;
};
SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const;
static std::set<NodeAndChannel> removeIllegalConnections (const Nodes& n,
std::set<NodeAndChannel> sources,
NodeAndChannel destination);
std::pair<Map::const_iterator, Map::const_iterator> getMatchingDestinations (NodeID destID) const;
Map sourcesForDestination;
};
//==============================================================================
/* Settings used to prepare a node for playback. */
struct PrepareSettings
{
using ProcessingPrecision = AudioProcessorGraph::ProcessingPrecision;
ProcessingPrecision precision = ProcessingPrecision::singlePrecision;
double sampleRate = 0.0;
int blockSize = 0;
auto tie() const noexcept { return std::tie (precision, sampleRate, blockSize); }
bool operator== (const PrepareSettings& other) const { return tie() == other.tie(); }
bool operator!= (const PrepareSettings& other) const { return tie() != other.tie(); }
};
//==============================================================================
/* Keeps track of the PrepareSettings applied to each node. */
class NodeStates
{
public:
using Node = AudioProcessorGraph::Node;
using NodeID = AudioProcessorGraph::NodeID;
/* Called from prepareToPlay and releaseResources with the PrepareSettings that should be
used next time the graph is rebuilt.
*/
void setState (Optional<PrepareSettings> newSettings);
/* Call from the audio thread only. */
Optional<PrepareSettings> getLastRequestedSettings() const { return next; }
/* Call from the main thread only!
Called after updating the graph topology to prepare any currently-unprepared nodes.
To ensure that all nodes are initialised with the same sample rate, buffer size, etc. as
the enclosing graph, we must ensure that any operation that uses these details (preparing
individual nodes) is synchronized with prepare-to-play and release-resources on the
enclosing graph.
If the new PrepareSettings are different to the last-seen settings, all nodes will
be prepared/unprepared as necessary. If the PrepareSettings have not changed, then only
new nodes will be prepared/unprepared.
Returns the settings that were applied to the nodes.
*/
Optional<PrepareSettings> applySettings (const Nodes& nodes);
private:
std::mutex mutex; // Protects 'next'
std::set<NodeID> preparedNodes;
Optional<PrepareSettings> current, next;
};
void NodeStates::setState (Optional<PrepareSettings> newSettings)
{
const std::lock_guard<std::mutex> lock (mutex);
next = newSettings;
}
Optional<AudioProcessorGraph::PrepareSettings> AudioProcessorGraph::NodeStates::applySettings (const Nodes& n)
Optional<PrepareSettings> NodeStates::applySettings (const Nodes& n)
{
const auto settingsChanged = [this]
{
@ -356,7 +500,7 @@ private:
};
//==============================================================================
class AudioProcessorGraph::RenderSequenceBuilder
class RenderSequenceBuilder
{
public:
using Node = AudioProcessorGraph::Node;
@ -868,7 +1012,13 @@ private:
};
//==============================================================================
class AudioProcessorGraph::RenderSequence
/* A full graph of audio processors, ready to process at a particular sample rate, block size,
and precision.
Instances of this class will be created on the main thread, and then passed over to the audio
thread for processing.
*/
class RenderSequence
{
public:
RenderSequence (PrepareSettings s, const Nodes& n, const Connections& c)
@ -888,12 +1038,12 @@ public:
renderSequenceD.perform (audio, midi, playHead);
}
void processIO (AudioGraphIOProcessor& io, AudioBuffer<float>& audio, MidiBuffer& midi)
void processIO (AudioProcessorGraph::AudioGraphIOProcessor& io, AudioBuffer<float>& audio, MidiBuffer& midi)
{
processIOBlock (io, renderSequenceF, audio, midi);
}
void processIO (AudioGraphIOProcessor& io, AudioBuffer<double>& audio, MidiBuffer& midi)
void processIO (AudioProcessorGraph::AudioGraphIOProcessor& io, AudioBuffer<double>& audio, MidiBuffer& midi)
{
processIOBlock (io, renderSequenceD, audio, midi);
}
@ -963,17 +1113,45 @@ private:
};
//==============================================================================
AudioProcessorGraph::RenderSequenceExchange::RenderSequenceExchange() = default;
AudioProcessorGraph::RenderSequenceExchange::~RenderSequenceExchange() = default;
/* Facilitates wait-free render-sequence updates.
void AudioProcessorGraph::RenderSequenceExchange::set (std::unique_ptr<RenderSequence>&& next)
Topology updates always happen on the main thread (or synchronised with the main thread).
After updating the graph, the 'baked' graph is passed to RenderSequenceExchange::set.
At the top of the audio callback, RenderSequenceExchange::updateAudioThreadState will
attempt to install the most-recently-baked graph, if there's one waiting.
*/
class RenderSequenceExchange
{
public:
RenderSequenceExchange();
~RenderSequenceExchange();
void set (std::unique_ptr<RenderSequence>&&);
/** Call from the audio thread only. */
void updateAudioThreadState();
/** Call from the audio thread only. */
RenderSequence* getAudioThreadState() const;
private:
SpinLock mutex;
std::unique_ptr<RenderSequence> mainThreadState, audioThreadState;
bool isNew = false;
};
//==============================================================================
RenderSequenceExchange::RenderSequenceExchange() = default;
RenderSequenceExchange::~RenderSequenceExchange() = default;
void RenderSequenceExchange::set (std::unique_ptr<RenderSequence>&& next)
{
const SpinLock::ScopedLockType lock (mutex);
mainThreadState = std::move (next);
isNew = true;
}
void AudioProcessorGraph::RenderSequenceExchange::updateAudioThreadState()
void RenderSequenceExchange::updateAudioThreadState()
{
const SpinLock::ScopedTryLockType lock (mutex);
@ -985,20 +1163,20 @@ void AudioProcessorGraph::RenderSequenceExchange::updateAudioThreadState()
}
}
AudioProcessorGraph::RenderSequence* AudioProcessorGraph::RenderSequenceExchange::getAudioThreadState() const
RenderSequence* RenderSequenceExchange::getAudioThreadState() const
{
return audioThreadState.get();
}
//==============================================================================
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::Nodes::getNodeForId (NodeID nodeID) const
AudioProcessorGraph::Node::Ptr Nodes::getNodeForId (NodeID nodeID) const
{
const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
return iter != array.end() && (*iter)->nodeID == nodeID ? *iter : nullptr;
}
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::Nodes::addNode (std::unique_ptr<AudioProcessor> newProcessor,
const NodeID nodeID)
AudioProcessorGraph::Node::Ptr Nodes::addNode (std::unique_ptr<AudioProcessor> newProcessor,
const NodeID nodeID)
{
if (newProcessor == nullptr)
{
@ -1031,7 +1209,7 @@ AudioProcessorGraph::Node::Ptr AudioProcessorGraph::Nodes::addNode (std::unique_
new Node { nodeID, std::move (newProcessor) });
}
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::Nodes::removeNode (NodeID nodeID)
AudioProcessorGraph::Node::Ptr Nodes::removeNode (NodeID nodeID)
{
const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
return iter != array.end() && (*iter)->nodeID == nodeID
@ -1040,7 +1218,7 @@ AudioProcessorGraph::Node::Ptr AudioProcessorGraph::Nodes::removeNode (NodeID no
}
//==============================================================================
bool AudioProcessorGraph::Connections::addConnection (const Nodes& n, const Connection& c)
bool Connections::addConnection (const Nodes& n, const Connection& c)
{
if (! canConnect (n, c))
return false;
@ -1050,13 +1228,13 @@ bool AudioProcessorGraph::Connections::addConnection (const Nodes& n, const Conn
return true;
}
bool AudioProcessorGraph::Connections::removeConnection (const Connection& c)
bool Connections::removeConnection (const Connection& c)
{
const auto iter = sourcesForDestination.find (c.destination);
return iter != sourcesForDestination.cend() && iter->second.erase (c.source) == 1;
}
bool AudioProcessorGraph::Connections::removeIllegalConnections (const Nodes& n)
bool Connections::removeIllegalConnections (const Nodes& n)
{
auto anyRemoved = false;
@ -1070,7 +1248,7 @@ bool AudioProcessorGraph::Connections::removeIllegalConnections (const Nodes& n)
return anyRemoved;
}
bool AudioProcessorGraph::Connections::disconnectNode (NodeID n)
bool Connections::disconnectNode (NodeID n)
{
const auto matchingDestinations = getMatchingDestinations (n);
auto result = matchingDestinations.first != matchingDestinations.second;
@ -1086,7 +1264,7 @@ bool AudioProcessorGraph::Connections::disconnectNode (NodeID n)
return result;
}
bool AudioProcessorGraph::Connections::isConnectionLegal (const Nodes& n, Connection c)
bool Connections::isConnectionLegal (const Nodes& n, Connection c)
{
const auto source = n.getNodeForId (c.source .nodeID);
const auto dest = n.getNodeForId (c.destination.nodeID);
@ -1094,8 +1272,8 @@ bool AudioProcessorGraph::Connections::isConnectionLegal (const Nodes& n, Connec
const auto sourceChannel = c.source .channelIndex;
const auto destChannel = c.destination.channelIndex;
const auto sourceIsMIDI = midiChannelIndex == sourceChannel;
const auto destIsMIDI = midiChannelIndex == destChannel;
const auto sourceIsMIDI = AudioProcessorGraph::midiChannelIndex == sourceChannel;
const auto destIsMIDI = AudioProcessorGraph::midiChannelIndex == destChannel;
return sourceChannel >= 0
&& destChannel >= 0
@ -1111,12 +1289,12 @@ bool AudioProcessorGraph::Connections::isConnectionLegal (const Nodes& n, Connec
: destChannel < dest->getProcessor()->getTotalNumInputChannels());
}
bool AudioProcessorGraph::Connections::canConnect (const Nodes& n, Connection c) const
bool Connections::canConnect (const Nodes& n, Connection c) const
{
return isConnectionLegal (n, c) && ! isConnected (c);
}
bool AudioProcessorGraph::Connections::isConnected (Connection c) const
bool Connections::isConnected (Connection c) const
{
const auto iter = sourcesForDestination.find (c.destination);
@ -1124,7 +1302,7 @@ bool AudioProcessorGraph::Connections::isConnected (Connection c) const
&& iter->second.find (c.source) != iter->second.cend();
}
bool AudioProcessorGraph::Connections::isConnected (NodeID srcID, NodeID destID) const
bool Connections::isConnected (NodeID srcID, NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
@ -1135,7 +1313,7 @@ bool AudioProcessorGraph::Connections::isConnected (NodeID srcID, NodeID destID)
});
}
std::set<AudioProcessorGraph::NodeID> AudioProcessorGraph::Connections::getSourceNodesForDestination (NodeID destID) const
std::set<AudioProcessorGraph::NodeID> Connections::getSourceNodesForDestination (NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
@ -1148,13 +1326,13 @@ std::set<AudioProcessorGraph::NodeID> AudioProcessorGraph::Connections::getSourc
return result;
}
std::set<AudioProcessorGraph::NodeAndChannel> AudioProcessorGraph::Connections::getSourcesForDestination (const NodeAndChannel& p) const
std::set<AudioProcessorGraph::NodeAndChannel> Connections::getSourcesForDestination (const NodeAndChannel& p) const
{
const auto iter = sourcesForDestination.find (p);
return iter != sourcesForDestination.cend() ? iter->second : std::set<NodeAndChannel>{};
}
std::vector<AudioProcessorGraph::Connection> AudioProcessorGraph::Connections::getConnections() const
std::vector<AudioProcessorGraph::Connection> Connections::getConnections() const
{
std::vector<Connection> result;
@ -1167,12 +1345,12 @@ std::vector<AudioProcessorGraph::Connection> AudioProcessorGraph::Connections::g
return result;
}
bool AudioProcessorGraph::Connections::isAnInputTo (NodeID source, NodeID dest) const
bool Connections::isAnInputTo (NodeID source, NodeID dest) const
{
return getConnectedRecursive (source, dest, {}).found;
}
AudioProcessorGraph::Connections::SearchState AudioProcessorGraph::Connections::getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const
Connections::SearchState Connections::getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const
{
state.visited.insert (dest);
@ -1188,9 +1366,9 @@ AudioProcessorGraph::Connections::SearchState AudioProcessorGraph::Connections::
return state;
}
std::set<AudioProcessorGraph::NodeAndChannel> AudioProcessorGraph::Connections::removeIllegalConnections (const Nodes& n,
std::set<NodeAndChannel> sources,
NodeAndChannel destination)
std::set<AudioProcessorGraph::NodeAndChannel> Connections::removeIllegalConnections (const Nodes& n,
std::set<NodeAndChannel> sources,
NodeAndChannel destination)
{
for (auto source = sources.cbegin(); source != sources.cend();)
{
@ -1203,9 +1381,9 @@ std::set<AudioProcessorGraph::NodeAndChannel> AudioProcessorGraph::Connections::
return sources;
}
std::pair<AudioProcessorGraph::Connections::Map::const_iterator,
AudioProcessorGraph::Connections::Map::const_iterator>
AudioProcessorGraph::Connections::getMatchingDestinations (NodeID destID) const
std::pair<Connections::Map::const_iterator,
Connections::Map::const_iterator>
Connections::getMatchingDestinations (NodeID destID) const
{
return std::equal_range (sourcesForDestination.cbegin(), sourcesForDestination.cend(), destID, ImplicitNode::compare);
}
@ -1240,75 +1418,281 @@ bool AudioProcessorGraph::Connection::operator< (const Connection& other) const
}
//==============================================================================
AudioProcessorGraph::AudioProcessorGraph() = default;
AudioProcessorGraph::~AudioProcessorGraph()
class AudioProcessorGraph::Pimpl : private AsyncUpdater
{
cancelPendingUpdate();
clear();
}
public:
explicit Pimpl (AudioProcessorGraph& o) : owner (&o) {}
const String AudioProcessorGraph::getName() const
{
return "Audio Graph";
}
//==============================================================================
void AudioProcessorGraph::topologyChanged (UpdateKind updateKind)
{
sendChangeMessage();
if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
else
triggerAsyncUpdate();
}
void AudioProcessorGraph::clear (UpdateKind updateKind)
{
if (nodes.getNodes().isEmpty())
return;
nodes = Nodes{};
connections = Connections{};
topologyChanged (updateKind);
}
AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID nodeID) const
{
return nodes.getNodeForId (nodeID);
}
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::addNode (std::unique_ptr<AudioProcessor> newProcessor, const NodeID nodeID, UpdateKind updateKind)
{
if (newProcessor.get() == this)
~Pimpl() override
{
jassertfalse;
return nullptr;
cancelPendingUpdate();
clear (UpdateKind::sync);
}
const auto idToUse = nodeID == NodeID() ? NodeID { ++(lastNodeID.uid) } : nodeID;
const auto& getNodes() const { return nodes.getNodes(); }
auto added = nodes.addNode (std::move (newProcessor), idToUse);
void clear (UpdateKind updateKind)
{
if (getNodes().isEmpty())
return;
if (added == nullptr)
return nullptr;
nodes = Nodes{};
connections = Connections{};
topologyChanged (updateKind);
}
if (lastNodeID < idToUse)
lastNodeID = idToUse;
auto getNodeForId (NodeID nodeID) const
{
return nodes.getNodeForId (nodeID);
}
if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (added->getProcessor()))
ioProc->setParentGraph (this);
Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor,
const NodeID nodeID,
UpdateKind updateKind)
{
if (newProcessor.get() == owner)
{
jassertfalse;
return nullptr;
}
topologyChanged (updateKind);
return added;
const auto idToUse = nodeID == NodeID() ? NodeID { ++(lastNodeID.uid) } : nodeID;
auto added = nodes.addNode (std::move (newProcessor), idToUse);
if (added == nullptr)
return nullptr;
if (lastNodeID < idToUse)
lastNodeID = idToUse;
if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (added->getProcessor()))
ioProc->setParentGraph (owner);
topologyChanged (updateKind);
return added;
}
Node::Ptr removeNode (NodeID nodeID, UpdateKind updateKind)
{
connections.disconnectNode (nodeID);
auto result = nodes.removeNode (nodeID);
topologyChanged (updateKind);
return result;
}
std::vector<Connection> getConnections() const
{
return connections.getConnections();
}
bool isConnected (const Connection& c) const
{
return connections.isConnected (c);
}
bool isConnected (NodeID srcID, NodeID destID) const
{
return connections.isConnected (srcID, destID);
}
bool isAnInputTo (Node& src, Node& dst) const
{
return connections.isAnInputTo (src.nodeID, dst.nodeID);
}
bool canConnect (const Connection& c) const
{
return connections.canConnect (nodes, c);
}
bool addConnection (const Connection& c, UpdateKind updateKind)
{
if (! connections.addConnection (nodes, c))
return false;
jassert (isConnected (c));
topologyChanged (updateKind);
return true;
}
bool removeConnection (const Connection& c, UpdateKind updateKind)
{
if (! connections.removeConnection (c))
return false;
topologyChanged (updateKind);
return true;
}
bool disconnectNode (NodeID nodeID, UpdateKind updateKind)
{
if (! connections.disconnectNode (nodeID))
return false;
topologyChanged (updateKind);
return true;
}
bool isConnectionLegal (const Connection& c) const
{
return connections.isConnectionLegal (nodes, c);
}
bool removeIllegalConnections (UpdateKind updateKind)
{
const auto result = connections.removeIllegalConnections (nodes);
topologyChanged (updateKind);
return result;
}
//==============================================================================
void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock)
{
owner->setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock);
PrepareSettings settings;
settings.precision = owner->getProcessingPrecision();
settings.sampleRate = sampleRate;
settings.blockSize = estimatedSamplesPerBlock;
nodeStates.setState (settings);
topologyChanged (UpdateKind::sync);
}
void releaseResources()
{
nodeStates.setState (nullopt);
topologyChanged (UpdateKind::sync);
}
void reset()
{
for (auto* n : getNodes())
n->getProcessor()->reset();
}
void setNonRealtime (bool isProcessingNonRealtime)
{
for (auto* n : getNodes())
n->getProcessor()->setNonRealtime (isProcessingNonRealtime);
}
template <typename Value>
void processBlock (AudioBuffer<Value>& audio, MidiBuffer& midi, AudioPlayHead* playHead)
{
renderSequenceExchange.updateAudioThreadState();
if (renderSequenceExchange.getAudioThreadState() == nullptr && MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
if (owner->isNonRealtime())
{
while (renderSequenceExchange.getAudioThreadState() == nullptr)
{
Thread::sleep (1);
renderSequenceExchange.updateAudioThreadState();
}
}
auto* state = renderSequenceExchange.getAudioThreadState();
// Only process if the graph has the correct blockSize, sampleRate etc.
if (state != nullptr && state->getSettings() == nodeStates.getLastRequestedSettings())
{
state->process (audio, midi, playHead);
}
else
{
audio.clear();
midi.clear();
}
}
/* Call from the audio thread only. */
auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); }
private:
void topologyChanged (UpdateKind updateKind)
{
owner->sendChangeMessage();
if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
else
triggerAsyncUpdate();
}
void handleAsyncUpdate() override
{
if (const auto newSettings = nodeStates.applySettings (nodes))
{
auto sequence = std::make_unique<RenderSequence> (*newSettings, nodes, connections);
owner->setLatencySamples (sequence->getLatencySamples());
renderSequenceExchange.set (std::move (sequence));
}
else
{
renderSequenceExchange.set (nullptr);
}
}
AudioProcessorGraph* owner = nullptr;
Nodes nodes;
Connections connections;
NodeStates nodeStates;
RenderSequenceExchange renderSequenceExchange;
NodeID lastNodeID;
};
//==============================================================================
AudioProcessorGraph::AudioProcessorGraph() : pimpl (std::make_unique<Pimpl> (*this)) {}
AudioProcessorGraph::~AudioProcessorGraph() = default;
const String AudioProcessorGraph::getName() const { return "Audio Graph"; }
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const { return true; }
double AudioProcessorGraph::getTailLengthSeconds() const { return 0; }
bool AudioProcessorGraph::acceptsMidi() const { return true; }
bool AudioProcessorGraph::producesMidi() const { return true; }
void AudioProcessorGraph::getStateInformation (MemoryBlock&) {}
void AudioProcessorGraph::setStateInformation (const void*, int) {}
void AudioProcessorGraph::processBlock (AudioBuffer<float>& audio, MidiBuffer& midi) { return pimpl->processBlock (audio, midi, getPlayHead()); }
void AudioProcessorGraph::processBlock (AudioBuffer<double>& audio, MidiBuffer& midi) { return pimpl->processBlock (audio, midi, getPlayHead()); }
std::vector<AudioProcessorGraph::Connection> AudioProcessorGraph::getConnections() const { return pimpl->getConnections(); }
bool AudioProcessorGraph::addConnection (const Connection& c, UpdateKind updateKind) { return pimpl->addConnection (c, updateKind); }
bool AudioProcessorGraph::removeConnection (const Connection& c, UpdateKind updateKind) { return pimpl->removeConnection (c, updateKind); }
void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) { return pimpl->prepareToPlay (sampleRate, estimatedSamplesPerBlock); }
void AudioProcessorGraph::clear (UpdateKind updateKind) { return pimpl->clear (updateKind); }
const ReferenceCountedArray<AudioProcessorGraph::Node>& AudioProcessorGraph::getNodes() const noexcept { return pimpl->getNodes(); }
AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID x) const { return pimpl->getNodeForId (x).get(); }
bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind) { return pimpl->disconnectNode (nodeID, updateKind); }
void AudioProcessorGraph::releaseResources() { return pimpl->releaseResources(); }
bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind) { return pimpl->removeIllegalConnections (updateKind); }
void AudioProcessorGraph::reset() { return pimpl->reset(); }
bool AudioProcessorGraph::canConnect (const Connection& c) const { return pimpl->canConnect (c); }
bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { return pimpl->isConnected (c); }
bool AudioProcessorGraph::isConnected (NodeID a, NodeID b) const noexcept { return pimpl->isConnected (a, b); }
bool AudioProcessorGraph::isConnectionLegal (const Connection& c) const { return pimpl->isConnectionLegal (c); }
bool AudioProcessorGraph::isAnInputTo (Node& source, Node& destination) const noexcept { return pimpl->isAnInputTo (source, destination); }
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::addNode (std::unique_ptr<AudioProcessor> newProcessor,
NodeID nodeId,
UpdateKind updateKind)
{
return pimpl->addNode (std::move (newProcessor), nodeId, updateKind);
}
void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept
{
AudioProcessor::setNonRealtime (isProcessingNonRealtime);
pimpl->setNonRealtime (isProcessingNonRealtime);
}
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::removeNode (NodeID nodeID, UpdateKind updateKind)
{
auto result = nodes.removeNode (nodeID);
topologyChanged (updateKind);
return result;
return pimpl->removeNode (nodeID, updateKind);
}
AudioProcessorGraph::Node::Ptr AudioProcessorGraph::removeNode (Node* node, UpdateKind updateKind)
@ -1320,176 +1704,6 @@ AudioProcessorGraph::Node::Ptr AudioProcessorGraph::removeNode (Node* node, Upda
return {};
}
//==============================================================================
std::vector<AudioProcessorGraph::Connection> AudioProcessorGraph::getConnections() const
{
return connections.getConnections();
}
bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept
{
return connections.isConnected (c);
}
bool AudioProcessorGraph::isConnected (NodeID srcID, NodeID destID) const noexcept
{
return connections.isConnected (srcID, destID);
}
bool AudioProcessorGraph::isAnInputTo (Node& src, Node& dst) const noexcept
{
return connections.isAnInputTo (src.nodeID, dst.nodeID);
}
bool AudioProcessorGraph::canConnect (const Connection& c) const
{
return connections.canConnect (nodes, c);
}
bool AudioProcessorGraph::addConnection (const Connection& c, UpdateKind updateKind)
{
if (! connections.addConnection (nodes, c))
return false;
jassert (isConnected (c));
topologyChanged (updateKind);
return true;
}
bool AudioProcessorGraph::removeConnection (const Connection& c, UpdateKind updateKind)
{
if (! connections.removeConnection (c))
return false;
topologyChanged (updateKind);
return true;
}
bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind)
{
if (! connections.disconnectNode (nodeID))
return false;
topologyChanged (updateKind);
return true;
}
bool AudioProcessorGraph::isConnectionLegal (const Connection& c) const
{
return connections.isConnectionLegal (nodes, c);
}
bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind)
{
auto result = connections.removeIllegalConnections (nodes);
topologyChanged (updateKind);
return result;
}
//==============================================================================
void AudioProcessorGraph::handleAsyncUpdate()
{
if (const auto newSettings = nodeStates.applySettings (nodes))
{
auto sequence = std::make_unique<RenderSequence> (*newSettings, nodes, connections);
setLatencySamples (sequence->getLatencySamples());
renderSequenceExchange.set (std::move (sequence));
}
else
{
renderSequenceExchange.set (nullptr);
}
}
//==============================================================================
void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamplesPerBlock)
{
setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock);
nodeStates.setState ([&]
{
PrepareSettings settings;
settings.precision = getProcessingPrecision();
settings.sampleRate = sampleRate;
settings.blockSize = estimatedSamplesPerBlock;
return settings;
}());
topologyChanged (UpdateKind::sync);
}
void AudioProcessorGraph::releaseResources()
{
nodeStates.setState (nullopt);
topologyChanged (UpdateKind::sync);
}
void AudioProcessorGraph::reset()
{
for (auto* n : getNodes())
n->getProcessor()->reset();
}
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const
{
return true;
}
void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept
{
AudioProcessor::setNonRealtime (isProcessingNonRealtime);
for (auto* n : getNodes())
n->getProcessor()->setNonRealtime (isProcessingNonRealtime);
}
double AudioProcessorGraph::getTailLengthSeconds() const { return 0; }
bool AudioProcessorGraph::acceptsMidi() const { return true; }
bool AudioProcessorGraph::producesMidi() const { return true; }
void AudioProcessorGraph::getStateInformation (MemoryBlock&) {}
void AudioProcessorGraph::setStateInformation (const void*, int) {}
template <typename Value>
void AudioProcessorGraph::processBlockImpl (AudioBuffer<Value>& audio, MidiBuffer& midi)
{
renderSequenceExchange.updateAudioThreadState();
if (renderSequenceExchange.getAudioThreadState() == nullptr && MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
if (isNonRealtime())
{
while (renderSequenceExchange.getAudioThreadState() == nullptr)
{
Thread::sleep (1);
renderSequenceExchange.updateAudioThreadState();
}
}
auto* state = renderSequenceExchange.getAudioThreadState();
// Only process if the graph has the correct blockSize, sampleRate etc.
if (state != nullptr && state->getSettings() == nodeStates.getLastRequestedSettings())
{
state->process (audio, midi, playHead);
}
else
{
audio.clear();
midi.clear();
}
}
void AudioProcessorGraph::processBlock (AudioBuffer<float>& audio, MidiBuffer& midi)
{
processBlockImpl (audio, midi);
}
void AudioProcessorGraph::processBlock (AudioBuffer<double>& audio, MidiBuffer& midi)
{
processBlockImpl (audio, midi);
}
//==============================================================================
AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType)
: type (deviceType)
@ -1552,7 +1766,7 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<float
{
jassert (graph != nullptr);
if (auto* state = graph->renderSequenceExchange.getAudioThreadState())
if (auto* state = graph->pimpl->getAudioThreadState())
state->processIO (*this, buffer, midiMessages);
}
@ -1560,7 +1774,7 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<doubl
{
jassert (graph != nullptr);
if (auto* state = graph->renderSequenceExchange.getAudioThreadState())
if (auto* state = graph->pimpl->getAudioThreadState())
state->processIO (*this, buffer, midiMessages);
}

View file

@ -43,8 +43,7 @@ namespace juce
@tags{Audio}
*/
class JUCE_API AudioProcessorGraph : public AudioProcessor,
public ChangeBroadcaster,
private AsyncUpdater
public ChangeBroadcaster
{
public:
//==============================================================================
@ -222,7 +221,7 @@ public:
void clear (UpdateKind = UpdateKind::sync);
/** Returns the array of nodes in the graph. */
const ReferenceCountedArray<Node>& getNodes() const noexcept { return nodes.getNodes(); }
const ReferenceCountedArray<Node>& getNodes() const noexcept;
/** Returns the number of nodes in the graph. */
int getNumNodes() const noexcept { return getNodes().size(); }
@ -420,169 +419,8 @@ public:
void setStateInformation (const void* data, int sizeInBytes) override;
private:
class ImplicitNode
{
public:
// Implicit conversions for use with compareNodes in equal_range, lower_bound etc.
ImplicitNode (NodeID x) : node (x) {}
ImplicitNode (NodeAndChannel x) : ImplicitNode (x.nodeID) {}
ImplicitNode (const Node* x) : ImplicitNode (x->nodeID) {}
ImplicitNode (const std::pair<const NodeAndChannel, std::set<NodeAndChannel>>& x) : ImplicitNode (x.first) {}
static bool compare (ImplicitNode a, ImplicitNode b) { return a.node < b.node; }
private:
NodeID node;
};
/* A copyable type holding all the nodes, and allowing fast lookup by id. */
class Nodes
{
public:
const ReferenceCountedArray<Node>& getNodes() const { return array; }
Node::Ptr getNodeForId (NodeID x) const;
Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor, const NodeID nodeID);
Node::Ptr removeNode (NodeID nodeID);
// If the arrays match, then the maps must also match.
bool operator== (const Nodes& other) const { return array == other.array; }
bool operator!= (const Nodes& other) const { return array != other.array; }
private:
ReferenceCountedArray<Node> array;
};
/* A value type holding a full set of graph connections. */
class Connections
{
public:
bool addConnection (const Nodes& n, const Connection& c);
bool removeConnection (const Connection& c);
bool removeIllegalConnections (const Nodes& n);
bool disconnectNode (NodeID n);
static bool isConnectionLegal (const Nodes& n, Connection c);
bool canConnect (const Nodes& n, Connection c) const;
bool isConnected (Connection c) const;
bool isConnected (NodeID srcID, NodeID destID) const;
std::set<NodeID> getSourceNodesForDestination (NodeID destID) const;
std::set<NodeAndChannel> getSourcesForDestination (const NodeAndChannel& p) const;
std::vector<AudioProcessorGraph::Connection> getConnections() const;
bool isAnInputTo (NodeID source, NodeID dest) const;
bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; }
bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; }
private:
struct SearchState
{
std::set<NodeID> visited;
bool found = false;
};
using Map = std::map<NodeAndChannel, std::set<NodeAndChannel>>;
SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const;
std::set<NodeAndChannel> removeIllegalConnections (const Nodes& n, std::set<NodeAndChannel>, NodeAndChannel);
std::pair<Map::const_iterator, Map::const_iterator> getMatchingDestinations (NodeID destID) const;
Map sourcesForDestination;
};
/* Settings used to prepare a node for playback. */
struct PrepareSettings
{
ProcessingPrecision precision = ProcessingPrecision::singlePrecision;
double sampleRate = 0.0;
int blockSize = 0;
auto tie() const noexcept { return std::tie (precision, sampleRate, blockSize); }
bool operator== (const PrepareSettings& other) const { return tie() == other.tie(); }
bool operator!= (const PrepareSettings& other) const { return tie() != other.tie(); }
};
/* Keeps track of the PrepareSettings applied to each node. */
class NodeStates
{
public:
/* Called from prepareToPlay and releaseResources with the PrepareSettings that should be
used next time the graph is rebuilt.
*/
void setState (Optional<PrepareSettings> newSettings);
/* Call from the audio thread only.
*/
Optional<PrepareSettings> getLastRequestedSettings() const { return next; }
/* Called after updating the graph topology (on the main thread) to prepare any currently-
unprepared nodes.
To ensure that all nodes are initialised with the same sample rate, buffer size, etc. as
the enclosing graph, we must ensure that any operation that uses these details (preparing
individual nodes) is synchronized with prepare-to-play and release-resources on the
enclosing graph.
If the new PrepareSettings are different to the last-seen settings, all nodes will
be prepared/unprepared as necessary. If the PrepareSettings have not changed, then only
new nodes will be prepared/unprepared.
Returns the settings that were applied to the nodes.
*/
Optional<PrepareSettings> applySettings (const Nodes& nodes);
private:
std::mutex mutex; // protects 'next'
std::set<NodeID> preparedNodes;
Optional<PrepareSettings> current, next;
};
class RenderSequenceBuilder;
class RenderSequence;
/* Facilitates wait-free render-sequence updates.
Topology updates always happen on the main thread (or synchronised with the main thread).
After updating the graph, the 'baked' graph is passed to RenderSequenceExchange::set.
At the top of the audio callback, RenderSequenceExchange::get will return the render
sequence to use for that callback.
*/
class RenderSequenceExchange
{
public:
RenderSequenceExchange();
~RenderSequenceExchange();
void set (std::unique_ptr<RenderSequence>&&);
/** Call from the audio thread only. */
void updateAudioThreadState();
/** Call from the audio thread only. */
RenderSequence* getAudioThreadState() const;
private:
SpinLock mutex;
std::unique_ptr<RenderSequence> mainThreadState, audioThreadState;
bool isNew = false;
};
//==============================================================================
// These members represent the logical state of the graph, which can be queried and
// updated from the main thread. The actual playback state of the graph may lag behind
// this state a little.
Nodes nodes;
Connections connections;
NodeID lastNodeID = {};
NodeStates nodeStates;
RenderSequenceExchange renderSequenceExchange;
template <typename Value>
void processBlockImpl (AudioBuffer<Value>&, MidiBuffer&);
void topologyChanged (UpdateKind);
void handleAsyncUpdate() override;
class Pimpl;
std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorGraph)
};