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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ContainerClipNode.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
11namespace tracktion { inline namespace engine
12{
13
14//==============================================================================
15//==============================================================================
16ContainerClipNode::ContainerClipNode (ProcessState& editProcessState,
17 EditItemID clipID,
18 BeatRange position,
19 BeatDuration offset,
20 BeatRange clipLoopRange,
21 std::unique_ptr<Node> inputNode)
22 : TracktionEngineNode (editProcessState),
23 containerClipID (clipID),
24 clipPosition (position),
25 loopRange (clipLoopRange),
26 clipOffset (offset),
27 input (std::move (inputNode))
28{
29 if (auto parentTempoPosition = getProcessState().getTempoSequencePosition())
30 {
31 tempoPosition = std::make_unique<tempo::Sequence::Position> (*parentTempoPosition);
32
33 // At the moment, this won't work correctly with complex tempo changes and the
34 // contained clips will use the tempo conversions of the main Edit. This needs
35 // to be added by using the ProcessState directly for tempo conversions with a
36 // specified offset
37//ddd jassert (getProcessState().getTempoSequence()->getNumTempos() == 1);
38 }
39
40 assert (input);
41 setOptimisations ({ tracktion::graph::ClearBuffers::no,
42 tracktion::graph::AllocateAudioBuffer::yes });
43}
44
45//==============================================================================
46tracktion::graph::NodeProperties ContainerClipNode::getNodeProperties()
47{
48 // Reset the NodeID as we need to be findable between graph loads to keep the same internal NodePlayer
49 nodeProperties.nodeID = 0;
50
51 // Calculated from hashing a string view of "ContainerClipNode"
52 const auto hashSalt = 9088803362895930667;
53 hash_combine (nodeProperties.nodeID, hashSalt);
54 hash_combine (nodeProperties.nodeID, containerClipID.getRawID());
55
56 return nodeProperties;
57}
58
59std::vector<tracktion::graph::Node*> ContainerClipNode::getDirectInputNodes()
60{
61 return {};
62}
63
64std::vector<Node*> ContainerClipNode::getInternalNodes()
65{
66 if (input)
67 return { input.get() };
68
69 return { playerContext->player.getNode() };
70}
71
72void ContainerClipNode::prepareToPlay (const tracktion::graph::PlaybackInitialisationInfo& info)
73{
74 if (info.nodeGraphToReplace != nullptr)
75 if (auto oldNode = findNodeWithID<ContainerClipNode> (*info.nodeGraphToReplace, getNodeProperties().nodeID))
76 playerContext = oldNode->playerContext;
77
78 if (! playerContext)
79 {
80 playerContext = std::make_shared<PlayerContext>();
81 playerContext->player.setNumThreads (0);
82
83 // We need to create our own Tempo::Position as we'll apply an offset so it stays in sync with the Edit's tempo sequence
84 // Make sure we do this before we overwrite the default ProcessState
85 if (auto tempoSequence = getProcessState().getTempoSequence())
86 playerContext->processState.setTempoSequence (tempoSequence);
87 }
88
89 if (! input)
90 {
91 assert (playerContext->player.getSampleRate() == info.sampleRate);
92 return;
93 }
94
95 // Set the ProcessState used for all the child nodes so they use the local time, not the Edit time
96 visitNodes (*input,
97 [localProcessState = &playerContext->processState] (auto& node)
98 {
99 if (auto ten = dynamic_cast<TracktionEngineNode*> (&node))
100 ten->setProcessState (*localProcessState);
101
102 for (auto internalNode : node.getInternalNodes())
103 if (auto ten = dynamic_cast<TracktionEngineNode*> (internalNode))
104 ten->setProcessState (*localProcessState);
105 }, true);
106
107 playerContext->player.setNode (std::move (input),
108 info.sampleRate, info.blockSize);
109}
110
111bool ContainerClipNode::isReadyToProcess()
112{
113 return true;
114}
115
116void ContainerClipNode::process (ProcessContext& pc)
117{
118 const auto sectionEditBeatRange = getEditBeatRange();
119 const auto sectionEditSampleRange = getTimelineSampleRange();
120
121 if (sectionEditBeatRange.getEnd() <= clipPosition.getStart()
122 || sectionEditBeatRange.getStart() >= clipPosition.getEnd())
123 return;
124
125 // Set playHead loop range using loopRange
126 // Find ref offset from clip time
127 // If playhead position was overriden, pass this on to the PlayHeadState
128 // Process buffer
129 // Add an offset to ProcessState so the tempo positions can be synced up
130
131 auto& player = playerContext->player;
132 const auto sampleRate = getSampleRate();
133
134 auto& editPlayHead = getPlayHead();
135 auto& editPlayHeadState = getPlayHeadState();
136 auto& localPlayHead = playerContext->playHead;
137
138 // Calculate sample positions of clip as these will vary when the tempo changes
139 const auto editStartBeatOfLocalTimeline = clipPosition.getStart() - clipOffset;
140 const auto editStartTimeOfLocalTimeline = tempoPosition->set (editStartBeatOfLocalTimeline);
141
142 const TimeRange loopTimeRange (tempoPosition->set (loopRange.getStart()),
143 tempoPosition->set (loopRange.getEnd()));
144 const auto loopRangeSamples = toSamples (loopTimeRange, sampleRate);
145
146
147 // Updated the PlayHead so the position/play setting below is in sync
148 localPlayHead.setReferenceSampleRange (pc.referenceSampleRange);
149
150 // We don't want to update the playhead position as we'll do that manually below to avoid triggering playhead jumps
151 if (! loopRangeSamples.isEmpty() && localPlayHead.getLoopRange() != loopRangeSamples)
152 localPlayHead.setLoopRange (true, loopRangeSamples, false);
153
154 // Syncronise positions
155 const auto playheadOffset = toSamples (editStartTimeOfLocalTimeline, sampleRate);
156 playerContext->processState.setPlaybackSpeedRatio (getPlaybackSpeedRatio());
157
158 int64_t newPosition = editPlayHead.getPosition() - playheadOffset + loopRangeSamples.getStart();
159
160 if (localPlayHead.isLooping())
161 newPosition = localPlayHead.linearPositionToLoopPosition (newPosition, localPlayHead.getLoopRange());
162
163 if (editPlayHeadState.isContiguousWithPreviousBlock())
164 localPlayHead.overridePosition (newPosition);
165 else
166 localPlayHead.setPosition (newPosition);
167
168 // Syncronise playing state
169 if (editPlayHead.isStopped() && ! localPlayHead.isStopped())
170 localPlayHead.stop();
171
172 if (editPlayHead.isPlaying() && ! localPlayHead.isPlaying())
173 localPlayHead.play();
174
175 assert (! localPlayHead.isLooping() || localPlayHead.getLoopRange().contains (localPlayHead.getPosition()));
176
177 // Process
178 ProcessContext localPC { pc.numSamples, pc.referenceSampleRange,
179 { pc.buffers.audio, pc.buffers.midi } };
180 player.process (localPC);
181
182 // Silence any samples before or after our edit time range
183 {
184 const TimeRange clipTimeRange (tempoPosition->set (clipPosition.getStart()),
185 tempoPosition->set (clipPosition.getEnd()));
186 const auto editPositionInSamples = toSamples ({ clipTimeRange.getStart(), clipTimeRange.getEnd() }, sampleRate);
187
188 const auto destBuffer = pc.buffers.audio;
189 auto numSamplesToClearAtStart = std::min (editPositionInSamples.getStart() - sectionEditSampleRange.getStart(), (SampleCount) destBuffer.getNumFrames());
190 auto numSamplesToClearAtEnd = std::min (sectionEditSampleRange.getEnd() - editPositionInSamples.getEnd(), (SampleCount) destBuffer.getNumFrames());
191
192 if (numSamplesToClearAtStart > 0)
193 destBuffer.getStart ((choc::buffer::FrameCount) numSamplesToClearAtStart).clear();
194
195 if (numSamplesToClearAtEnd > 0)
196 destBuffer.getEnd ((choc::buffer::FrameCount) numSamplesToClearAtEnd).clear();
197 }
198}
199
200}} // namespace tracktion { inline namespace engine
assert
Struct to describe a single iteration of a process call.
T is_pointer_v
T min(T... args)
T move(T... args)
void visitNodes(Node &, Visitor &&, bool preordering)
Should call the visitor for any direct inputs to the node exactly once.
typedef int64_t
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...