tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_RackNode.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11#pragma once
12
13namespace tracktion { inline namespace engine
14{
15
16//==============================================================================
17//==============================================================================
22{
23public:
25 std::shared_ptr<InputProvider> inputProviderToUse)
26 : input (std::move (inputNode)),
27 inputProvider (std::move (inputProviderToUse))
28 {
29 assert (input);
30 assert (inputProvider);
31 }
32
34 {
35 return { input.get() };
36 }
37
39 {
40 return input->getNodeProperties();
41 }
42
43 bool isReadyToProcess() override
44 {
45 return input->hasProcessed();
46 }
47
51
52 void process (ProcessContext&) override
53 {
54 auto inputs = input->getProcessedOutput();
55 tracktion::graph::sanityCheckView (inputs.audio);
56 inputProvider->setInputs (inputs);
57 }
58
59private:
62};
63
64
65//==============================================================================
66//==============================================================================
68{
69public:
70 InputNode (std::shared_ptr<InputProvider> inputProviderToUse,
71 int numAudioChannels, bool hasMidiInput)
72 : inputProvider (std::move (inputProviderToUse)),
73 numChannels (numAudioChannels),
74 hasMidi (hasMidiInput)
75 {
76 }
77
78 InputNode (std::shared_ptr<InputProvider> inputProviderToUse)
79 : inputProvider (std::move (inputProviderToUse)),
80 numChannels ((int) (inputProvider->numChannels > 0 ? inputProvider->numChannels
81 : inputProvider->audio.getNumChannels())),
82 hasMidi (true)
83 {
84 }
85
86 void setInputDependancy (Node* dependancy)
87 {
88 nodeDependancy = dependancy;
89 }
90
92 {
94 props.hasAudio = numChannels > 0;
95 props.hasMidi = hasMidi;
96 props.numberOfChannels = numChannels;
97 props.nodeID = nodeID;
98
99 if (nodeDependancy)
100 props.latencyNumSamples = nodeDependancy->getNodeProperties().latencyNumSamples;
101
102 return props;
103 }
104
106 {
107 if (nodeDependancy)
108 return { nodeDependancy };
109
110 return {};
111 }
112
113 bool isReadyToProcess() override
114 {
115 return nodeDependancy ? nodeDependancy->hasProcessed()
116 : true;
117 }
118
122
123 void process (ProcessContext& pc) override
124 {
125 auto inputBuffers = inputProvider->getInputs();
126 tracktion::graph::sanityCheckView (inputBuffers.audio);
127
128 if (numChannels > 0)
129 {
130 auto& outputBuffers = pc.buffers;
131
132 // The InputProvider may have less channels than this Node requires so only take the number available
133 auto numInputChannelsToCopy = std::min (inputBuffers.audio.getNumChannels(),
134 outputBuffers.audio.getNumChannels());
135
136 if (numInputChannelsToCopy > 0)
137 {
138 // For testing purposes, the last block might be smaller than the InputProvider
139 // so we'll just take the number of samples required
140 jassert (inputBuffers.audio.getNumFrames() >= outputBuffers.audio.getNumFrames());
141
142 add (outputBuffers.audio.getFirstChannels (numInputChannelsToCopy),
143 inputBuffers.audio.getFirstChannels (numInputChannelsToCopy).getStart (outputBuffers.audio.getNumFrames()));
144 }
145 }
146
147 if (hasMidi)
148 pc.buffers.midi.copyFrom (inputBuffers.midi);
149 }
150
151private:
152 std::shared_ptr<InputProvider> inputProvider;
153 const int numChannels;
154 const bool hasMidi;
155 Node* nodeDependancy = nullptr;
156 const size_t nodeID { (size_t) juce::Random::getSystemRandom().nextInt() };
157};
158
159
160//==============================================================================
161//==============================================================================
164{
165public:
167 EditItemID rackID)
168 : nodes (std::move (nodesToStore)),
169 nodeID ((size_t) rackID.getRawID())
170 {
171 }
172
174 {
175 return { true, true, 0, 0, nodeID };
176 }
177
179 {
180 return {};
181 }
182
183 bool isReadyToProcess() override
184 {
185 return true;
186 }
187
188 void process (ProcessContext&) override
189 {
190 }
191
192private:
194 const size_t nodeID;
195};
196
197
198//==============================================================================
199//==============================================================================
200namespace RackNodeBuilder
201{
202 inline tracktion::graph::ConnectedNode& getConnectedNode (tracktion::graph::Node& pluginOrModifierNode)
203 {
204 tracktion::graph::Node* node = dynamic_cast<PluginNode*> (&pluginOrModifierNode);
205
206 if (node == nullptr)
207 node = dynamic_cast<ModifierNode*> (&pluginOrModifierNode);
208
209 jassert (node != nullptr);
210 auto connectedNode = node->getDirectInputNodes().front();
211 jassert (dynamic_cast<tracktion::graph::ConnectedNode*> (connectedNode) != nullptr);
212 return *dynamic_cast<tracktion::graph::ConnectedNode*> (connectedNode);
213 }
214
222 inline int getDestChannelIndex (tracktion::graph::Node& destPluginOrModifierNode, int destPinIndex)
223 {
224 if (dynamic_cast<PluginNode*> (&destPluginOrModifierNode) != nullptr)
225 return destPinIndex - 1;
226
227 if (auto node = dynamic_cast<ModifierNode*> (&destPluginOrModifierNode))
228 return destPinIndex - node->getModifier().getMidiInputNames().size();
229
231 return -1;
232 }
233
234 static inline tracktion::graph::SummingNode& getSummingNode (tracktion::graph::Node& pluginOrModifierNode)
235 {
236 tracktion::graph::Node* node = dynamic_cast<PluginNode*> (&pluginOrModifierNode);
237
238 if (node == nullptr)
239 node = dynamic_cast<ModifierNode*> (&pluginOrModifierNode);
240
241 jassert (node != nullptr);
242 auto summingNode = node->getDirectInputNodes().front();
243 jassert (dynamic_cast<tracktion::graph::SummingNode*> (summingNode) != nullptr);
244 return *dynamic_cast<tracktion::graph::SummingNode*> (summingNode);
245 }
246
247 struct RackConnectionData
248 {
249 EditItemID sourceID, destID;
250 int sourcePin = -1, destPin = -1;
251 };
252
254 struct RackPinConnections
255 {
256 EditItemID sourceID, destID;
257 std::vector<std::pair<int /*source pin*/, int /*dest pin*/>> pins;
258 };
259
260 struct ChannelMap
261 {
262 std::vector<std::pair<int /*source channel*/, int /*dest channel*/>> channels;
263 bool passMidi = false;
264 };
265
266 static inline int getMaxNumChannels (std::vector<std::pair<int, int>> channels)
267 {
268 int numChannels = 0;
269
270 for (auto c : channels)
271 numChannels = std::max (numChannels,
272 std::max (c.first, c.second) + 1);
273
274 return numChannels;
275 }
276
282 static inline int getNumMidiInputPins (const std::vector<std::unique_ptr<tracktion::graph::Node>>& itemNodes, EditItemID itemID)
283 {
284 for (auto& node : itemNodes)
285 if (auto modifierNode = dynamic_cast<ModifierNode*> (node.get()))
286 if (modifierNode->getModifier().itemID == itemID)
287 return modifierNode->getModifier().getMidiInputNames().size();
288
289 return 1;
290 }
291
292 static inline std::vector<std::pair<int, int>> createPinConnections (std::vector<RackConnectionData> connections)
293 {
294 // Check all the connections are from a single source/dest pair
295 #if JUCE_DEBUG
296 for (auto c : connections)
297 {
298 jassert (c.sourceID == connections.front().sourceID);
299 jassert (c.destID == connections.front().destID);
300 }
301 #endif
302
303 std::vector<std::pair<int, int>> pinConnections;
304
305 for (auto c : connections)
306 pinConnections.push_back ({ c.sourcePin, c.destPin });
307
308 return pinConnections;
309 }
310
311 static inline ChannelMap createChannelMap (std::vector<std::pair<int, int>> pinConnections,
312 int numDestMidiPins)
313 {
314 ChannelMap channelMap;
315
316 for (auto c : pinConnections)
317 {
318 if (c.first == 0 && c.second < numDestMidiPins)
319 channelMap.passMidi = true;
320 else
321 channelMap.channels.push_back ({ c.first - 1, c.second - numDestMidiPins });
322 }
323
324 return channelMap;
325 }
326
327 static inline std::vector<RackConnectionData> getConnectionsBetween (juce::Array<const RackConnection*> allConnections,
328 EditItemID sourceID, EditItemID destID)
329 {
331
332 for (auto c : allConnections)
333 if (c->sourceID == sourceID && c->destID == destID)
334 connections.push_back ({ c->sourceID.get(), c->destID.get(), c->sourcePin.get(), c->destPin.get() });
335
336 return connections;
337 }
338
339 static inline std::vector<std::vector<RackConnectionData>> getConnectionsToOrFrom (RackType& rack, EditItemID itemID, bool itemIsSource)
340 {
342 auto allConnections = rack.getConnections();
344
345 for (auto c : allConnections)
346 {
347 if (itemIsSource && c->sourceID != itemID)
348 continue;
349
350 if (c->destID != itemID)
351 continue;
352
353 auto sourceDest = std::make_pair (c->sourceID.get(), c->destID.get());
354
355 // Skip any sources already done
356 if (std::find (sourcesDestsDone.begin(), sourcesDestsDone.end(), sourceDest) != sourcesDestsDone.end())
357 continue;
358
359 connections.push_back (getConnectionsBetween (allConnections, c->sourceID, c->destID));
360 sourcesDestsDone.push_back (sourceDest);
361 }
362
363 connections.erase (std::remove_if (connections.begin(), connections.end(),
364 [] (auto c) { return c.empty(); }),
365 connections.end());
366
367 return connections;
368 }
369
370 static inline std::vector<std::vector<RackConnectionData>> getConnectionsTo (RackType& rack, EditItemID destID)
371 {
372 return getConnectionsToOrFrom (rack, destID, false);
373 }
374
375 static inline std::vector<std::vector<RackConnectionData>> getConnectionsFrom (RackType& rack, EditItemID sourceID)
376 {
377 return getConnectionsToOrFrom (rack, sourceID, true);
378 }
379
380 //==============================================================================
381 inline std::unique_ptr<tracktion::graph::Node> createRackNodeConnectedNode (RackType& rack,
382 double sampleRate, int blockSize,
384 ProcessState& processState,
385 bool isRendering)
386 {
387 using namespace tracktion::graph;
388
389 std::shared_ptr<Node> inputNode (std::move (inputNodeToUse));
390
391 // Gather all the PluginNodes and ModifierNodes with a ConnectedNode
393
394 jassert (inputNode);
395
396 for (auto plugin : rack.getPlugins())
397 itemNodes[plugin->itemID] = makeNode<PluginNode> (makeNode<ConnectedNode> ((size_t) plugin->itemID.getRawID()),
398 plugin, sampleRate, blockSize, nullptr,
399 processState, isRendering, true, -1);
400
401 for (auto m : rack.getModifierList().getModifiers())
402 itemNodes[m->itemID] = makeNode<ModifierNode> (makeNode<ConnectedNode> ((size_t) m->itemID.getRawID()),
403 m, sampleRate, blockSize, nullptr,
404 processState.playHeadState, isRendering);
405
406 // Create an input node and an output summing node
407 auto outputNode = std::make_unique<ConnectedNode>();
408
409 // Iterate all the connections and make Node connections between them
410 for (const auto& connection : rack.getConnections())
411 {
412 const EditItemID sourceID = connection->sourceID;
413 const EditItemID destID = connection->destID;
414 const int sourcePin = connection->sourcePin;
415 const int destPin = connection->destPin;
416
417 if (sourceID.isInvalid() && destID.isInvalid())
418 {
419 // Direct input -> output connection
420 if (sourcePin == 0 && destPin == 0)
421 outputNode->addMidiConnection (inputNode);
422 else if (sourcePin > 0 && destPin > 0)
423 outputNode->addAudioConnection (inputNode, ChannelConnection { sourcePin - 1, destPin - 1 });
424 else
426 }
427 else if (sourceID.isInvalid() && destID.isValid())
428 {
429 // Input -> item connection
430 if (auto destNode = itemNodes[destID])
431 {
432 auto& destConnectedNode = getConnectedNode (*destNode);
433
434 if (sourcePin == 0 && destPin == 0)
435 destConnectedNode.addMidiConnection (inputNode);
436 else if (sourcePin > 0 && destPin >= 0) // N.B. This has to account for the dest pin being an audio input to a Modifier
437 destConnectedNode.addAudioConnection (inputNode, ChannelConnection { sourcePin - 1, getDestChannelIndex (*destNode, destPin) });
438 else
440 }
441 else
442 {
444 }
445 }
446 else if (sourceID.isValid() && destID.isInvalid())
447 {
448 // Item -> output connection
449 if (auto sourceNode = itemNodes[sourceID])
450 {
451 if (sourcePin == 0 && destPin == 0)
452 outputNode->addMidiConnection (sourceNode);
453 else if (sourcePin > 0 && destPin > 0)
454 outputNode->addAudioConnection (sourceNode, ChannelConnection { sourcePin - 1, destPin - 1 });
455 else
457 }
458 else
459 {
461 }
462 }
463 else if (sourceID.isValid() && destID.isValid())
464 {
465 // Item -> item connection
466 auto sourceNode = itemNodes[sourceID];
467 auto destNode = itemNodes[destID];
468
469 if (sourceNode == nullptr || destNode == nullptr)
470 {
472 }
473 else
474 {
475 auto& destConnectedNode = getConnectedNode (*destNode);
476
477 if (sourcePin == 0 && destPin == 0)
478 destConnectedNode.addMidiConnection (sourceNode);
479 else if (sourcePin > 0 && destPin >= 0) // N.B. This has to account for the dest pin being an audio input to a Modifier
480 destConnectedNode.addAudioConnection (sourceNode, ChannelConnection { sourcePin - 1, getDestChannelIndex (*destNode, destPin) });
481 else
483 }
484 }
485 }
486
487 // Next get any modifiers that aren't connected to the output or another plugin as
488 // they'll still need to be processed but not pass any output on
489 auto finalOutput = std::make_unique<SummingNode>();
490
491 for (auto& node : itemNodes)
492 if (auto modifierNode = dynamic_cast<ModifierNode*> (node.second.get()))
493 if (getConnectionsFrom (rack, modifierNode->getModifier().itemID).empty())
494 finalOutput->addInput (makeNode<ForwardingNode> (node.second));
495
496 if (finalOutput->getDirectInputNodes().size() > 0)
497 {
498 finalOutput->addInput (std::move (outputNode));
499 return finalOutput;
500 }
501
502 return outputNode;
503 }
504
505 //==============================================================================
507 double sampleRate, int blockSize,
509 ProcessState& processState, bool isRendering)
510 {
511 return createRackNodeConnectedNode (rackType,
512 sampleRate, blockSize,
513 std::move (node),
514 processState, isRendering);
515 }
516}
517
518}} // namespace tracktion { inline namespace engine
assert
T begin(T... args)
int nextInt() noexcept
static Random & getSystemRandom() noexcept
Takes ownership of a number of nodes but doesn't do any processing.
void process(ProcessContext &) override
Called when the node is to be processed.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
std::vector< tracktion::graph::Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
std::vector< Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
void process(ProcessContext &pc) override
Called when the node is to be processed.
Fills an InputProvider with output from a Node.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
void process(ProcessContext &) override
Called when the node is to be processed.
std::vector< Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
Node for processing a Modifier.
Node for processing a plugin.
An Node which sums together the multiple inputs adding additional latency to provide a coherent outpu...
Takes a non-owning input node and simply forwards its outputs on.
Main graph Node processor class.
virtual std::vector< Node * > getDirectInputNodes()
Should return all the inputs directly feeding in to this node.
virtual NodeProperties getNodeProperties()=0
Should return the properties of the node.
bool hasProcessed() const
Returns true if this node has processed and its outputs can be retrieved.
Struct to describe a single iteration of a process call.
An Node which sums together the multiple inputs adding additional latency to provide a coherent outpu...
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T front(T... args)
T is_pointer_v
#define jassert(expression)
#define jassertfalse
T make_pair(T... args)
T max(T... args)
T min(T... args)
std::unique_ptr< Node > makeNode(Args &&... args)
Creates a node of the given type and returns it as the base Node class.
T push_back(T... args)
T remove_if(T... args)
T size(T... args)
ID for objects of type EditElement - e.g.
Holds the state of a process call.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...
typedef size_t