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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_NodePlayer.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
13
14namespace tracktion { inline namespace graph
15{
16
22{
23public:
25 NodePlayer() = default;
26
28 NodePlayer (std::unique_ptr<Node> nodeToProcess, PlayHeadState* playHeadStateToUse = nullptr)
29 : input (std::move (nodeToProcess)), playHeadState (playHeadStateToUse)
30 {
31 }
32
35 {
36 return input ? input.get()
37 : nodeGraph ? nodeGraph->rootNode.get()
38 : nullptr;
39 }
40
43 {
44 setNode (std::move (newNode), sampleRate, blockSize);
45 }
46
48 void setNode (std::unique_ptr<Node> newNode, double sampleRateToUse, int blockSizeToUse)
49 {
50 auto newGraph = prepareToPlay (std::move (newNode), nodeGraph.get(), sampleRateToUse, blockSizeToUse);
52
53 {
54 const juce::SpinLock::ScopedLockType sl (inputAndNodesLock);
55 oldGraph = std::move (nodeGraph);
56 nodeGraph = std::move (newGraph);
57 }
58 }
59
61 void prepareToPlay (double sampleRateToUse, int blockSizeToUse)
62 {
63 if (sampleRate == sampleRateToUse && blockSize == blockSizeToUse)
64 return;
65
66 auto oldGraph = std::move (nodeGraph);
67 auto rootNodeToPrepare = input ? std::move (input)
68 : oldGraph ? std::move (oldGraph->rootNode)
69 : nullptr;
70 nodeGraph = prepareToPlay (std::move (rootNodeToPrepare), nullptr, sampleRateToUse, blockSizeToUse);
71 }
72
74 std::unique_ptr<NodeGraph> prepareToPlay (std::unique_ptr<Node> node, NodeGraph* oldGraph, double sampleRateToUse, int blockSizeToUse)
75 {
76 sampleRate = sampleRateToUse;
77 blockSize = blockSizeToUse;
78
79 if (playHeadState != nullptr)
80 playHeadState->playHead.setScrubbingBlockLength (timeToSample (0.08, sampleRate));
81
82 return node_player_utils::prepareToPlay (std::move (node), oldGraph, sampleRateToUse, blockSizeToUse);
83 }
84
89 {
90 if (inputAndNodesLock.tryEnter())
91 {
92 if (! nodeGraph)
93 {
94 inputAndNodesLock.exit();
95 return 0;
96 }
97
98 int numMisses = 0;
99
100 if (playHeadState != nullptr)
101 numMisses = processWithPlayHeadState (*playHeadState, *nodeGraph, pc);
102 else
103 numMisses = processPostorderedNodes (*nodeGraph, pc);
104
105 inputAndNodesLock.exit();
106
107 return numMisses;
108 }
109
110 return 0;
111 }
112
113 double getSampleRate() const
114 {
115 return sampleRate;
116 }
117
118 int processPostorderedNodes (NodeGraph& graphToProcess, const Node::ProcessContext& pc)
119 {
120 return processPostorderedNodesSingleThreaded (graphToProcess, pc);
121 }
122
123protected:
126 PlayHeadState* playHeadState = nullptr;
127
128 double sampleRate = 0.0;
129 int blockSize = 0;
130
131 juce::SpinLock inputAndNodesLock;
132
133 int processWithPlayHeadState (PlayHeadState& phs, NodeGraph& graphToProcess,
134 const Node::ProcessContext& pc)
135 {
136 int numMisses = 0;
137
138 // Check to see if the timeline needs to be processed in two halves due to looping
139 const auto splitTimelineRange = referenceSampleRangeToSplitTimelineRange (phs.playHead, pc.referenceSampleRange);
140
141 if (splitTimelineRange.isSplit)
142 {
143 const auto firstProportion = splitTimelineRange.timelineRange1.getLength() / (double) pc.referenceSampleRange.getLength();
144
145 const auto firstReferenceRange = pc.referenceSampleRange.withEnd (pc.referenceSampleRange.getStart() + (int64_t) std::llround (pc.referenceSampleRange.getLength() * firstProportion));
146 const auto secondReferenceRange = juce::Range<int64_t> (firstReferenceRange.getEnd(), pc.referenceSampleRange.getEnd());
147 jassert (firstReferenceRange.getLength() + secondReferenceRange.getLength() == pc.referenceSampleRange.getLength());
148
149 const auto firstNumSamples = (choc::buffer::FrameCount) std::llround (pc.numSamples * firstProportion);
150 const auto secondNumSamples = pc.numSamples - firstNumSamples;
151 jassert (firstNumSamples + secondNumSamples == pc.numSamples);
152
153 {
154 auto destAudio = pc.buffers.audio.getStart (firstNumSamples);
155 auto& destMidi = pc.buffers.midi;
156
157 phs.update (firstReferenceRange);
158 tracktion::graph::Node::ProcessContext pc1 { firstNumSamples, firstReferenceRange, { destAudio , destMidi } };
159 numMisses += processPostorderedNodes (graphToProcess, pc1);
160 }
161
162 {
163 auto destAudio = pc.buffers.audio.getFrameRange ({ firstNumSamples, firstNumSamples + secondNumSamples });
164 auto& destMidi = pc.buffers.midi;
165
166 //TODO: Use a scratch MidiMessageArray and then merge it back with the offset time
167 tracktion::graph::Node::ProcessContext pc2 { secondNumSamples, secondReferenceRange, { destAudio, destMidi } };
168 phs.update (secondReferenceRange);
169 numMisses += processPostorderedNodes (graphToProcess, pc2);
170 }
171 }
172 else
173 {
174 phs.update (pc.referenceSampleRange);
175 numMisses += processPostorderedNodes (graphToProcess, pc);
176 }
177
178 return numMisses;
179 }
180
185 {
186 for (auto node : nodeGraph.orderedNodes)
187 node->prepareForNextBlock (pc.referenceSampleRange);
188
189 int numMisses = 0;
190 size_t numNodesProcessed = 0;
191
192 for (;;)
193 {
194 for (auto node : nodeGraph.orderedNodes)
195 {
196 if (! node->hasProcessed() && node->isReadyToProcess())
197 {
198 node->process (pc.numSamples, pc.referenceSampleRange);
199 ++numNodesProcessed;
200 }
201 else
202 {
203 ++numMisses;
204 }
205 }
206
207 if (numNodesProcessed == nodeGraph.orderedNodes.size())
208 {
209 auto output = nodeGraph.rootNode->getProcessedOutput();
210 auto numAudioChannels = std::min (output.audio.getNumChannels(),
211 pc.buffers.audio.getNumChannels());
212
213 if (numAudioChannels > 0)
214 add (pc.buffers.audio.getFirstChannels (numAudioChannels),
215 output.audio.getFirstChannels (numAudioChannels));
216
217 pc.buffers.midi.mergeFrom (output.midi);
218
219 break;
220 }
221 }
222
223 return numMisses;
224 }
225};
226
227}}
void exit() const noexcept
bool tryEnter() const noexcept
Simple player for an Node.
static int processPostorderedNodesSingleThreaded(NodeGraph &nodeGraph, const Node::ProcessContext &pc)
Processes a group of Nodes assuming a postordering VertexOrdering.
int process(const Node::ProcessContext &pc)
Processes a block of audio and MIDI data.
void prepareToPlay(double sampleRateToUse, int blockSizeToUse)
Prepares the current Node to be played.
Node * getNode()
Returns the current Node.
void setNode(std::unique_ptr< Node > newNode, double sampleRateToUse, int blockSizeToUse)
Sets the Node to process with a new sample rate and block size.
std::unique_ptr< NodeGraph > prepareToPlay(std::unique_ptr< Node > node, NodeGraph *oldGraph, double sampleRateToUse, int blockSizeToUse)
Prepares a specific Node to be played and returns all the Nodes.
void setNode(std::unique_ptr< Node > newNode)
Sets the Node to process.
NodePlayer()=default
Creates an empty NodePlayer.
NodePlayer(std::unique_ptr< Node > nodeToProcess, PlayHeadState *playHeadStateToUse=nullptr)
Creates an NodePlayer to process a Node.
Main graph Node processor class.
Struct to describe a single iteration of a process call.
Determines how this block releates to other previous render blocks and if the play head has jumped in...
void setScrubbingBlockLength(int64_t numSamples)
Sets the length of the small looped blocks to play while scrubbing.
#define jassert(expression)
typedef double
T min(T... args)
T llround(T... args)
typedef int64_t
Holds a graph in an order ready for processing and a sorted map for quick lookups.