Audacity 3.2.0
PaulstretchBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 PaulstretchBase.cpp
6
7 Nasca Octavian Paul (Paul Nasca)
8
9*******************************************************************//*******************************************************************/
15#include "PaulstretchBase.h"
16#include "BasicUI.h"
17#include "EffectOutputTracks.h"
18#include "FFT.h"
19#include "Prefs.h"
20#include "SyncLock.h"
21#include "TimeWarper.h"
22#include "WaveTrack.h"
23#include <algorithm>
24#include <cfloat> // FLT_MAX
25#include <cmath>
26
28
30{
32 return parameters;
33}
34
38{
39public:
40 PaulStretch(float rap_, size_t in_bufsize_, float samplerate_);
41 // in_bufsize is also a half of a FFT buffer (in samples)
42 virtual ~PaulStretch();
43
44 void process(float* smps, size_t nsmps);
45
46 size_t get_nsamples(); // how many samples are required to be added in the
47 // pool next time
48 size_t get_nsamples_for_fill(); // how many samples are required to be added
49 // for a complete buffer refill (at start of
50 // the song or after seek)
51
52private:
53 void process_spectrum(float* WXUNUSED(freq)) {};
54
55 const float samplerate;
56 const float rap;
57 const size_t in_bufsize;
58
59public:
60 const size_t out_bufsize;
62
63private:
65
66public:
67 const size_t
68 poolsize; // how many samples are inside the input_pool size
69 // (need to know how many samples to fill when seeking)
70
71private:
72 const Floats in_pool; // de marimea in_bufsize
73
74 double remained_samples; // how many fraction of samples has remained (0..1)
75
77};
78
80{
81 Parameters().Reset(*this);
82
84}
85
87{
88}
89
90// ComponentInterface implementation
91
93{
94 return Symbol;
95}
96
98{
99 return XO(
100 "Paulstretch is only for an extreme time-stretch or \"stasis\" effect");
101}
102
104{
105 return L"Paulstretch";
106}
107
108// EffectDefinitionInterface implementation
109
111{
112 return EffectTypeProcess;
113}
114
115// Effect implementation
116
118 const EffectSettings&, double previewLength) const
119{
120 // FIXME: Preview is currently at the project rate, but should really be
121 // at the track rate (bugs 1284 and 852).
122 auto minDuration = GetBufferSize(mProjectRate) * 2 + 1;
123
124 // Preview playback may need to be trimmed but this is the smallest selection
125 // that we can use.
126 double minLength =
127 std::max<double>(minDuration / mProjectRate, previewLength / mAmount);
128
129 return minLength;
130}
131
133{
134 // Pass true because sync lock adjustment is needed
135 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } }, true };
136 auto newT1 = mT1;
137 int count = 0;
138 // Process selected wave tracks first, to find the new t1 value
139 for (const auto track : outputs.Get().Selected<WaveTrack>())
140 {
141 double trackStart = track->GetStartTime();
142 double trackEnd = track->GetEndTime();
143 double t0 = mT0 < trackStart ? trackStart : mT0;
144 double t1 = mT1 > trackEnd ? trackEnd : mT1;
145 if (t1 > t0)
146 {
147 auto tempTrack = track->EmptyCopy();
148 const auto channels = track->Channels();
149 auto iter = tempTrack->Channels().begin();
150 for (const auto pChannel : channels)
151 {
152 if (!ProcessOne(*pChannel, **iter++, t0, t1, count++))
153 return false;
154 }
155 tempTrack->Flush();
156 newT1 = std::max(newT1, mT0 + tempTrack->GetEndTime());
157 PasteTimeWarper warper { t1, t0 + tempTrack->GetEndTime() };
158 constexpr auto preserve = false;
159 constexpr auto merge = true;
160 track->ClearAndPaste(t0, t1, *tempTrack, preserve, merge, &warper);
161 }
162 else
163 count += track->NChannels();
164 }
165
166 // Sync lock adjustment of other tracks
167 outputs.Get().Any().Visit(
168 [&](auto&& fallthrough) {
169 return [&](WaveTrack& track) {
170 if (!track.IsSelected())
171 fallthrough();
172 };
173 },
174 [&](Track& track) {
176 track.SyncLockAdjust(mT1, newT1);
177 });
178 mT1 = newT1;
179
180 outputs.Commit();
181
182 return true;
183}
184
186 const WaveChannel& track, WaveChannel& outputTrack, double t0, double t1,
187 int count)
188{
189 const auto badAllocMessage = XO("Requested value exceeds memory capacity.");
190
191 const auto rate = track.GetTrack().GetRate();
192 const auto stretch_buf_size = GetBufferSize(rate);
193 if (stretch_buf_size == 0)
194 {
195 BasicUI::ShowMessageBox(badAllocMessage);
196 return {};
197 }
198
199 double amount = this->mAmount;
200
201 auto start = track.TimeToLongSamples(t0);
202 auto end = track.TimeToLongSamples(t1);
203 auto len = end - start;
204
205 const auto minDuration = stretch_buf_size * 2 + 1;
206 if (minDuration < stretch_buf_size)
207 {
208 // overflow!
209 BasicUI::ShowMessageBox(badAllocMessage);
210 return {};
211 }
212
213 if (len < minDuration)
214 { // error because the selection is too short
215
216 float maxTimeRes = log(len.as_double()) / log(2.0);
217 maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
218 maxTimeRes = maxTimeRes / rate;
219
220 using namespace BasicUI;
221 if (this->IsPreviewing())
222 {
223 double defaultPreviewLen;
224 gPrefs->Read(
225 wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
226
227 if ((minDuration / mProjectRate) < defaultPreviewLen)
228 {
230 /* i18n-hint: 'Time Resolution' is the name of a control in the
231 Paulstretch effect.*/
232 XO("Audio selection too short to preview.\n\n"
233 "Try increasing the audio selection to at least %.1f seconds,\n"
234 "or reducing the 'Time Resolution' to less than %.1f seconds.")
235 .Format(
236 (minDuration / rate) + 0.05, // round up to 1/10 s.
237 floor(maxTimeRes * 10.0) / 10.0),
238 MessageBoxOptions {}.IconStyle(Icon::Warning));
239 }
240 else
241 {
243 /* i18n-hint: 'Time Resolution' is the name of a control in the
244 Paulstretch effect.*/
245 XO("Unable to Preview.\n\n"
246 "For the current audio selection, the maximum\n"
247 "'Time Resolution' is %.1f seconds.")
248 .Format(floor(maxTimeRes * 10.0) / 10.0),
249 MessageBoxOptions {}.IconStyle(Icon::Warning));
250 }
251 }
252 else
253 {
255 /* i18n-hint: 'Time Resolution' is the name of a control in the
256 Paulstretch effect.*/
257 XO("The 'Time Resolution' is too long for the selection.\n\n"
258 "Try increasing the audio selection to at least %.1f seconds,\n"
259 "or reducing the 'Time Resolution' to less than %.1f seconds.")
260 .Format(
261 (minDuration / rate) + 0.05, // round up to 1/10 s.
262 floor(maxTimeRes * 10.0) / 10.0),
263 MessageBoxOptions {}.IconStyle(Icon::Warning));
264 }
265
266 return {};
267 }
268
269 auto dlen = len.as_double();
270 double adjust_amount = dlen / (dlen - ((double)stretch_buf_size * 2.0));
271 amount = 1.0 + (amount - 1.0) * adjust_amount;
272
273 try
274 {
275 // This encloses all the allocations of buffers, including those in
276 // the constructor of the PaulStretch object
277
278 PaulStretch stretch(amount, stretch_buf_size, rate);
279
280 auto nget = stretch.get_nsamples_for_fill();
281
282 auto bufsize = stretch.poolsize;
283 Floats buffer0 { bufsize };
284 float* bufferptr0 = buffer0.get();
285 bool first_time = true;
286
287 const auto fade_len = std::min<size_t>(100, bufsize / 2 - 1);
288 bool cancelled = false;
289
290 {
291 Floats fade_track_smps { fade_len };
292 decltype(len) s = 0;
293
294 while (s < len)
295 {
296 track.GetFloats(bufferptr0, start + s, nget);
297 stretch.process(buffer0.get(), nget);
298
299 if (first_time)
300 {
301 stretch.process(buffer0.get(), 0);
302 };
303
304 s += nget;
305
306 if (first_time)
307 { // blend the start of the selection
308 track.GetFloats(fade_track_smps.get(), start, fade_len);
309 first_time = false;
310 for (size_t i = 0; i < fade_len; i++)
311 {
312 float fi = (float)i / (float)fade_len;
313 stretch.out_buf[i] =
314 stretch.out_buf[i] * fi + (1.0 - fi) * fade_track_smps[i];
315 }
316 }
317 if (s >= len)
318 { // blend the end of the selection
319 track.GetFloats(fade_track_smps.get(), end - fade_len, fade_len);
320 for (size_t i = 0; i < fade_len; i++)
321 {
322 float fi = (float)i / (float)fade_len;
323 auto i2 = bufsize / 2 - 1 - i;
324 stretch.out_buf[i2] =
325 stretch.out_buf[i2] * fi +
326 (1.0 - fi) * fade_track_smps[fade_len - 1 - i];
327 }
328 }
329
330 outputTrack.Append(
331 (samplePtr)stretch.out_buf.get(), floatSample,
332 stretch.out_bufsize);
333
334 nget = stretch.get_nsamples();
335 if (TrackProgress(count, s.as_double() / len.as_double()))
336 {
337 cancelled = true;
338 break;
339 }
340 }
341 }
342
343 if (!cancelled)
344 return true;
345 }
346 catch (const std::bad_alloc&)
347 {
348 BasicUI::ShowMessageBox(badAllocMessage);
349 }
350 return false;
351};
352
353size_t PaulstretchBase::GetBufferSize(double rate) const
354{
355 // Audacity's fft requires a power of 2
356 float tmp = rate * mTime_resolution / 2.0;
357 tmp = log(tmp) / log(2.0);
358 tmp = pow(2.0, floor(tmp + 0.5));
359
360 auto stmp = size_t(tmp);
361 if (stmp != tmp)
362 // overflow
363 return 0;
364 if (stmp >= 2 * stmp)
365 // overflow
366 return 0;
367
368 return std::max<size_t>(stmp, 128);
369}
370
371/*************************************************************/
372
373PaulStretch::PaulStretch(float rap_, size_t in_bufsize_, float samplerate_)
374 : samplerate { samplerate_ }
375 , rap { std::max(1.0f, rap_) }
376 , in_bufsize { in_bufsize_ }
377 , out_bufsize { std::max(size_t { 8 }, in_bufsize) }
378 , out_buf { out_bufsize }
379 , old_out_smp_buf { out_bufsize * 2, true }
380 , poolsize { in_bufsize_ * 2 }
381 , in_pool { poolsize, true }
382 , remained_samples { 0.0 }
383 , fft_smps { poolsize, true }
384 , fft_c { poolsize, true }
385 , fft_s { poolsize, true }
386 , fft_freq { poolsize, true }
387 , fft_tmp { poolsize }
388{
389}
390
392{
393}
394
395void PaulStretch::process(float* smps, size_t nsmps)
396{
397 // add NEW samples to the pool
398 if ((smps != NULL) && (nsmps != 0))
399 {
400 if (nsmps > poolsize)
401 {
402 nsmps = poolsize;
403 }
404 int nleft = poolsize - nsmps;
405
406 // move left the samples from the pool to make room for NEW samples
407 for (int i = 0; i < nleft; i++)
408 in_pool[i] = in_pool[i + nsmps];
409
410 // add NEW samples to the pool
411 for (size_t i = 0; i < nsmps; i++)
412 in_pool[i + nleft] = smps[i];
413 }
414
415 // get the samples from the pool
416 for (size_t i = 0; i < poolsize; i++)
417 fft_smps[i] = in_pool[i];
419
420 RealFFT(poolsize, fft_smps.get(), fft_c.get(), fft_s.get());
421
422 for (size_t i = 0; i < poolsize / 2; i++)
423 fft_freq[i] = sqrt(fft_c[i] * fft_c[i] + fft_s[i] * fft_s[i]);
425
426 // put randomize phases to frequencies and do a IFFT
427 float inv_2p15_2pi = 1.0 / 16384.0 * (float)M_PI;
428 for (size_t i = 1; i < poolsize / 2; i++)
429 {
430 unsigned int random = (rand()) & 0x7fff;
431 float phase = random * inv_2p15_2pi;
432 float s = fft_freq[i] * sin(phase);
433 float c = fft_freq[i] * cos(phase);
434
435 fft_c[i] = fft_c[poolsize - i] = c;
436
437 fft_s[i] = s;
438 fft_s[poolsize - i] = -s;
439 }
440 fft_c[0] = fft_s[0] = 0.0;
441 fft_c[poolsize / 2] = fft_s[poolsize / 2] = 0.0;
442
443 FFT(poolsize, true, fft_c.get(), fft_s.get(), fft_smps.get(), fft_tmp.get());
444
445 float max = 0.0, max2 = 0.0;
446 for (size_t i = 0; i < poolsize; i++)
447 {
448 max = std::max(max, fabsf(fft_tmp[i]));
449 max2 = std::max(max2, fabsf(fft_smps[i]));
450 }
451
452 // make the output buffer
453 float tmp = 1.0 / (float)out_bufsize * M_PI;
454 float hinv_sqrt2 = 0.853553390593f; //(1.0+1.0/sqrt(2))*0.5;
455
456 float ampfactor = 1.0;
457 if (rap < 1.0)
458 ampfactor = rap * 0.707;
459 else
460 ampfactor = (out_bufsize / (float)poolsize) * 4.0;
461
462 for (size_t i = 0; i < out_bufsize; i++)
463 {
464 float a = (0.5 + 0.5 * cos(i * tmp));
465 float out =
466 fft_smps[i + out_bufsize] * (1.0 - a) + old_out_smp_buf[i] * a;
467 out_buf[i] = out *
468 (hinv_sqrt2 - (1.0 - hinv_sqrt2) * cos(i * 2.0 * tmp)) *
469 ampfactor;
470 }
471
472 // copy the current output buffer to old buffer
473 for (size_t i = 0; i < out_bufsize * 2; i++)
475}
476
478{
479 double r = out_bufsize / rap;
480 auto ri = (size_t)floor(r);
481 double rf = r - floor(r);
482
483 remained_samples += rf;
484 if (remained_samples >= 1.0)
485 {
486 ri += (size_t)floor(remained_samples);
488 }
489
490 if (ri > poolsize)
491 {
492 ri = poolsize;
493 }
494
495 return ri;
496}
497
499{
500 return poolsize;
501}
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
#define M_PI
Definition: Distortion.cpp:22
EffectType
@ EffectTypeProcess
void WindowFunc(int whichFunction, size_t NumSamples, float *in)
Definition: FFT.cpp:513
void RealFFT(size_t NumSamples, const float *RealIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:228
void FFT(size_t NumSamples, bool InverseTransform, const float *RealIn, const float *ImagIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:129
@ eWinFuncHann
Definition: FFT.h:114
XO("Cut/Copy/Paste")
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
char * samplePtr
Definition: SampleFormat.h:57
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:123
bool IsPreviewing() const
Definition: EffectBase.h:100
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
double mProjectRate
Definition: EffectBase.h:119
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
double mT0
Definition: EffectBase.h:122
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
Abstract base class used in importing a file.
Unit slope but with either a jump (pasting more) or a flat interval (pasting less)
Definition: TimeWarper.h:181
Class that helps EffectPaulStretch. It does the FFTs and inner loop of the effect.
const float rap
void process(float *smps, size_t nsmps)
const Floats in_pool
virtual ~PaulStretch()
double remained_samples
const size_t out_bufsize
const Floats old_out_smp_buf
const Floats fft_tmp
size_t get_nsamples_for_fill()
const float samplerate
const Floats fft_freq
PaulStretch(float rap_, size_t in_bufsize_, float samplerate_)
const Floats fft_smps
const size_t in_bufsize
const size_t poolsize
const Floats out_buf
size_t get_nsamples()
void process_spectrum(float *WXUNUSED(freq))
const Floats fft_s
const Floats fft_c
TranslatableString GetDescription() const override
const EffectParameterMethods & Parameters() const override
bool ProcessOne(const WaveChannel &track, WaveChannel &outputTrack, double t0, double t1, int count)
EffectType GetType() const override
Type determines how it behaves.
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
bool Process(EffectInstance &instance, EffectSettings &settings) override
size_t GetBufferSize(double rate) const
double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const override
virtual ~PaulstretchBase()
static const ComponentInterfaceSymbol Symbol
ComponentInterfaceSymbol GetSymbol() const override
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.
WaveTrack & GetTrack()
Definition: WaveTrack.h:841
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
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
double GetRate() const override
Definition: WaveTrack.cpp:821
sampleCount TimeToLongSamples(double t0) const
virtual bool Read(const wxString &key, bool *value) const =0
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.
MessageBoxOptions && IconStyle(Icon style) &&
Definition: BasicUI.h:104
Externalized state of a plug-in.