Audacity 3.2.0
ChangeSpeedBase.cpp
Go to the documentation of this file.
1#include "ChangeSpeedBase.h"
3#include "LabelTrack.h"
5#include "Prefs.h"
6#include "Resample.h"
7#include "ShuttleAutomation.h"
8#include "SyncLock.h"
9#include "TimeWarper.h"
10#include "WaveClip.h"
11#include "WaveTrack.h"
12#include <cmath>
13
14// Soundtouch is not reasonable below -99% or above 3000%.
15
17{
19 return parameters;
20}
21
23 "Change Speed and Pitch") };
24
26{
27 Parameters().Reset(*this);
28
31 mFromLength = 0.0;
32 mToLength = 0.0;
34 mbLoopDetect = false;
35
37}
38
40{
41}
42
44{
45 return Symbol;
46}
47
49{
50 return XO("Changes the speed of a track, also changing its pitch");
51}
52
54{
55 return L"Change_Speed";
56}
57
59{
60 return EffectTypeProcess;
61}
62
65{
66 // To do: externalize state so const_cast isn't needed
67 return const_cast<ChangeSpeedBase&>(*this).DoLoadFactoryDefaults(settings);
68}
69
71{
74
76}
77
79{
80 return (m_PercentChange == 0.0);
81}
82
84 const EffectSettings&, double previewLength) const
85{
86 return previewLength * (100.0 + m_PercentChange) / 100.0;
87}
88
90{
91 // The selection might have changed since the last time ChangeSpeedBase
92 // was invoked, so recalculate the Length parameters.
94 return true;
95}
96
98 const WaveTrack& track, const double curT0, const double curT1) -> Gaps
99{
100 // Silenced samples will be inserted in gaps between clips, so capture where
101 // these gaps are for later deletion
102 Gaps gaps;
103 const auto newGap = [&](double st, double et) {
104 gaps.emplace_back(track.SnapToSample(st), track.SnapToSample(et));
105 };
106 double last = curT0;
107 auto clips = track.SortedIntervalArray();
108 auto front = clips.front();
109 auto back = clips.back();
110 for (auto& clip : clips)
111 {
112 auto st = clip->GetPlayStartTime();
113 auto et = clip->GetPlayEndTime();
114 if (st >= curT0 || et < curT1)
115 {
116 if (curT0 < st && clip == front)
117 newGap(curT0, st);
118 else if (last < st && curT0 <= last)
119 newGap(last, st);
120 if (et < curT1 && clip == back)
121 newGap(et, curT1);
122 }
123 last = et;
124 }
125 return gaps;
126}
127
129{
130 // Similar to SoundTouchBase::Process()
131
132 // Iterate over each track.
133 // All needed because this effect needs to introduce
134 // silence in the sync-lock group tracks to keep sync
135 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } }, true };
136 bool bGoodResult = true;
137
138 mCurTrackNum = 0;
139
140 mFactor = 100.0 / (100.0 + m_PercentChange);
141
142 outputs.Get().Any().VisitWhile(
143 bGoodResult,
144 [&](LabelTrack& lt) {
146 {
147 if (!ProcessLabelTrack(&lt))
148 bGoodResult = false;
149 }
150 },
151 [&](auto&& fallthrough) {
152 return [&](WaveTrack& outWaveTrack) {
153 if (!outWaveTrack.GetSelected())
154 return fallthrough();
155
156 // Get start and end times from track
157 mCurT0 = outWaveTrack.GetStartTime();
158 mCurT1 = outWaveTrack.GetEndTime();
159
160 // Set the current bounds to whichever left marker is
161 // greater and whichever right marker is less:
162 mCurT0 = std::max(mT0, mCurT0);
164
165 // Process only if the right marker is to the right of the left
166 // marker
167 if (mCurT1 > mCurT0)
168 {
169 // Transform the marker timepoints to samples
170 auto start = outWaveTrack.TimeToLongSamples(mCurT0);
171 auto end = outWaveTrack.TimeToLongSamples(mCurT1);
172
173 const auto gaps = FindGaps(outWaveTrack, mCurT0, mCurT1);
174
175 auto pNewTrack = outWaveTrack.EmptyCopy();
176 auto iter = pNewTrack->Channels().begin();
177 for (const auto pChannel : outWaveTrack.Channels())
178 {
179 // ProcessOne() (implemented below) processes a single channel
180 if (ProcessOne(*pChannel, **iter++, start, end))
181 ++mCurTrackNum;
182 else
183 {
184 pNewTrack.reset();
185 break;
186 }
187 }
188 if (!pNewTrack)
189 {
190 bGoodResult = false;
191 return;
192 }
193 pNewTrack->Flush();
194
195 const double newLength = pNewTrack->GetEndTime();
196 const LinearTimeWarper warper { mCurT0, mCurT0, mCurT1,
197 mCurT0 + newLength };
198
199 outWaveTrack.ClearAndPaste(
200 mCurT0, mCurT1, *pNewTrack, true, true, &warper);
201
202 // Finally, recreate the gaps
203 for (const auto [st, et] : gaps)
204 if (st >= mCurT0 && et <= mCurT1 && st != et)
205 outWaveTrack.SplitDelete(warper.Warp(st), warper.Warp(et));
206 }
207 else
208 mCurTrackNum += outWaveTrack.NChannels();
209 };
210 },
211 [&](Track& t) {
213 t.SyncLockAdjust(mT1, mT0 + (mT1 - mT0) * mFactor);
214 });
215
216 if (bGoodResult)
217 outputs.Commit();
218
219 // Update selection.
220 mT1 = mT0 + (((mT1 - mT0) * 100.0) / (100.0 + m_PercentChange));
221
222 return bGoodResult;
223}
224
225// Labels are time-scaled linearly inside the affected region, and labels after
226// the region are shifted along according to how the region size changed.
228{
229 RegionTimeWarper warper { mT0, mT1,
230 std::make_unique<LinearTimeWarper>(
231 mT0, mT0, mT1, mT0 + (mT1 - mT0) * mFactor) };
232 lt->WarpLabels(warper);
233 return true;
234}
235
236// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
237// and calls libsamplerate code on these blocks.
239 const WaveChannel& track, WaveChannel& outputTrack, sampleCount start,
241{
242 // Get the length of the selection (as double). len is
243 // used simple to calculate a progress meter, so it is easier
244 // to make it a double now than it is to do it later
245 auto len = (end - start).as_double();
246
247 // Initiate processing buffers, most likely shorter than
248 // the length of the selection being processed.
249 auto inBufferSize = track.GetMaxBlockSize();
250
251 Floats inBuffer { inBufferSize };
252
253 // mFactor is at most 100-fold so this shouldn't overflow size_t
254 auto outBufferSize = size_t(mFactor * inBufferSize + 10);
255 Floats outBuffer { outBufferSize };
256
257 // Set up the resampling stuff for this track.
258 Resample resample(true, mFactor, mFactor); // constant rate resampling
259
260 // Go through the track one buffer at a time. samplePos counts which
261 // sample the current buffer starts at.
262 bool bResult = true;
263 auto samplePos = start;
264 while (samplePos < end)
265 {
266 // Get a blockSize of samples (smaller than the size of the buffer)
267 auto blockSize = limitSampleBufferSize(
268 track.GetBestBlockSize(samplePos), end - samplePos);
269
270 // Get the samples from the track and put them in the buffer
271 track.GetFloats(inBuffer.get(), samplePos, blockSize);
272
273 const auto results = resample.Process(
274 mFactor, inBuffer.get(), blockSize, ((samplePos + blockSize) >= end),
275 outBuffer.get(), outBufferSize);
276 const auto outgen = results.second;
277
278 if (outgen > 0)
279 outputTrack.Append((samplePtr)outBuffer.get(), floatSample, outgen);
280
281 // Increment samplePos
282 samplePos += results.first;
283
284 // Update the Progress meter
285 if (TrackProgress(mCurTrackNum, (samplePos - start).as_double() / len))
286 {
287 bResult = false;
288 break;
289 }
290 }
291
292 return bResult;
293}
@ kVinyl_33AndAThird
int min(int a, int b)
EffectType
@ EffectTypeProcess
std::optional< std::unique_ptr< EffectSettingsAccess::Message > > OptionalMessage
XO("Cut/Copy/Paste")
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
char * samplePtr
Definition: SampleFormat.h:57
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
static Settings & settings()
Definition: TrackInfo.cpp:51
Generates EffectParameterMethods overrides from variadic template arguments.
An Effect that affects both pitch & speed.
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
NumericFormatID mFormat
OptionalMessage DoLoadFactoryDefaults(EffectSettings &settings)
bool ProcessLabelTrack(LabelTrack *t)
bool CheckWhetherSkipEffect(const EffectSettings &settings) const override
After Init(), tell whether Process() should be skipped.
static const ComponentInterfaceSymbol Symbol
const EffectParameterMethods & Parameters() const override
ComponentInterfaceSymbol GetSymbol() const override
std::vector< Gap > Gaps
bool Init() override
OptionalMessage LoadFactoryDefaults(EffectSettings &settings) const override
Gaps FindGaps(const WaveTrack &track, const double curT0, const double curT1)
bool ProcessOne(const WaveChannel &track, WaveChannel &outputTrack, sampleCount start, sampleCount end)
TranslatableString GetDescription() const override
double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const override
virtual ~ChangeSpeedBase()
bool Process(EffectInstance &instance, EffectSettings &settings) override
EffectType GetType() const override
Type determines how it behaves.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
const wxString & Internal() const
double mT1
Definition: EffectBase.h:123
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
double mT0
Definition: EffectBase.h:122
OptionalMessage LoadFactoryDefaults(EffectSettings &settings) const override
Definition: Effect.cpp:165
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:343
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
Interface for manipulations of an Effect's settings.
virtual void Reset(Effect &effect) const =0
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:98
void WarpLabels(const TimeWarper &warper)
Definition: LabelTrack.cpp:323
Linear scaling, initialised by giving two points on the line.
Definition: TimeWarper.h:87
No change before the specified region; during the region, warp according to the given warper; after t...
Definition: TimeWarper.h:192
Interface to libsoxr.
Definition: Resample.h:27
std::pair< size_t, size_t > Process(double factor, const float *inBuffer, size_t inBufferLen, bool lastFlag, float *outBuffer, size_t outBufferLen)
Main processing function. Resamples from the input buffer to the output buffer.
Definition: Resample.cpp:88
static bool IsSelectedOrSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:104
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
Holds a msgid for the translation catalog; may also bind format arguments.
bool Append(constSamplePtr buffer, sampleFormat format, size_t len)
Definition: WaveTrack.cpp:2237
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
"narrow" overload fetches from the unique channel
Definition: WaveTrack.h:129
size_t GetBestBlockSize(sampleCount t) const
A hint for sizing of well aligned fetches.
Definition: WaveTrack.h:851
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:859
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
NUMERIC_FORMATS_API NumericFormatSymbol DefaultSelectionFormat()
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
Externalized state of a plug-in.