Browse Source

Graph: Define functions inline

v7.0.9
reuk 2 years ago
parent
commit
ecdebbd885
No known key found for this signature in database GPG Key ID: 9ADCD339CFC98A11
1 changed files with 281 additions and 334 deletions
  1. +281
    -334
      modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp

+ 281
- 334
modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp View File

@@ -58,10 +58,50 @@ public:
const ReferenceCountedArray<Node>& getNodes() const { return array; }
Node::Ptr getNodeForId (NodeID x) const;
Node::Ptr 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;
}
Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor, const NodeID nodeID)
{
if (newProcessor == nullptr)
{
// Cannot add a null audio processor!
jassertfalse;
return {};
}
if (std::any_of (array.begin(),
array.end(),
[&] (auto* n) { return n->getProcessor() == newProcessor.get(); }))
{
// This audio processor has already been added to the graph!
jassertfalse;
return {};
}
const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
if (iter != array.end() && (*iter)->nodeID == nodeID)
{
// This nodeID has already been used for a node in the graph!
jassertfalse;
return {};
}
Node::Ptr addNode (std::unique_ptr<AudioProcessor> newProcessor, const NodeID nodeID);
Node::Ptr removeNode (NodeID nodeID);
return array.insert ((int) std::distance (array.begin(), iter),
new Node { nodeID, std::move (newProcessor) });
}
Node::Ptr removeNode (NodeID nodeID)
{
const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
return iter != array.end() && (*iter)->nodeID == nodeID
? array.removeAndReturn ((int) std::distance (array.begin(), iter))
: nullptr;
}
bool operator== (const Nodes& other) const { return array == other.array; }
bool operator!= (const Nodes& other) const { return array != other.array; }
@@ -80,19 +120,137 @@ public:
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);
bool addConnection (const Nodes& n, const Connection& c)
{
if (! canConnect (n, c))
return false;
sourcesForDestination[c.destination].insert (c.source);
jassert (isConnected (c));
return true;
}
bool removeConnection (const Connection& c)
{
const auto iter = sourcesForDestination.find (c.destination);
return iter != sourcesForDestination.cend() && iter->second.erase (c.source) == 1;
}
bool removeIllegalConnections (const Nodes& n)
{
auto anyRemoved = false;
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;
for (auto& dest : sourcesForDestination)
{
const auto initialSize = dest.second.size();
dest.second = removeIllegalConnections (n, std::move (dest.second), dest.first);
anyRemoved |= (dest.second.size() != initialSize);
}
return anyRemoved;
}
bool disconnectNode (NodeID n)
{
const auto matchingDestinations = getMatchingDestinations (n);
auto result = matchingDestinations.first != matchingDestinations.second;
sourcesForDestination.erase (matchingDestinations.first, matchingDestinations.second);
for (auto& pair : sourcesForDestination)
{
const auto range = std::equal_range (pair.second.cbegin(), pair.second.cend(), n, ImplicitNode::compare);
result |= range.first != range.second;
pair.second.erase (range.first, range.second);
}
return result;
}
static bool isConnectionLegal (const Nodes& n, Connection c)
{
const auto source = n.getNodeForId (c.source .nodeID);
const auto dest = n.getNodeForId (c.destination.nodeID);
const auto sourceChannel = c.source .channelIndex;
const auto destChannel = c.destination.channelIndex;
const auto sourceIsMIDI = AudioProcessorGraph::midiChannelIndex == sourceChannel;
const auto destIsMIDI = AudioProcessorGraph::midiChannelIndex == destChannel;
return sourceChannel >= 0
&& destChannel >= 0
&& source != dest
&& sourceIsMIDI == destIsMIDI
&& source != nullptr
&& (sourceIsMIDI
? source->getProcessor()->producesMidi()
: sourceChannel < source->getProcessor()->getTotalNumOutputChannels())
&& dest != nullptr
&& (destIsMIDI
? dest->getProcessor()->acceptsMidi()
: destChannel < dest->getProcessor()->getTotalNumInputChannels());
}
bool canConnect (const Nodes& n, Connection c) const
{
return isConnectionLegal (n, c) && ! isConnected (c);
}
bool isConnected (Connection c) const
{
const auto iter = sourcesForDestination.find (c.destination);
return iter != sourcesForDestination.cend()
&& iter->second.find (c.source) != iter->second.cend();
}
bool isConnected (NodeID srcID, NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
return std::any_of (matchingDestinations.first, matchingDestinations.second, [srcID] (const auto& pair)
{
const auto iter = std::lower_bound (pair.second.cbegin(), pair.second.cend(), srcID, ImplicitNode::compare);
return iter != pair.second.cend() && iter->nodeID == srcID;
});
}
std::set<NodeID> getSourceNodesForDestination (NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
std::set<NodeID> result;
std::for_each (matchingDestinations.first, matchingDestinations.second, [&] (const auto& pair)
{
for (const auto& source : pair.second)
result.insert (source.nodeID);
});
return result;
}
std::set<NodeAndChannel> getSourcesForDestination (const NodeAndChannel& p) const
{
const auto iter = sourcesForDestination.find (p);
return iter != sourcesForDestination.cend() ? iter->second : std::set<NodeAndChannel>{};
}
std::vector<AudioProcessorGraph::Connection> getConnections() const
{
std::vector<Connection> result;
for (auto& pair : sourcesForDestination)
for (const auto& source : pair.second)
result.emplace_back (source, pair.first);
std::sort (result.begin(), result.end());
result.erase (std::unique (result.begin(), result.end()), result.end());
return result;
}
bool isAnInputTo (NodeID source, NodeID dest) const
{
return getConnectedRecursive (source, dest, {}).found;
}
bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; }
bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; }
@@ -106,11 +264,41 @@ private:
bool found = false;
};
SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const;
static std::set<NodeAndChannel> removeIllegalConnections (const Nodes& n,
SearchState getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const
{
state.visited.insert (dest);
for (const auto& s : getSourceNodesForDestination (dest))
{
if (state.found || s == source)
return { std::move (state.visited), true };
if (state.visited.find (s) == state.visited.cend())
state = getConnectedRecursive (source, s, std::move (state));
}
return state;
}
static std::set<NodeAndChannel> removeIllegalConnections (const Nodes& nodes,
std::set<NodeAndChannel> sources,
NodeAndChannel destination);
std::pair<Map::const_iterator, Map::const_iterator> getMatchingDestinations (NodeID destID) const;
NodeAndChannel destination)
{
for (auto source = sources.cbegin(); source != sources.cend();)
{
if (! isConnectionLegal (nodes, { *source, destination }))
source = sources.erase (source);
else
++source;
}
return sources;
}
std::pair<Map::const_iterator, Map::const_iterator> getMatchingDestinations (NodeID destID) const
{
return std::equal_range (sourcesForDestination.cbegin(), sourcesForDestination.cend(), destID, ImplicitNode::compare);
}
Map sourcesForDestination;
};
@@ -142,7 +330,11 @@ public:
/* Called from prepareToPlay and releaseResources with the PrepareSettings that should be
used next time the graph is rebuilt.
*/
void setState (Optional<PrepareSettings> newSettings);
void setState (Optional<PrepareSettings> newSettings)
{
const std::lock_guard<std::mutex> lock (mutex);
next = newSettings;
}
/* Call from the audio thread only. */
Optional<PrepareSettings> getLastRequestedSettings() const { return next; }
@@ -162,74 +354,66 @@ public:
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<PrepareSettings> NodeStates::applySettings (const Nodes& n)
{
const auto settingsChanged = [this]
{
const std::lock_guard<std::mutex> lock (mutex);
const auto result = current != next;
current = next;
return result;
}();
// It may look like releaseResources and prepareToPlay could race with calls to processBlock
// here, because applySettings is called from the main thread, processBlock is called from
// the audio thread (normally), and there's no explicit mutex ensuring that the calls don't
// overlap.
// However, it is part of the AudioProcessor contract that users shall not call
// processBlock, prepareToPlay, and/or releaseResources concurrently. That is, there's an
// implied mutex synchronising these functions on each AudioProcessor.
//
// Inside processBlock, we always ensure that the current RenderSequence's PrepareSettings
// match the graph's settings before attempting to call processBlock on any of the graph
// nodes; as a result, it's impossible to start calling processBlock on a node on the audio
// thread while a render sequence rebuild (including prepareToPlay/releaseResources calls)
// is already in progress here.
//
// Due to the implied mutex between prepareToPlay/releaseResources/processBlock, it's also
// impossible to receive new PrepareSettings and to start a new RenderSequence rebuild while
// a processBlock call is in progress.
if (settingsChanged)
Optional<PrepareSettings> applySettings (const Nodes& n)
{
for (const auto& node : n.getNodes())
node->getProcessor()->releaseResources();
const auto settingsChanged = [this]
{
const std::lock_guard<std::mutex> lock (mutex);
const auto result = current != next;
current = next;
return result;
}();
// It may look like releaseResources and prepareToPlay could race with calls to processBlock
// here, because applySettings is called from the main thread, processBlock is called from
// the audio thread (normally), and there's no explicit mutex ensuring that the calls don't
// overlap.
// However, it is part of the AudioProcessor contract that users shall not call
// processBlock, prepareToPlay, and/or releaseResources concurrently. That is, there's an
// implied mutex synchronising these functions on each AudioProcessor.
//
// Inside processBlock, we always ensure that the current RenderSequence's PrepareSettings
// match the graph's settings before attempting to call processBlock on any of the graph
// nodes; as a result, it's impossible to start calling processBlock on a node on the audio
// thread while a render sequence rebuild (including prepareToPlay/releaseResources calls)
// is already in progress here.
//
// Due to the implied mutex between prepareToPlay/releaseResources/processBlock, it's also
// impossible to receive new PrepareSettings and to start a new RenderSequence rebuild while
// a processBlock call is in progress.
if (settingsChanged)
{
for (const auto& node : n.getNodes())
node->getProcessor()->releaseResources();
preparedNodes.clear();
}
preparedNodes.clear();
}
if (current.hasValue())
{
for (const auto& node : n.getNodes())
if (current.hasValue())
{
if (preparedNodes.find (node->nodeID) != preparedNodes.cend())
continue;
for (const auto& node : n.getNodes())
{
if (preparedNodes.find (node->nodeID) != preparedNodes.cend())
continue;
preparedNodes.insert (node->nodeID);
preparedNodes.insert (node->nodeID);
node->getProcessor()->setProcessingPrecision (node->getProcessor()->supportsDoublePrecisionProcessing() ? current->precision
: AudioProcessor::singlePrecision);
node->getProcessor()->setRateAndBufferSizeDetails (current->sampleRate, current->blockSize);
node->getProcessor()->prepareToPlay (current->sampleRate, current->blockSize);
node->getProcessor()->setProcessingPrecision (node->getProcessor()->supportsDoublePrecisionProcessing() ? current->precision
: AudioProcessor::singlePrecision);
node->getProcessor()->setRateAndBufferSizeDetails (current->sampleRate, current->blockSize);
node->getProcessor()->prepareToPlay (current->sampleRate, current->blockSize);
}
}
return current;
}
return current;
}
private:
std::mutex mutex;
std::set<NodeID> preparedNodes;
Optional<PrepareSettings> current, next;
};
//==============================================================================
template <typename FloatType>
@@ -1123,16 +1307,28 @@ private:
class RenderSequenceExchange
{
public:
RenderSequenceExchange();
~RenderSequenceExchange();
void set (std::unique_ptr<RenderSequence>&&);
void set (std::unique_ptr<RenderSequence>&& next)
{
const SpinLock::ScopedLockType lock (mutex);
mainThreadState = std::move (next);
isNew = true;
}
/** Call from the audio thread only. */
void updateAudioThreadState();
void updateAudioThreadState()
{
const SpinLock::ScopedTryLockType lock (mutex);
if (lock.isLocked() && isNew)
{
// Swap pointers rather than assigning to avoid calling delete here
std::swap (mainThreadState, audioThreadState);
isNew = false;
}
}
/** Call from the audio thread only. */
RenderSequence* getAudioThreadState() const;
RenderSequence* getAudioThreadState() const { return audioThreadState.get(); }
private:
SpinLock mutex;
@@ -1140,255 +1336,6 @@ private:
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 RenderSequenceExchange::updateAudioThreadState()
{
const SpinLock::ScopedTryLockType lock (mutex);
if (lock.isLocked() && isNew)
{
// Swap pointers rather than assigning to avoid calling delete here
std::swap (mainThreadState, audioThreadState);
isNew = false;
}
}
RenderSequence* RenderSequenceExchange::getAudioThreadState() const
{
return audioThreadState.get();
}
//==============================================================================
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 Nodes::addNode (std::unique_ptr<AudioProcessor> newProcessor,
const NodeID nodeID)
{
if (newProcessor == nullptr)
{
// Cannot add a null audio processor!
jassertfalse;
return {};
}
if (std::any_of (array.begin(),
array.end(),
[&] (auto* n) { return n->getProcessor() == newProcessor.get(); }))
{
// This audio processor has already been added to the graph!
jassertfalse;
return {};
}
const auto iter = std::lower_bound (array.begin(), array.end(), nodeID, ImplicitNode::compare);
if (iter != array.end() && (*iter)->nodeID == nodeID)
{
// This nodeID has already been used for a node in the graph!
jassertfalse;
return {};
}
return array.insert ((int) std::distance (array.begin(), iter),
new Node { nodeID, std::move (newProcessor) });
}
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
? array.removeAndReturn ((int) std::distance (array.begin(), iter))
: nullptr;
}
//==============================================================================
bool Connections::addConnection (const Nodes& n, const Connection& c)
{
if (! canConnect (n, c))
return false;
sourcesForDestination[c.destination].insert (c.source);
jassert (isConnected (c));
return true;
}
bool Connections::removeConnection (const Connection& c)
{
const auto iter = sourcesForDestination.find (c.destination);
return iter != sourcesForDestination.cend() && iter->second.erase (c.source) == 1;
}
bool Connections::removeIllegalConnections (const Nodes& n)
{
auto anyRemoved = false;
for (auto& dest : sourcesForDestination)
{
const auto initialSize = dest.second.size();
dest.second = removeIllegalConnections (n, std::move (dest.second), dest.first);
anyRemoved |= (dest.second.size() != initialSize);
}
return anyRemoved;
}
bool Connections::disconnectNode (NodeID n)
{
const auto matchingDestinations = getMatchingDestinations (n);
auto result = matchingDestinations.first != matchingDestinations.second;
sourcesForDestination.erase (matchingDestinations.first, matchingDestinations.second);
for (auto& pair : sourcesForDestination)
{
const auto range = std::equal_range (pair.second.cbegin(), pair.second.cend(), n, ImplicitNode::compare);
result |= range.first != range.second;
pair.second.erase (range.first, range.second);
}
return result;
}
bool Connections::isConnectionLegal (const Nodes& n, Connection c)
{
const auto source = n.getNodeForId (c.source .nodeID);
const auto dest = n.getNodeForId (c.destination.nodeID);
const auto sourceChannel = c.source .channelIndex;
const auto destChannel = c.destination.channelIndex;
const auto sourceIsMIDI = AudioProcessorGraph::midiChannelIndex == sourceChannel;
const auto destIsMIDI = AudioProcessorGraph::midiChannelIndex == destChannel;
return sourceChannel >= 0
&& destChannel >= 0
&& source != dest
&& sourceIsMIDI == destIsMIDI
&& source != nullptr
&& (sourceIsMIDI
? source->getProcessor()->producesMidi()
: sourceChannel < source->getProcessor()->getTotalNumOutputChannels())
&& dest != nullptr
&& (destIsMIDI
? dest->getProcessor()->acceptsMidi()
: destChannel < dest->getProcessor()->getTotalNumInputChannels());
}
bool Connections::canConnect (const Nodes& n, Connection c) const
{
return isConnectionLegal (n, c) && ! isConnected (c);
}
bool Connections::isConnected (Connection c) const
{
const auto iter = sourcesForDestination.find (c.destination);
return iter != sourcesForDestination.cend()
&& iter->second.find (c.source) != iter->second.cend();
}
bool Connections::isConnected (NodeID srcID, NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
return std::any_of (matchingDestinations.first, matchingDestinations.second, [srcID] (const auto& pair)
{
const auto iter = std::lower_bound (pair.second.cbegin(), pair.second.cend(), srcID, ImplicitNode::compare);
return iter != pair.second.cend() && iter->nodeID == srcID;
});
}
std::set<AudioProcessorGraph::NodeID> Connections::getSourceNodesForDestination (NodeID destID) const
{
const auto matchingDestinations = getMatchingDestinations (destID);
std::set<NodeID> result;
std::for_each (matchingDestinations.first, matchingDestinations.second, [&] (const auto& pair)
{
for (const auto& source : pair.second)
result.insert (source.nodeID);
});
return result;
}
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> Connections::getConnections() const
{
std::vector<Connection> result;
for (auto& pair : sourcesForDestination)
for (const auto& source : pair.second)
result.emplace_back (source, pair.first);
std::sort (result.begin(), result.end());
result.erase (std::unique (result.begin(), result.end()), result.end());
return result;
}
bool Connections::isAnInputTo (NodeID source, NodeID dest) const
{
return getConnectedRecursive (source, dest, {}).found;
}
Connections::SearchState Connections::getConnectedRecursive (NodeID source, NodeID dest, SearchState state) const
{
state.visited.insert (dest);
for (const auto& s : getSourceNodesForDestination (dest))
{
if (state.found || s == source)
return { std::move (state.visited), true };
if (state.visited.find (s) == state.visited.cend())
state = getConnectedRecursive (source, s, std::move (state));
}
return state;
}
std::set<AudioProcessorGraph::NodeAndChannel> Connections::removeIllegalConnections (const Nodes& n,
std::set<NodeAndChannel> sources,
NodeAndChannel destination)
{
for (auto source = sources.cbegin(); source != sources.cend();)
{
if (! isConnectionLegal (n, { *source, destination }))
source = sources.erase (source);
else
++source;
}
return sources;
}
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);
}
//==============================================================================
//==============================================================================
AudioProcessorGraph::Connection::Connection (NodeAndChannel src, NodeAndChannel dst) noexcept
: source (src), destination (dst)


Loading…
Cancel
Save