Audacity 3.2.0
SoundTouchEffect.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5SoundTouchEffect.cpp
6
7Dominic Mazzoni, Vaughan Johnson
8
9This abstract class contains all of the common code for an
10effect that uses SoundTouch to do its processing (ChangeTempo
11 and ChangePitch).
12
13**********************************************************************/
14
15
16
17#if USE_SOUNDTOUCH
18#include "SoundTouchEffect.h"
19#include "EffectOutputTracks.h"
20
21#include <math.h>
22
23#include "../LabelTrack.h"
24#include "SyncLock.h"
25#include "WaveClip.h"
26#include "WaveTrack.h"
27#include "NoteTrack.h"
28#include "TimeWarper.h"
29
30// Soundtouch defines these as well, which are also in generated configmac.h
31// and configunix.h, so get rid of them before including,
32// to avoid compiler warnings, and be sure to do this
33// after all other #includes, to avoid any mischief that might result
34// from doing the un-definitions before seeing any wx headers.
35#undef PACKAGE_NAME
36#undef PACKAGE_STRING
37#undef PACKAGE_TARNAME
38#undef PACKAGE_VERSION
39#undef PACKAGE_BUGREPORT
40#undef PACKAGE
41#undef VERSION
42#include "SoundTouch.h"
43
44#ifdef USE_MIDI
45EffectSoundTouch::EffectSoundTouch()
46{
47 mSemitones = 0;
48}
49#endif
50
51EffectSoundTouch::~EffectSoundTouch()
52{
53}
54
55bool EffectSoundTouch::ProcessLabelTrack(
56 LabelTrack *lt, const TimeWarper &warper)
57{
58// SetTimeWarper(std::make_unique<RegionTimeWarper>(mT0, mT1,
59 // std::make_unique<LinearTimeWarper>(mT0, mT0,
60 // mT1, mT0 + (mT1 - mT0) * mFactor)));
61 lt->WarpLabels(warper);
62 return true;
63}
64
65#ifdef USE_MIDI
66bool EffectSoundTouch::ProcessNoteTrack(NoteTrack *nt, const TimeWarper &warper)
67{
68 nt->WarpAndTransposeNotes(mT0, mT1, warper, mSemitones);
69 return true;
70}
71#endif
72
73bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
74 const TimeWarper &warper,
75 bool preserveLength)
76{
77 // Assumes that mSoundTouch has already been initialized
78 // by the subclass for subclass-specific parameters. The
79 // time warper should also be set.
80
81 // Check if this effect will alter the selection length; if so, we need
82 // to operate on sync-lock selected tracks.
83 bool mustSync = true;
84 if (mT1 == warper.Warp(mT1)) {
85 mustSync = false;
86 }
87
88 //Iterate over each track
89 // Needs all for sync-lock grouping.
90 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } }, true };
91 bool bGoodResult = true;
92
93 mPreserveLength = preserveLength;
94 mCurTrackNum = 0;
95 m_maxNewLength = 0.0;
96
97 outputs.Get().Any().VisitWhile(bGoodResult,
98 [&](auto &&fallthrough){ return [&](LabelTrack &lt) {
99 if ( !(lt.GetSelected() ||
100 (mustSync && SyncLock::IsSyncLockSelected(lt))) )
101 return fallthrough();
102 if (!ProcessLabelTrack(&lt, warper))
103 bGoodResult = false;
104 }; },
105#ifdef USE_MIDI
106 [&](auto &&fallthrough){ return [&](NoteTrack &nt) {
107 if (!(nt.GetSelected() ||
108 (mustSync && SyncLock::IsSyncLockSelected(nt))))
109 return fallthrough();
110 if (!ProcessNoteTrack(&nt, warper))
111 bGoodResult = false;
112 }; },
113#endif
114 [&](auto &&fallthrough){ return [&](WaveTrack &orig) {
115 if (!orig.GetSelected())
116 return fallthrough();
117
118 // Process only if the right marker is to the right of the left marker
119 if (mT1 > mT0) {
120 //Transform the marker timepoints to samples
121 const auto start = orig.TimeToLongSamples(mT0);
122 const auto end = orig.TimeToLongSamples(mT1);
123
124 const auto tempTrack = orig.EmptyCopy();
125 auto &out = *tempTrack;
126
127 const auto pSoundTouch = std::make_unique<soundtouch::SoundTouch>();
128 initer(pSoundTouch.get());
129
130 // TODO: more-than-two-channels
131 auto channels = orig.Channels();
132 if (channels.size() > 1) {
133
134 //Inform soundtouch there's 2 channels
135 pSoundTouch->setChannels(2);
136
137 //ProcessStereo() (implemented below) processes a stereo track
138 if (!ProcessStereo(pSoundTouch.get(),
139 orig, out, start, end, warper))
140 bGoodResult = false;
141 mCurTrackNum++; // Increment for rightTrack, too.
142 } else {
143 //Inform soundtouch there's a single channel
144 pSoundTouch->setChannels(1);
145
146 //ProcessOne() (implemented below) processes a single track
147 if (!ProcessOne(pSoundTouch.get(), **channels.begin(),
148 out, start, end, warper))
149 bGoodResult = false;
150 }
151 // pSoundTouch is destroyed here
152 }
153 mCurTrackNum++;
154 }; },
155 [&](Track &t) {
156 if (mustSync && SyncLock::IsSyncLockSelected(t))
157 t.SyncLockAdjust(mT1, warper.Warp(mT1));
158 }
159 );
160
161 if (bGoodResult)
162 outputs.Commit();
163
164 return bGoodResult;
165}
166
167//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
168//and executes ProcessSoundTouch on these blocks
169bool EffectSoundTouch::ProcessOne(soundtouch::SoundTouch *pSoundTouch,
170 WaveChannel &orig, WaveTrack &out,
172 const TimeWarper &warper)
173{
174 // ProcessStereo handles the stereo case instead. This is a precondition
175 // for Append:
176 assert(out.NChannels() == 1);
177
178 pSoundTouch->setSampleRate(
179 static_cast<unsigned int>((orig.GetRate() + 0.5)));
180
181 //Get the length of the buffer (as double). len is
182 //used simple to calculate a progress meter, so it is easier
183 //to make it a double now than it is to do it later
184 auto len = (end - start).as_double();
185
186 {
187 //Initiate a processing buffer. This buffer will (most likely)
188 //be shorter than the length of the track being processed.
189 Floats buffer{ orig.GetMaxBlockSize() };
190
191 //Go through the track one buffer at a time. s counts which
192 //sample the current buffer starts at.
193 auto s = start;
194 while (s < end) {
195 //Get a block of samples (smaller than the size of the buffer)
196 const auto block = std::min<size_t>(8192,
198
199 //Get the samples from the track and put them in the buffer
200 orig.GetFloats(buffer.get(), s, block);
201
202 //Add samples to SoundTouch
203 pSoundTouch->putSamples(buffer.get(), block);
204
205 //Get back samples from SoundTouch
206 unsigned int outputCount = pSoundTouch->numSamples();
207 if (outputCount > 0) {
208 Floats buffer2{ outputCount };
209 pSoundTouch->receiveSamples(buffer2.get(), outputCount);
210 out.Append(0, (samplePtr)buffer2.get(), floatSample, outputCount);
211 }
212
213 //Increment s one blockfull of samples
214 s += block;
215
216 //Update the Progress meter
217 if (TrackProgress(mCurTrackNum, (s - start).as_double() / len))
218 return false;
219 }
220
221 // Tell SoundTouch to finish processing any remaining samples
222 pSoundTouch->flush(); // this should only be used for changeTempo - it dumps data otherwise with pRateTransposer->clear();
223
224 unsigned int outputCount = pSoundTouch->numSamples();
225 if (outputCount > 0) {
226 Floats buffer2{ outputCount };
227 pSoundTouch->receiveSamples(buffer2.get(), outputCount);
228 out.Append(0, (samplePtr)buffer2.get(), floatSample, outputCount);
229 }
230
231 out.Flush();
232 }
233
234 // Transfer output samples to the original
235 Finalize(orig.GetTrack(), out, warper);
236
237 double newLength = out.GetEndTime();
238 m_maxNewLength = std::max(m_maxNewLength, newLength);
239
240 //Return true because the effect processing succeeded.
241 return true;
242}
243
244bool EffectSoundTouch::ProcessStereo(soundtouch::SoundTouch *pSoundTouch,
245 WaveTrack &orig, WaveTrack &outputTrack,
246 sampleCount start, sampleCount end, const TimeWarper &warper)
247{
248 pSoundTouch->setSampleRate(
249 static_cast<unsigned int>(orig.GetRate() + 0.5));
250
251 auto channels = orig.Channels();
252 auto &leftTrack = **channels.first++;
253 auto &rightTrack = **channels.first;
254
255 auto newChannels = outputTrack.Channels();
256 auto &outputLeftTrack = **newChannels.first++;
257 auto &outputRightTrack = **newChannels.first;
258
259 //Get the length of the buffer (as double). len is
260 //used simple to calculate a progress meter, so it is easier
261 //to make it a double now than it is to do it later
262 double len = (end - start).as_double();
263
264 //Initiate a processing buffer. This buffer will (most likely)
265 //be shorter than the length of the track being processed.
266 // Make soundTouchBuffer twice as big as MaxBlockSize for each channel,
267 // because Soundtouch wants them interleaved, i.e., each
268 // Soundtouch sample is left-right pair.
269 auto maxBlockSize = orig.GetMaxBlockSize();
270 {
271 Floats leftBuffer{ maxBlockSize };
272 Floats rightBuffer{ maxBlockSize };
273 Floats soundTouchBuffer{ maxBlockSize * 2 };
274
275 // Go through the track one stereo buffer at a time.
276 // sourceSampleCount counts the sample at which the current buffer starts,
277 // per channel.
278 auto sourceSampleCount = start;
279 while (sourceSampleCount < end) {
280 auto blockSize = limitSampleBufferSize(
281 orig.GetBestBlockSize(sourceSampleCount),
282 end - sourceSampleCount
283 );
284
285 // Get the samples from the tracks and put them in the buffers.
286 leftTrack.GetFloats((leftBuffer.get()), sourceSampleCount, blockSize);
287 rightTrack
288 .GetFloats((rightBuffer.get()), sourceSampleCount, blockSize);
289
290 // Interleave into soundTouchBuffer.
291 for (decltype(blockSize) index = 0; index < blockSize; index++) {
292 soundTouchBuffer[index * 2] = leftBuffer[index];
293 soundTouchBuffer[(index * 2) + 1] = rightBuffer[index];
294 }
295
296 //Add samples to SoundTouch
297 pSoundTouch->putSamples(soundTouchBuffer.get(), blockSize);
298
299 //Get back samples from SoundTouch
300 unsigned int outputCount = pSoundTouch->numSamples();
301 if (outputCount > 0)
302 this->ProcessStereoResults(pSoundTouch,
303 outputCount, outputLeftTrack, outputRightTrack);
304
305 //Increment sourceSampleCount one blockfull of samples
306 sourceSampleCount += blockSize;
307
308 //Update the Progress meter
309 // mCurTrackNum is left track. Include right track.
310 int nWhichTrack = mCurTrackNum;
311 double frac = (sourceSampleCount - start).as_double() / len;
312 if (frac < 0.5)
313 frac *= 2.0; // Show twice as far for each track, because we're doing 2 at once.
314 else
315 {
316 nWhichTrack++;
317 frac -= 0.5;
318 frac *= 2.0; // Show twice as far for each track, because we're doing 2 at once.
319 }
320 if (TrackProgress(nWhichTrack, frac))
321 return false;
322 }
323
324 // Tell SoundTouch to finish processing any remaining samples
325 pSoundTouch->flush();
326
327 unsigned int outputCount = pSoundTouch->numSamples();
328 if (outputCount > 0)
329 this->ProcessStereoResults(pSoundTouch,
330 outputCount, outputLeftTrack, outputRightTrack);
331
332 outputTrack.Flush();
333 }
334
335 // Transfer output samples to the original
336 Finalize(orig, outputTrack, warper);
337
338
339 // Track the longest result length
340 double newLength = outputTrack.GetEndTime();
341 m_maxNewLength = std::max(m_maxNewLength, newLength);
342
343 //Return true because the effect processing succeeded.
344 return true;
345}
346
347bool EffectSoundTouch::ProcessStereoResults(soundtouch::SoundTouch *pSoundTouch,
348 const size_t outputCount,
349 WaveChannel &outputLeftTrack,
350 WaveChannel &outputRightTrack)
351{
352 Floats outputSoundTouchBuffer{ outputCount * 2 };
353 pSoundTouch->receiveSamples(outputSoundTouchBuffer.get(), outputCount);
354
355 // Dis-interleave outputSoundTouchBuffer into separate track buffers.
356 Floats outputLeftBuffer{ outputCount };
357 Floats outputRightBuffer{ outputCount };
358 for (unsigned int index = 0; index < outputCount; ++index) {
359 outputLeftBuffer[index] = outputSoundTouchBuffer[index * 2];
360 outputRightBuffer[index] = outputSoundTouchBuffer[(index * 2) + 1];
361 }
362
363 outputLeftTrack.Append(
364 (samplePtr)outputLeftBuffer.get(), floatSample, outputCount);
365 outputRightTrack.Append(
366 (samplePtr)outputRightBuffer.get(), floatSample, outputCount);
367
368 return true;
369}
370
371void EffectSoundTouch::Finalize(
372 WaveTrack &orig, WaveTrack &out, const TimeWarper &warper)
373{
374 assert(out.NChannels() == orig.NChannels());
375 if (mPreserveLength) {
376 auto newLen = out.GetVisibleSampleCount();
377 auto oldLen = out.TimeToLongSamples(mT1) - out.TimeToLongSamples(mT0);
378
379 // Pad output track to original length since SoundTouch may remove samples
380 if (newLen < oldLen) {
381 const auto t = out.LongSamplesToTime(newLen - 1);
382 const auto len = out.LongSamplesToTime(oldLen - newLen);
383 out.InsertSilence(t, len);
384 }
385 // Trim output track to original length since SoundTouch may add extra samples
386 else if (newLen > oldLen) {
387 const auto t1 = out.LongSamplesToTime(oldLen);
388 out.Trim(0, t1);
389 }
390 }
391
392 // Silenced samples will be inserted in gaps between clips, so capture where
393 // these gaps are for later deletion
394 std::vector<std::pair<double, double>> gaps;
395 double last = mT0;
396 auto clips = orig.SortedIntervalArray();
397 auto front = clips.front();
398 auto back = clips.back();
399 for (auto &clip : clips) {
400 auto st = clip->GetPlayStartTime();
401 auto et = clip->GetPlayEndTime();
402
403 if (st >= mT0 || et < mT1) {
404 if (mT0 < st && clip == front) {
405 gaps.push_back(std::make_pair(mT0, st));
406 }
407 else if (last < st && mT0 <= last ) {
408 gaps.push_back(std::make_pair(last, st));
409 }
410
411 if (et < mT1 && clip == back) {
412 gaps.push_back(std::make_pair(et, mT1));
413 }
414 }
415 last = et;
416 }
417
418 // Take the output track and insert it in place of the original sample data
419 orig.ClearAndPaste(mT0, mT1, out, true, true, &warper);
420
421 // Finally, recreate the gaps
422 for (auto gap : gaps) {
423 const auto st = orig.SnapToSample(gap.first);
424 const auto et = orig.SnapToSample(gap.second);
425 if (st >= mT0 && et <= mT1 && st != et)
426 orig.SplitDelete(warper.Warp(st), warper.Warp(et));
427 }
428}
429
430#endif // USE_SOUNDTOUCH
static const int gap
Definition: MeterPanel.cpp:257
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
char * samplePtr
Definition: SampleFormat.h:57
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
Use this object to copy the input tracks to tentative outputTracks.
TrackList & Get()
Expose the output track list for iterations or even erasures.
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:95
void WarpLabels(const TimeWarper &warper)
Definition: LabelTrack.cpp:297
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:78
void WarpAndTransposeNotes(double t0, double t1, const TimeWarper &warper, double semitones)
Definition: NoteTrack.cpp:220
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:62
virtual double Warp(double originalTime) const =0
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:78
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
double GetRate() const override
Definition: WaveTrack.cpp:793
WaveTrack & GetTrack()
Definition: WaveTrack.h:840
bool Append(constSamplePtr buffer, sampleFormat format, size_t len)
Definition: WaveTrack.cpp:2216
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:850
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:858
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void SplitDelete(double t0, double t1)
Definition: WaveTrack.cpp:1503
void InsertSilence(double t, double len) override
Definition: WaveTrack.cpp:2002
auto Channels()
Definition: WaveTrack.h:263
bool Append(size_t iChannel, constSamplePtr buffer, sampleFormat format, size_t len, unsigned int stride=1, sampleFormat effectiveFormat=widestSampleFormat) override
Definition: WaveTrack.cpp:2227
void Flush() override
Definition: WaveTrack.cpp:2294
IntervalHolders SortedIntervalArray()
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3270
void Trim(double t0, double t1)
Definition: WaveTrack.cpp:957
double GetEndTime() const override
Implement WideSampleSequence.
Definition: WaveTrack.cpp:2586
double GetRate() const override
Definition: WaveTrack.cpp:798
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:532
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:2258
size_t GetBestBlockSize(sampleCount t) const
Definition: WaveTrack.cpp:2239
void ClearAndPaste(double t0, double t1, const WaveTrack &src, bool preserve=true, bool merge=true, const TimeWarper *effectWarper=nullptr, bool clearByTrimming=false)
Definition: WaveTrack.cpp:1196
sampleCount GetVisibleSampleCount() const
Definition: WaveTrack.cpp:885
double LongSamplesToTime(sampleCount pos) const
sampleCount TimeToLongSamples(double t0) const
double SnapToSample(double t) const
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
const char * end(const char *str) noexcept
Definition: StringUtils.h:106