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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TimeStretchingWaveNode.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//==============================================================================
16TimeStretchingWaveNode::TimeStretchingWaveNode (AudioClipBase& clip, tracktion::graph::PlayHeadState& playHeadStateToUse)
17 : c (clip), playHeadState (playHeadStateToUse), clipPtr (clip),
18 file (c.getAudioFile()),
19 fileInfo (file.getInfo()),
20 sampleRate (fileInfo.sampleRate),
21 fifo ((choc::buffer::ChannelCount) std::max (1, fileInfo.numChannels), 8192)
22{
24
25 reader = c.edit.engine.getAudioFileManager().cache.createReader (file);
26
27 auto wi = clip.getWaveInfo();
28 auto& li = c.getLoopInfo();
29
30 if (li.getNumBeats() > 0)
31 {
32 if (c.getAutoTempo() && wi.hashCode != 0)
33 speedRatio = (float) (li.getBpm (wi) / clip.edit.tempoSequence.getTempo (0)->getBpm());
34 else
35 speedRatio = (float) c.getSpeedRatio();
36 }
37
38 const int rootNote = li.getRootNote();
39
40 if (rootNote != -1)
41 {
42 if (c.getAutoPitch() && rootNote != -1)
43 pitchSemitones = (float) c.getTransposeSemiTones (true);
44 else
45 pitchSemitones = c.getPitchChange();
46 }
47
48 jassert (speedRatio >= 0.1f);
49 speedRatio = std::max (speedRatio, 0.1f);
50}
51
52//==============================================================================
53tracktion::graph::NodeProperties TimeStretchingWaveNode::getNodeProperties()
54{
56 props.hasAudio = true;
57 props.numberOfChannels = fileInfo.numChannels;
58 return props;
59}
60
61std::vector<tracktion::graph::Node*> TimeStretchingWaveNode::getDirectInputNodes()
62{
63 return {};
64}
65
66void TimeStretchingWaveNode::prepareToPlay (const tracktion::graph::PlaybackInitialisationInfo& info)
67{
69
70 // Elastique can't handle blocks larger than 1024
71 stretchBlockSize = std::min (std::max (info.blockSize, 512), 1024);
72 sampleRate = info.sampleRate;
73
74 const TimeStretcher::Mode m = c.getTimeStretchMode();
75
76 if (TimeStretcher::canProcessFor (m))
77 {
78 fileSpeedRatio = float (fileInfo.sampleRate / sampleRate);
79 const float resamplingPitchRatio = fileSpeedRatio > 0.0f ? (float) std::log2 (fileSpeedRatio) : 1.0f;
80 timestretcher.initialise (info.sampleRate, stretchBlockSize, fileInfo.numChannels, m, c.elastiqueProOptions.get(), false);
81 timestetchSpeedRatio = std::max (0.1f, float (speedRatio / fileSpeedRatio));
82 timestetchSemitonesUp = float ((pitchSemitones + (resamplingPitchRatio * 12.0f)));
83 timestretcher.setSpeedAndPitch (timestetchSpeedRatio, timestetchSemitonesUp);
84 }
85
86 fifo.setSize ((choc::buffer::ChannelCount) fileInfo.numChannels,
87 (choc::buffer::FrameCount) timestretcher.getMaxFramesNeeded());
88}
89
90bool TimeStretchingWaveNode::isReadyToProcess()
91{
92 if (file.getHash() == 0)
93 return true;
94
95 if (reader == nullptr)
96 reader = c.edit.engine.getAudioFileManager().cache.createReader (file);
97
98 return reader != nullptr && reader->getSampleRate() > 0.0;
99}
100
101void TimeStretchingWaveNode::process (ProcessContext& pc)
102{
104 const auto timelineRange = referenceSampleRangeToSplitTimelineRange (playHeadState.playHead, pc.referenceSampleRange).timelineRange1;
105 const auto editRange = tracktion::graph::sampleToTime (timelineRange, sampleRate);
106
107 if (timelineRange.isEmpty())
108 return;
109
110 auto destAudioView = pc.buffers.audio;
111
112 if (! playHeadState.isContiguousWithPreviousBlock() || editRange.getStart() != nextEditTime)
113 reset (editRange.getStart());
114
115 auto numSamples = destAudioView.getNumFrames();
116 choc::buffer::FrameCount start = 0;
117
118 while (numSamples > 0)
119 {
120 auto numReady = std::min (numSamples, (choc::buffer::FrameCount) fifo.getNumReady());
121
122 if (numReady > 0)
123 {
124 const bool res = fifo.readAdding (destAudioView.getFrameRange ({ start, start + numReady }));
125 jassert (res); juce::ignoreUnused (res);
126
127 start += numReady;
128 numSamples -= numReady;
129 }
130 else
131 {
132 if (! fillNextBlock())
133 break;
134 }
135 }
136
137 nextEditTime = editRange.getEnd();
138}
139
140//==============================================================================
141int64_t TimeStretchingWaveNode::timeToFileSample (double time) const noexcept
142{
143 const double fileStartTime = time / speedRatio;
144 return juce::roundToInt (fileStartTime * fileInfo.sampleRate);
145}
146
147void TimeStretchingWaveNode::reset (double newStartTime)
148{
149 const int64_t readPos = timeToFileSample (newStartTime);
150
151 if (reader != nullptr)
152 {
153 if (readPos == reader->getReadPosition())
154 return;
155
156 reader->setReadPosition (readPos);
157 }
158
159 fifo.reset();
160
161 timestretcher.reset();
162 timestretcher.setSpeedAndPitch (timestetchSpeedRatio, timestetchSemitonesUp);
163}
164
165bool TimeStretchingWaveNode::fillNextBlock()
166{
168 const int needed = timestretcher.getFramesNeeded();
169 jassert (needed < fifo.getFreeSpace());
170 jassert (reader != nullptr);
171
172 if (reader == nullptr)
173 return false;
174
175 AudioScratchBuffer fifoScratch (fileInfo.numChannels, stretchBlockSize);
176
177 float* outs[] =
178 {
179 fifoScratch.buffer.getWritePointer (0),
180 fileInfo.numChannels > 1 ? fifoScratch.buffer.getWritePointer (1) : nullptr,
181 nullptr
182 };
183
184 if (needed >= 0)
185 {
186 AudioScratchBuffer scratch (fileInfo.numChannels, needed);
187 const juce::AudioChannelSet bufChannels = fileInfo.numChannels == 1 ? juce::AudioChannelSet::mono() : juce::AudioChannelSet::stereo();
189
190 if (needed > 0)
191 {
192 bool b = reader->readSamples (needed, scratch.buffer, bufChannels, 0, channelsToUse, 3);
194 // don't worry about failed reads -- they are cache misses. It'll catch up
195 }
196
197 const float* ins[] =
198 {
199 scratch.buffer.getReadPointer (0),
200 fileInfo.numChannels > 1 ? scratch.buffer.getReadPointer (1) : nullptr,
201 nullptr
202 };
203
204 if (TimeStretcher::canProcessFor (c.getTimeStretchMode()))
205 timestretcher.processData (ins, needed, outs);
206 else
207 for (int channel = fileInfo.numChannels; --channel >= 0;)
208 juce::FloatVectorOperations::copy (outs[channel], ins[channel], needed);
209 }
210 else
211 {
212 jassert (needed == -1);
213 timestretcher.flush (outs);
214 }
215
216 const bool res = fifo.write (choc::buffer::createChannelArrayView (fifoScratch.buffer.getArrayOfWritePointers(),
217 (choc::buffer::ChannelCount) fifoScratch.buffer.getNumChannels(),
218 (choc::buffer::FrameCount) stretchBlockSize));
219 jassert (res); juce::ignoreUnused (res);
220
221 return true;
222}
223
224}} // namespace tracktion { inline namespace engine
static AudioChannelSet JUCE_CALLTYPE mono()
static AudioChannelSet JUCE_CALLTYPE stereo()
Mode
Holds the various algorithms to which can be used (if enabled).
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...
#define jassert(expression)
T log2(T... args)
typedef float
T max(T... args)
T min(T... args)
void ignoreUnused(Types &&...) noexcept
int roundToInt(const FloatType value) noexcept
SplitTimelineRange referenceSampleRangeToSplitTimelineRange(const PlayHead &playHead, juce::Range< int64_t > referenceSampleRange)
Converts a reference sample range to a TimelinePositionWindow which could have two time ranges if the...
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...
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.