Browse Source

AudioProcessorGraph: Ensure graph is rebuilt if any node latencies change

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

+ 75
- 9
modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp View File

@@ -1046,12 +1046,13 @@ private:
enum { readOnlyEmptyBufferIndex = 0 }; enum { readOnlyEmptyBufferIndex = 0 };
HashMap<uint32, int> delays;
std::unordered_map<uint32, int> delays;
int totalLatency = 0; int totalLatency = 0;
int getNodeDelay (NodeID nodeID) const noexcept int getNodeDelay (NodeID nodeID) const noexcept
{ {
return delays[nodeID.uid];
const auto iter = delays.find (nodeID.uid);
return iter != delays.end() ? iter->second : 0;
} }
int getInputLatencyForNode (const Connections& c, NodeID nodeID) const int getInputLatencyForNode (const Connections& c, NodeID nodeID) const
@@ -1379,7 +1380,7 @@ private:
auto totalChans = jmax (numIns, numOuts); auto totalChans = jmax (numIns, numOuts);
Array<int> audioChannelsToUse; Array<int> audioChannelsToUse;
auto maxLatency = getInputLatencyForNode (c, node.nodeID);
const auto maxInputLatency = getInputLatencyForNode (c, node.nodeID);
for (int inputChan = 0; inputChan < numIns; ++inputChan) for (int inputChan = 0; inputChan < numIns; ++inputChan)
{ {
@@ -1390,7 +1391,7 @@ private:
node, node,
inputChan, inputChan,
ourRenderingIndex, ourRenderingIndex,
maxLatency);
maxInputLatency);
jassert (index >= 0); jassert (index >= 0);
audioChannelsToUse.add (index); audioChannelsToUse.add (index);
@@ -1413,10 +1414,11 @@ private:
if (processor.producesMidi()) if (processor.producesMidi())
midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, midiChannelIndex }; midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, midiChannelIndex };
delays.set (node.nodeID.uid, maxLatency + processor.getLatencySamples());
const auto thisNodeLatency = maxInputLatency + processor.getLatencySamples();
delays[node.nodeID.uid] = thisNodeLatency;
if (numOuts == 0) if (numOuts == 0)
totalLatency = maxLatency;
totalLatency = jmax (totalLatency, thisNodeLatency);
sequence.addProcessOp (node, audioChannelsToUse, totalChans, midiBufferToUse); sequence.addProcessOp (node, audioChannelsToUse, totalChans, midiBufferToUse);
} }
@@ -1548,6 +1550,25 @@ private:
SequenceAndLatency sequence; SequenceAndLatency sequence;
}; };
//==============================================================================
/* Holds information about the properties of a graph node at the point it was prepared.
If the bus layout or latency of a given node changes, the graph should be rebuilt so
that channel connections are ordered correctly, and the graph's internal delay lines have
the correct delay.
*/
class NodeAttributes
{
auto tie() const { return std::tie (layout, latencySamples); }
public:
AudioProcessor::BusesLayout layout;
int latencySamples = 0;
bool operator== (const NodeAttributes& other) const { return tie() == other.tie(); }
bool operator!= (const NodeAttributes& other) const { return tie() != other.tie(); }
};
//============================================================================== //==============================================================================
/* Holds information about a particular graph configuration, without sharing ownership of any /* Holds information about a particular graph configuration, without sharing ownership of any
graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see
@@ -1565,7 +1586,7 @@ public:
bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); } bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); }
private: private:
using NodeMap = std::map<AudioProcessorGraph::NodeID, AudioProcessor::BusesLayout>;
using NodeMap = std::map<AudioProcessorGraph::NodeID, NodeAttributes>;
static NodeMap getNodeMap (const Nodes& n) static NodeMap getNodeMap (const Nodes& n)
{ {
@@ -1573,7 +1594,12 @@ private:
NodeMap result; NodeMap result;
for (const auto& node : nodeRefs) for (const auto& node : nodeRefs)
result.emplace (node->nodeID, node->getProcessor()->getBusesLayout());
{
auto* proc = node->getProcessor();
result.emplace (node->nodeID,
NodeAttributes { proc->getBusesLayout(),
proc->getLatencySamples() });
}
return result; return result;
} }
@@ -1912,7 +1938,6 @@ private:
} }
} }
AudioProcessorGraph* owner = nullptr; AudioProcessorGraph* owner = nullptr;
Nodes nodes; Nodes nodes;
Connections connections; Connections connections;
@@ -2204,6 +2229,42 @@ public:
} }
} }
beginTest ("rebuilding the graph recalculates overall latency");
{
AudioProcessorGraph graph;
const auto nodeA = graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(), MidiIn::no, MidiOut::no))->nodeID;
const auto nodeB = graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(), MidiIn::no, MidiOut::no))->nodeID;
const auto final = graph.addNode (BasicProcessor::make (BasicProcessor::getInputOnlyProperties(), MidiIn::no, MidiOut::no))->nodeID;
expect (graph.addConnection ({ { nodeA, 0 }, { nodeB, 0 } }));
expect (graph.addConnection ({ { nodeA, 1 }, { nodeB, 1 } }));
expect (graph.addConnection ({ { nodeB, 0 }, { final, 0 } }));
expect (graph.addConnection ({ { nodeB, 1 }, { final, 1 } }));
expect (graph.getLatencySamples() == 0);
// Graph isn't built, latency is 0 if prepareToPlay hasn't been called yet
const auto nodeALatency = 100;
graph.getNodeForId (nodeA)->getProcessor()->setLatencySamples (nodeALatency);
graph.rebuild();
expect (graph.getLatencySamples() == 0);
graph.prepareToPlay (44100, 512);
expect (graph.getLatencySamples() == nodeALatency);
const auto nodeBLatency = 200;
graph.getNodeForId (nodeB)->getProcessor()->setLatencySamples (nodeBLatency);
graph.rebuild();
expect (graph.getLatencySamples() == nodeALatency + nodeBLatency);
const auto finalLatency = 300;
graph.getNodeForId (final)->getProcessor()->setLatencySamples (finalLatency);
graph.rebuild();
expect (graph.getLatencySamples() == nodeALatency + nodeBLatency + finalLatency);
}
beginTest ("large render sequence can be built"); beginTest ("large render sequence can be built");
{ {
AudioProcessorGraph graph; AudioProcessorGraph graph;
@@ -2275,6 +2336,11 @@ private:
return std::make_unique<BasicProcessor> (layout, midiIn, midiOut); return std::make_unique<BasicProcessor> (layout, midiIn, midiOut);
} }
static BusesProperties getInputOnlyProperties()
{
return BusesProperties().withInput ("in", AudioChannelSet::stereo());
}
static BusesProperties getStereoProperties() static BusesProperties getStereoProperties()
{ {
return BusesProperties().withInput ("in", AudioChannelSet::stereo()) return BusesProperties().withInput ("in", AudioChannelSet::stereo())


Loading…
Cancel
Save