1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-02 03:20:06 +00:00

AudioProcessorGraph: Ensure graph is rebuilt if any node latencies change

This commit is contained in:
reuk 2024-01-04 19:37:01 +00:00
parent 5ee9d24e36
commit b8f3030e0b
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C

View file

@ -1046,12 +1046,13 @@ private:
enum { readOnlyEmptyBufferIndex = 0 };
HashMap<uint32, int> delays;
std::unordered_map<uint32, int> delays;
int totalLatency = 0;
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
@ -1379,7 +1380,7 @@ private:
auto totalChans = jmax (numIns, numOuts);
Array<int> audioChannelsToUse;
auto maxLatency = getInputLatencyForNode (c, node.nodeID);
const auto maxInputLatency = getInputLatencyForNode (c, node.nodeID);
for (int inputChan = 0; inputChan < numIns; ++inputChan)
{
@ -1390,7 +1391,7 @@ private:
node,
inputChan,
ourRenderingIndex,
maxLatency);
maxInputLatency);
jassert (index >= 0);
audioChannelsToUse.add (index);
@ -1413,10 +1414,11 @@ private:
if (processor.producesMidi())
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)
totalLatency = maxLatency;
totalLatency = jmax (totalLatency, thisNodeLatency);
sequence.addProcessOp (node, audioChannelsToUse, totalChans, midiBufferToUse);
}
@ -1548,6 +1550,25 @@ private:
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
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(); }
private:
using NodeMap = std::map<AudioProcessorGraph::NodeID, AudioProcessor::BusesLayout>;
using NodeMap = std::map<AudioProcessorGraph::NodeID, NodeAttributes>;
static NodeMap getNodeMap (const Nodes& n)
{
@ -1573,7 +1594,12 @@ private:
NodeMap result;
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;
}
@ -1912,7 +1938,6 @@ private:
}
}
AudioProcessorGraph* owner = nullptr;
Nodes nodes;
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");
{
AudioProcessorGraph graph;
@ -2275,6 +2336,11 @@ private:
return std::make_unique<BasicProcessor> (layout, midiIn, midiOut);
}
static BusesProperties getInputOnlyProperties()
{
return BusesProperties().withInput ("in", AudioChannelSet::stereo());
}
static BusesProperties getStereoProperties()
{
return BusesProperties().withInput ("in", AudioChannelSet::stereo())