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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_SummingNode.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
13namespace tracktion { inline namespace graph
14{
15
16//==============================================================================
17//==============================================================================
22class SummingNode final : public Node
23{
24public:
25 SummingNode() = default;
26
28 : ownedNodes (std::move (inputs))
29 {
30 for (auto& ownedNode : ownedNodes)
31 nodes.push_back (ownedNode.get());
32
33 assert (std::find (nodes.begin(), nodes.end(), nullptr) == nodes.end());
34 }
35
37 : nodes (std::move (inputs))
38 {
39 assert (std::find (nodes.begin(), nodes.end(), nullptr) == nodes.end());
40 }
41
43 std::vector<Node*> referencedInputs)
44 : SummingNode (std::move (ownedInputs))
45 {
46 nodes.insert (nodes.begin(), referencedInputs.begin(), referencedInputs.end());
47 assert (std::find (nodes.begin(), nodes.end(), nullptr) == nodes.end());
48 }
49
50 //==============================================================================
53 {
54 assert (newInput != nullptr);
55 nodes.push_back (newInput.get());
56 ownedNodes.push_back (std::move (newInput));
57 }
58
63 void setDoubleProcessingPrecision (bool shouldSumInDoublePrecision)
64 {
65 useDoublePrecision = shouldSumInDoublePrecision;
66 }
67
68 //==============================================================================
70 {
71 if (cachedNodeProperties)
72 return *cachedNodeProperties;
73
74 NodeProperties props;
75 props.hasAudio = false;
76 props.hasMidi = false;
77 props.numberOfChannels = 0;
78 props.latencyNumSamples = nodes.empty() ? 0 : std::numeric_limits<int>::min();
79
80 for (auto& node : nodes)
81 {
82 auto nodeProps = node->getNodeProperties();
83 props.hasAudio = props.hasAudio || nodeProps.hasAudio;
84 props.hasMidi = props.hasMidi || nodeProps.hasMidi;
85 props.numberOfChannels = std::max (props.numberOfChannels, nodeProps.numberOfChannels);
86 props.latencyNumSamples = std::max (props.latencyNumSamples, nodeProps.latencyNumSamples);
87 hash_combine (props.nodeID, nodeProps.nodeID);
88 }
89
90 cachedNodeProperties = props;
91
92 return props;
93 }
94
96 {
97 return nodes;
98 }
99
101 {
102 const bool hasFlattened = flattenSummingNodes();
103 const bool hasCreatedLatency = createLatencyNodes();
104
105 if (hasFlattened)
106 return TransformResult::nodesDeleted;
107
108 if (hasCreatedLatency)
109 return TransformResult::connectionsMade;
110
111 return TransformResult::none;
112 }
113
114 void prepareToPlay (const PlaybackInitialisationInfo& info) override
115 {
116 useDoublePrecision = useDoublePrecision && nodes.size() > 1;
117
118 if (useDoublePrecision)
119 tempDoubleBuffer.resize ({ (choc::buffer::ChannelCount) getNodeProperties().numberOfChannels,
120 (choc::buffer::FrameCount) info.blockSize });
121
122 isPrepared = true;
123 }
124
125 bool isReadyToProcess() override
126 {
127 for (auto& node : nodes)
128 if (! node->hasProcessed())
129 return false;
130
131 return true;
132 }
133
134 void process (ProcessContext& pc) override
135 {
136 if (useDoublePrecision)
137 processDoublePrecision (pc);
138 else
139 processSinglePrecision (pc);
140 }
141
142private:
143 //==============================================================================
145 std::vector<Node*> nodes;
146
147 std::optional<NodeProperties> cachedNodeProperties;
148 bool isPrepared = false;
149
150 bool useDoublePrecision = false;
151 choc::buffer::ChannelArrayBuffer<double> tempDoubleBuffer;
152
153 static void sortByTimestampUnstable (tracktion_engine::MidiMessageArray& messages) noexcept
154 {
155 std::sort (messages.begin(), messages.end(), [] (const juce::MidiMessage& a, const juce::MidiMessage& b)
156 {
157 auto t1 = a.getTimeStamp();
158 auto t2 = b.getTimeStamp();
159
160 if (t1 == t2)
161 {
162 if (a.isNoteOff() && b.isNoteOn()) return true;
163 if (a.isNoteOn() && b.isNoteOff()) return false;
164 }
165 return t1 < t2;
166 });
167 }
168
169 //==============================================================================
170 void processSinglePrecision (const ProcessContext& pc)
171 {
172 const auto numChannels = pc.buffers.audio.getNumChannels();
173
174 int nodesWithMidi = pc.buffers.midi.isEmpty() ? 0 : 1;
175
176 // Get each of the inputs and add them to dest
177 for (auto& node : nodes)
178 {
179 auto inputFromNode = node->getProcessedOutput();
180
181 if (auto numChannelsToAdd = std::min (inputFromNode.audio.getNumChannels(), numChannels))
182 add (pc.buffers.audio.getFirstChannels (numChannelsToAdd),
183 inputFromNode.audio.getFirstChannels (numChannelsToAdd));
184
185 if (inputFromNode.midi.isNotEmpty())
186 nodesWithMidi++;
187
188 pc.buffers.midi.mergeFrom (inputFromNode.midi);
189 }
190
191 if (nodesWithMidi > 1)
192 sortByTimestampUnstable (pc.buffers.midi);
193 }
194
195 void processDoublePrecision (const ProcessContext& pc)
196 {
197 const auto numChannels = pc.buffers.audio.getNumChannels();
198 auto doubleView = tempDoubleBuffer.getView().getStart (pc.buffers.audio.getNumFrames());
199 doubleView.clear();
200
201 int nodesWithMidi = pc.buffers.midi.isEmpty() ? 0 : 1;
202
203 // Get each of the inputs and add them to dest
204 for (auto& node : nodes)
205 {
206 auto inputFromNode = node->getProcessedOutput();
207
208 if (auto numChannelsToAdd = std::min (inputFromNode.audio.getNumChannels(), numChannels))
209 add (doubleView.getFirstChannels (numChannelsToAdd),
210 inputFromNode.audio.getFirstChannels (numChannelsToAdd));
211
212 if (inputFromNode.midi.isNotEmpty())
213 nodesWithMidi++;
214
215 pc.buffers.midi.mergeFrom (inputFromNode.midi);
216 }
217
218 assert (doubleView.getNumChannels() == (choc::buffer::ChannelCount) numChannels);
219
220 if (numChannels != 0)
221 add (pc.buffers.audio.getFirstChannels (numChannels), doubleView);
222
223 if (nodesWithMidi > 1)
224 sortByTimestampUnstable (pc.buffers.midi);
225 }
226
227 //==============================================================================
228 bool flattenSummingNodes()
229 {
230 bool hasChanged = false;
231
232 std::vector<std::unique_ptr<Node>> ownedNodesToAdd;
233 std::vector<Node*> nodesToAdd, nodesToErase;
234
235 for (auto& n : ownedNodes)
236 {
237 if (auto summingNode = dynamic_cast<SummingNode*> (n.get()))
238 {
239 for (auto& ownedNodeToAdd : summingNode->ownedNodes)
240 ownedNodesToAdd.push_back (std::move (ownedNodeToAdd));
241
242 for (auto& nodeToAdd : summingNode->nodes)
243 nodesToAdd.push_back (std::move (nodeToAdd));
244
245 nodesToErase.push_back (n.get());
246 hasChanged = true;
247 }
248 }
249
250 for (auto& n : ownedNodesToAdd)
251 ownedNodes.push_back (std::move (n));
252
253 for (auto& n : nodesToAdd)
254 nodes.push_back (std::move (n));
255
256 ownedNodes.erase (std::remove_if (ownedNodes.begin(), ownedNodes.end(),
257 [&] (auto& n) { return std::find (nodesToErase.begin(), nodesToErase.end(), n.get()) != nodesToErase.end(); }),
258 ownedNodes.end());
259
260 nodes.erase (std::remove_if (nodes.begin(), nodes.end(),
261 [&] (auto& n) { return std::find (nodesToErase.begin(), nodesToErase.end(), n) != nodesToErase.end(); }),
262 nodes.end());
263
264 return hasChanged;
265 }
266
267 bool createLatencyNodes()
268 {
269 bool topologyChanged = false;
270 const int maxLatency = getNodeProperties().latencyNumSamples;
271 std::vector<std::unique_ptr<Node>> ownedNodesToAdd;
272
273 for (auto& node : nodes)
274 {
275 auto props = node->getNodeProperties();
276 const int nodeLatency = props.latencyNumSamples;
277 const int latencyToAdd = subtractNoWrap (maxLatency, nodeLatency);
278
279 if (latencyToAdd <= 0)
280 continue;
281
282 auto getOwnedNode = [this] (auto nodeToFind)
283 {
284 for (auto& ownedN : ownedNodes)
285 {
286 if (ownedN.get() == nodeToFind)
287 {
288 auto nodeToReturn = std::move (ownedN);
289 ownedN.reset();
290 return nodeToReturn;
291 }
292 }
293
294 return std::unique_ptr<Node>();
295 };
296
297 auto ownedNode = getOwnedNode (node);
298 auto latencyNode = ownedNode != nullptr ? makeNode<LatencyNode> (std::move (ownedNode), latencyToAdd)
299 : makeNode<LatencyNode> (node, latencyToAdd);
300 ownedNodesToAdd.push_back (std::move (latencyNode));
301 node = nullptr;
302 topologyChanged = true;
303 cachedNodeProperties = std::nullopt;
304 }
305
306 // Take ownership of any new nodes and also ensure they're reference in the raw array
307 for (auto& newNode : ownedNodesToAdd)
308 {
309 nodes.push_back (newNode.get());
310 ownedNodes.push_back (std::move (newNode));
311 }
312
313 nodes.erase (std::remove_if (nodes.begin(), nodes.end(),
314 [] (auto& n) { return n == nullptr; }),
315 nodes.end());
316
317 ownedNodes.erase (std::remove_if (ownedNodes.begin(), ownedNodes.end(),
318 [] (auto& n) { return n == nullptr; }),
319 ownedNodes.end());
320
321 return topologyChanged;
322 }
323};
324
325
327static inline std::unique_ptr<SummingNode> makeSummingNode (std::initializer_list<Node*> nodes)
328{
330
331 for (auto node : nodes)
332 nodeVector.push_back (std::unique_ptr<Node> (node));
333
334 return std::make_unique<SummingNode> (std::move (nodeVector));
335}
336
337}}
assert
T begin(T... args)
Main graph Node processor class.
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...
void process(ProcessContext &pc) 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.
void prepareToPlay(const PlaybackInitialisationInfo &info) override
Called once before playback begins for each node.
void addInput(std::unique_ptr< Node > newInput)
Adds an input to be summed in this 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.
void setDoubleProcessingPrecision(bool shouldSumInDoublePrecision)
Enables Nodes to be intermediately summed using double precision.
TransformResult transform(Node &, const std::vector< Node * > &, TransformCache &) override
Called after construction to give the node a chance to modify its topology.
T end(T... args)
T find(T... args)
T get(T... args)
T is_pointer_v
T max(T... args)
T min(T... args)
T move(T... args)
std::unique_ptr< Node > makeNode(Args &&... args)
Creates a node of the given type and returns it as the base Node class.
TransformResult
Enum to signify the result of the transform function.
T push_back(T... args)
T remove_if(T... args)
T sort(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...
constexpr int subtractNoWrap(int a, int b)
Subtracts b from a without wrapping.