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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ConnectedNode.h
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
14
15
16namespace tracktion { inline namespace graph
17{
18
19//==============================================================================
20//==============================================================================
21/*
22 Represents a connection between a source and destination channel.
23 */
25{
26 int sourceChannel = -1;
27 int destChannel = -1;
29 bool operator== (const ChannelConnection& o) const noexcept
30 {
32 && destChannel == o.destChannel;
33 }
34};
35
36
37//==============================================================================
38//==============================================================================
44class ConnectedNode : public Node
45{
46public:
48 ConnectedNode() = default;
49
51 ConnectedNode (size_t nodeID);
52
53 //==============================================================================
59
65
66 //==============================================================================
70 bool isReadyToProcess() override;
71 void prepareToPlay (const PlaybackInitialisationInfo&) override;
72 void process (ProcessContext&) override;
73
74private:
75 struct NodeConnection
76 {
78 bool connectMidi = false;
79 std::vector<ChannelConnection> connectedChannels;
80 };
81
82 static int getMaxDestChannel (const NodeConnection& connection)
83 {
84 int maxChannel = -1;
85
86 for (const auto& channelConnection : connection.connectedChannels)
87 maxChannel = std::max (maxChannel, channelConnection.destChannel);
88
89 return maxChannel;
90 }
91
92 size_t nodeID = 0;
94
95 bool createLatencyNodes();
96};
97
98
99//==============================================================================
100//==============================================================================
101inline ConnectedNode::ConnectedNode (size_t nodeIDToUse)
102 : nodeID (nodeIDToUse)
103{
104}
105
107{
108 jassert (newConnection.sourceChannel >= 0);
109 jassert (newConnection.destChannel >= 0);
110
111 bool cycleDetected = false;
112 visitNodes (*input, [this, &cycleDetected] (auto& n) { cycleDetected = cycleDetected || &n == this; }, false);
113
114 if (cycleDetected)
115 return false;
116
117 // Check for existing connections first
118 for (auto& connection : connections)
119 {
120 if (connection.node.get() == input.get())
121 {
122 for (const auto& channelConnection : connection.connectedChannels)
123 if (newConnection == channelConnection)
124 return false;
125
126 connection.connectedChannels.push_back (newConnection);
127 return true;
128 }
129 }
130
131 // Otherwise add a new connection
132 connections.push_back ({ std::move (input), false, { newConnection } });
133
134 return true;
135}
136
138{
139 bool cycleDetected = false;
140 visitNodes (*input, [this, &cycleDetected] (auto& n) { cycleDetected = cycleDetected || &n == this; }, false);
141
142 if (cycleDetected)
143 return false;
144
145 // Check for existing MIDI connections first
146 for (auto& connection : connections)
147 {
148 if (connection.node.get() == input.get())
149 {
150 if (connection.connectMidi)
151 return false;
152
153 connection.connectMidi = true;
154 return true;
155 }
156 }
157
158 // Otherwise add a new connection
159 connections.push_back ({ std::move (input), true, {} });
160
161 return true;
162}
163
165{
166 NodeProperties props;
167 props.hasAudio = false;
168 props.hasMidi = false;
169 props.numberOfChannels = 0;
170 props.nodeID = nodeID;
171
172 constexpr size_t connectedNodeMagicHash = size_t (0x636f6e6e656374);
173
174 if (props.nodeID != 0)
175 hash_combine (props.nodeID, connectedNodeMagicHash);
176
177 for (const auto& connection : connections)
178 {
179 auto nodeProps = connection.node->getNodeProperties();
180 props.hasAudio = props.hasAudio || ! connection.connectedChannels.empty();
181 props.hasMidi = props.hasMidi || connection.connectMidi;
182 props.numberOfChannels = std::max (props.numberOfChannels, getMaxDestChannel (connection)) + 1;
183 props.latencyNumSamples = std::max (props.latencyNumSamples, nodeProps.latencyNumSamples);
184
185 // Hash inputs
186 hash_combine (props.nodeID, nodeProps.nodeID);
187 hash_combine (props.nodeID, connection.connectMidi);
188
189 for (const auto& channelConnection : connection.connectedChannels)
190 {
191 hash_combine (props.nodeID, channelConnection.sourceChannel);
192 hash_combine (props.nodeID, channelConnection.destChannel);
193 }
194 }
195
196 return props;
197}
198
200{
201 std::vector<Node*> inputs;
202
203 for (const auto& connection : connections)
204 inputs.push_back (connection.node.get());
205
206 return inputs;
207}
208
210{
211 return createLatencyNodes() ? TransformResult::connectionsMade
212 : TransformResult::none;
213}
214
216{
217 for (const auto& connection : connections)
218 if (! connection.node->hasProcessed())
219 return false;
220
221 return true;
222}
223
227
229{
230 auto destAudio = pc.buffers.audio;
231 auto& destMIDI = pc.buffers.midi;
232
233 const int numSamples = (int) pc.referenceSampleRange.getLength();
234 juce::ignoreUnused (numSamples);
235 jassert (destAudio.getNumChannels() == 0 || numSamples == (int) destAudio.getNumFrames());
236
237 for (const auto& connection : connections)
238 {
239 auto sourceOutput = connection.node->getProcessedOutput();
240 auto sourceAudio = sourceOutput.audio;
241 auto& sourceMidi = sourceOutput.midi;
242 jassert (destAudio.getNumChannels() == 0 || numSamples == (int) sourceAudio.getNumFrames());
243
244 if (connection.connectMidi)
245 destMIDI.mergeFrom (sourceMidi);
246
247 for (const auto& channelConnection : connection.connectedChannels)
248 {
249 auto sourceChan = channelConnection.sourceChannel;
250 auto destChan = channelConnection.destChannel;
251
252 if (sourceChan < 0 || destChan < 0)
253 {
255 continue;
256 }
257
258 if (sourceChan >= (int) sourceAudio.getNumChannels())
259 continue;
260
261 add (destAudio.getChannel ((choc::buffer::ChannelCount) destChan),
262 sourceAudio.getChannel ((choc::buffer::ChannelCount) sourceChan));
263 }
264 }
265}
266
267inline bool ConnectedNode::createLatencyNodes()
268{
269 bool topologyChanged = false;
270 const int maxLatency = getNodeProperties().latencyNumSamples;
272
273 for (auto& connection : connections)
274 {
275 auto& node = connection.node;
276 const int nodeLatency = node->getNodeProperties().latencyNumSamples;
277 const int latencyToAdd = maxLatency - nodeLatency;
278
279 if (latencyToAdd == 0)
280 continue;
281
282 // We should be the only thing owning this Node or we can't release it!
283 // We shouldn't be stacking LatencyNodes, rather we should modify their latency
284 jassert (dynamic_cast<LatencyNode*> (node.get()) == nullptr);
285 node = std::make_shared<LatencyNode> (std::move (node), latencyToAdd);
286
287 topologyChanged = true;
288 }
289
290 return topologyChanged;
291}
292
293}}
constexpr ValueType getLength() const noexcept
An Node which sums together the multiple inputs adding additional latency to provide a coherent outpu...
void process(ProcessContext &) override
Called when the node is to be processed.
void prepareToPlay(const PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
NodeProperties getNodeProperties() override
Should return the properties of the node.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
bool addMidiConnection(std::shared_ptr< Node >)
Adds a MIDI connection.
TransformResult transform(Node &, const std::vector< Node * > &, TransformCache &) override
Called after construction to give the node a chance to modify its topology.
bool addAudioConnection(std::shared_ptr< Node >, ChannelConnection)
Adds an audio connection.
ConnectedNode()=default
Creates a ConnectedNode with no connections.
std::vector< Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
Main graph Node processor class.
Struct to describe a single iteration of a process call.
T get(T... args)
T is_pointer_v
#define jassert(expression)
#define jassertfalse
typedef int
T max(T... args)
void ignoreUnused(Types &&...) noexcept
void visitNodes(Node &, Visitor &&, bool preordering)
Should call the visitor for any direct inputs to the node exactly once.
TransformResult
Enum to signify the result of the transform function.
T push_back(T... args)
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