Audacity 3.2.0
Paulstretch.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Paulstretch.cpp
6
7 Nasca Octavian Paul (Paul Nasca)
8 Some GUI code was taken from the Echo effect
9
10*******************************************************************//*******************************************************************/
16
17
18#include "Paulstretch.h"
19#include "LoadEffects.h"
20
21#include <algorithm>
22
23#include <math.h>
24
25#include <wx/intl.h>
26#include <wx/valgen.h>
27
28#include "../ShuttleGui.h"
29#include "FFT.h"
30#include "../widgets/valnum.h"
31#include "../widgets/AudacityMessageBox.h"
32#include "Prefs.h"
33
34#include "../WaveTrack.h"
35
37{
40 > parameters;
41 return parameters;
42}
43
47{
48public:
49 PaulStretch(float rap_, size_t in_bufsize_, float samplerate_);
50 //in_bufsize is also a half of a FFT buffer (in samples)
51 virtual ~PaulStretch();
52
53 void process(float *smps, size_t nsmps);
54
55 size_t get_nsamples();//how many samples are required to be added in the pool next time
56 size_t get_nsamples_for_fill();//how many samples are required to be added for a complete buffer refill (at start of the song or after seek)
57
58private:
59 void process_spectrum(float *WXUNUSED(freq)) {};
60
61 const float samplerate;
62 const float rap;
63 const size_t in_bufsize;
64
65public:
66 const size_t out_bufsize;
68
69private:
71
72public:
73 const size_t poolsize;//how many samples are inside the input_pool size (need to know how many samples to fill when seeking)
74
75private:
76 const Floats in_pool;//de marimea in_bufsize
77
78 double remained_samples;//how many fraction of samples has remained (0..1)
79
81};
82
83//
84// EffectPaulstretch
85//
86
88{ XO("Paulstretch") };
89
91
92BEGIN_EVENT_TABLE(EffectPaulstretch, wxEvtHandler)
93 EVT_TEXT(wxID_ANY, EffectPaulstretch::OnText)
95
97{
98 Parameters().Reset(*this);
99
100 SetLinearEffectFlag(true);
101}
102
104{
105}
106
107// ComponentInterface implementation
108
110{
111 return Symbol;
112}
113
115{
116 return XO("Paulstretch is only for an extreme time-stretch or \"stasis\" effect");
117}
118
120{
121 return L"Paulstretch";
122}
123
124// EffectDefinitionInterface implementation
125
127{
128 return EffectTypeProcess;
129}
130
131// Effect implementation
132
134 const EffectSettings &, double previewLength) const
135{
136 // FIXME: Preview is currently at the project rate, but should really be
137 // at the track rate (bugs 1284 and 852).
138 auto minDuration = GetBufferSize(mProjectRate) * 2 + 1;
139
140 // Preview playback may need to be trimmed but this is the smallest selection that we can use.
141 double minLength = std::max<double>(minDuration / mProjectRate, previewLength / mAmount);
142
143 return minLength;
144}
145
146
148{
150 m_t1=mT1;
151 int count=0;
152 for( auto track : mOutputTracks->Selected< WaveTrack >() ) {
153 double trackStart = track->GetStartTime();
154 double trackEnd = track->GetEndTime();
155 double t0 = mT0 < trackStart? trackStart: mT0;
156 double t1 = mT1 > trackEnd? trackEnd: mT1;
157
158 if (t1 > t0) {
159 if (!ProcessOne(track, t0,t1,count))
160 return false;
161 }
162
163 count++;
164 }
165 mT1=m_t1;
166
168
169 return true;
170}
171
172
173std::unique_ptr<EffectUIValidator> EffectPaulstretch::PopulateOrExchange(
175{
176 S.StartMultiColumn(2, wxALIGN_CENTER);
177 {
178 S
179 .Validator<FloatingPointValidator<float>>(
180 1, &mAmount, NumValidatorStyle::DEFAULT, Amount.min)
181 /* i18n-hint: This is how many times longer the sound will be, e.g. applying
182 * the effect to a 1-second sample, with the default Stretch Factor of 10.0
183 * will give an (approximately) 10 second sound
184 */
185 .AddTextBox(XXO("&Stretch Factor:"), wxT(""), 10);
186
187 S
188 .Validator<FloatingPointValidator<float>>(
189 3, &mTime_resolution, NumValidatorStyle::ONE_TRAILING_ZERO, Time.min)
190 .AddTextBox(XXO("&Time Resolution (seconds):"), L"", 10);
191 }
192 S.EndMultiColumn();
193 return nullptr;
194};
195
196// EffectPaulstretch implementation
197
198void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt))
199{
200 EnableApply(mUIParent->TransferDataFromWindow());
201}
202
203size_t EffectPaulstretch::GetBufferSize(double rate) const
204{
205 // Audacity's fft requires a power of 2
206 float tmp = rate * mTime_resolution / 2.0;
207 tmp = log(tmp) / log(2.0);
208 tmp = pow(2.0, floor(tmp + 0.5));
209
210 auto stmp = size_t(tmp);
211 if (stmp != tmp)
212 // overflow
213 return 0;
214 if (stmp >= 2 * stmp)
215 // overflow
216 return 0;
217
218 return std::max<size_t>(stmp, 128);
219}
220
221bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count)
222{
223 const auto badAllocMessage =
224 XO("Requested value exceeds memory capacity.");
225
226 const auto stretch_buf_size = GetBufferSize(track->GetRate());
227 if (stretch_buf_size == 0) {
228 ::Effect::MessageBox( badAllocMessage );
229 return false;
230 }
231
232 double amount = this->mAmount;
233
234 auto start = track->TimeToLongSamples(t0);
235 auto end = track->TimeToLongSamples(t1);
236 auto len = end - start;
237
238 const auto minDuration = stretch_buf_size * 2 + 1;
239 if (minDuration < stretch_buf_size) {
240 // overflow!
241 ::Effect::MessageBox( badAllocMessage );
242 return false;
243 }
244
245 if (len < minDuration) { //error because the selection is too short
246
247 float maxTimeRes = log( len.as_double() ) / log(2.0);
248 maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
249 maxTimeRes = maxTimeRes / track->GetRate();
250
251 if (this->IsPreviewing()) {
252 double defaultPreviewLen;
253 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
254
255 if ((minDuration / mProjectRate) < defaultPreviewLen) {
257 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
258 XO("Audio selection too short to preview.\n\n"
259 "Try increasing the audio selection to at least %.1f seconds,\n"
260 "or reducing the 'Time Resolution' to less than %.1f seconds.")
261 .Format(
262 (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
263 floor(maxTimeRes * 10.0) / 10.0),
264 wxOK | wxICON_EXCLAMATION );
265 }
266 else {
268 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
269 XO("Unable to Preview.\n\n"
270 "For the current audio selection, the maximum\n"
271 "'Time Resolution' is %.1f seconds.")
272 .Format( floor(maxTimeRes * 10.0) / 10.0 ),
273 wxOK | wxICON_EXCLAMATION );
274 }
275 }
276 else {
278 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
279 XO("The 'Time Resolution' is too long for the selection.\n\n"
280 "Try increasing the audio selection to at least %.1f seconds,\n"
281 "or reducing the 'Time Resolution' to less than %.1f seconds.")
282 .Format(
283 (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
284 floor(maxTimeRes * 10.0) / 10.0),
285 wxOK | wxICON_EXCLAMATION );
286 }
287
288 return false;
289 }
290
291
292 auto dlen = len.as_double();
293 double adjust_amount = dlen /
294 (dlen - ((double)stretch_buf_size * 2.0));
295 amount = 1.0 + (amount - 1.0) * adjust_amount;
296
297 auto outputTrack = track->EmptyCopy();
298
299 try {
300 // This encloses all the allocations of buffers, including those in
301 // the constructor of the PaulStretch object
302
303 PaulStretch stretch(amount, stretch_buf_size, track->GetRate());
304
305 auto nget = stretch.get_nsamples_for_fill();
306
307 auto bufsize = stretch.poolsize;
308 Floats buffer0{ bufsize };
309 float *bufferptr0 = buffer0.get();
310 bool first_time = true;
311
312 const auto fade_len = std::min<size_t>(100, bufsize / 2 - 1);
313 bool cancelled = false;
314
315 {
316 Floats fade_track_smps{ fade_len };
317 decltype(len) s=0;
318
319 while (s < len) {
320 track->GetFloats(bufferptr0, start + s, nget);
321 stretch.process(buffer0.get(), nget);
322
323 if (first_time) {
324 stretch.process(buffer0.get(), 0);
325 };
326
327 s += nget;
328
329 if (first_time){//blend the start of the selection
330 track->GetFloats(fade_track_smps.get(), start, fade_len);
331 first_time = false;
332 for (size_t i = 0; i < fade_len; i++){
333 float fi = (float)i / (float)fade_len;
334 stretch.out_buf[i] =
335 stretch.out_buf[i] * fi + (1.0 - fi) * fade_track_smps[i];
336 }
337 }
338 if (s >= len){//blend the end of the selection
339 track->GetFloats(fade_track_smps.get(), end - fade_len, fade_len);
340 for (size_t i = 0; i < fade_len; i++){
341 float fi = (float)i / (float)fade_len;
342 auto i2 = bufsize / 2 - 1 - i;
343 stretch.out_buf[i2] =
344 stretch.out_buf[i2] * fi + (1.0 - fi) *
345 fade_track_smps[fade_len - 1 - i];
346 }
347 }
348
349 outputTrack->Append((samplePtr)stretch.out_buf.get(), floatSample, stretch.out_bufsize);
350
351 nget = stretch.get_nsamples();
352 if (TrackProgress(count,
353 s.as_double() / len.as_double()
354 )) {
355 cancelled = true;
356 break;
357 }
358 }
359 }
360
361 if (!cancelled){
362 outputTrack->Flush();
363
364 track->Clear(t0,t1);
365 track->Paste(t0, outputTrack.get());
366 m_t1 = mT0 + outputTrack->GetEndTime();
367 }
368
369 return !cancelled;
370 }
371 catch ( const std::bad_alloc& ) {
372 ::Effect::MessageBox( badAllocMessage );
373 return false;
374 }
375};
376
377/*************************************************************/
378
379
380PaulStretch::PaulStretch(float rap_, size_t in_bufsize_, float samplerate_ )
381 : samplerate { samplerate_ }
382 , rap { std::max(1.0f, rap_) }
383 , in_bufsize { in_bufsize_ }
384 , out_bufsize { std::max(size_t{ 8 }, in_bufsize) }
385 , out_buf { out_bufsize }
386 , old_out_smp_buf { out_bufsize * 2, true }
387 , poolsize { in_bufsize_ * 2 }
388 , in_pool { poolsize, true }
389 , remained_samples { 0.0 }
390 , fft_smps { poolsize, true }
391 , fft_c { poolsize, true }
392 , fft_s { poolsize, true }
393 , fft_freq { poolsize, true }
394 , fft_tmp { poolsize }
395{
396}
397
399{
400}
401
402void PaulStretch::process(float *smps, size_t nsmps)
403{
404 //add NEW samples to the pool
405 if ((smps != NULL) && (nsmps != 0)) {
406 if (nsmps > poolsize) {
407 nsmps = poolsize;
408 }
409 int nleft = poolsize - nsmps;
410
411 //move left the samples from the pool to make room for NEW samples
412 for (int i = 0; i < nleft; i++)
413 in_pool[i] = in_pool[i + nsmps];
414
415 //add NEW samples to the pool
416 for (size_t i = 0; i < nsmps; i++)
417 in_pool[i + nleft] = smps[i];
418 }
419
420 //get the samples from the pool
421 for (size_t i = 0; i < poolsize; i++)
422 fft_smps[i] = in_pool[i];
424
425 RealFFT(poolsize, fft_smps.get(), fft_c.get(), fft_s.get());
426
427 for (size_t i = 0; i < poolsize / 2; i++)
428 fft_freq[i] = sqrt(fft_c[i] * fft_c[i] + fft_s[i] * fft_s[i]);
430
431
432 //put randomize phases to frequencies and do a IFFT
433 float inv_2p15_2pi = 1.0 / 16384.0 * (float)M_PI;
434 for (size_t i = 1; i < poolsize / 2; i++) {
435 unsigned int random = (rand()) & 0x7fff;
436 float phase = random * inv_2p15_2pi;
437 float s = fft_freq[i] * sin(phase);
438 float c = fft_freq[i] * cos(phase);
439
440 fft_c[i] = fft_c[poolsize - i] = c;
441
442 fft_s[i] = s; fft_s[poolsize - i] = -s;
443 }
444 fft_c[0] = fft_s[0] = 0.0;
445 fft_c[poolsize / 2] = fft_s[poolsize / 2] = 0.0;
446
447 FFT(poolsize, true, fft_c.get(), fft_s.get(), fft_smps.get(), fft_tmp.get());
448
449 float max = 0.0, max2 = 0.0;
450 for (size_t i = 0; i < poolsize; i++) {
451 max = std::max(max, fabsf(fft_tmp[i]));
452 max2 = std::max(max2, fabsf(fft_smps[i]));
453 }
454
455
456 //make the output buffer
457 float tmp = 1.0 / (float) out_bufsize * M_PI;
458 float hinv_sqrt2 = 0.853553390593f;//(1.0+1.0/sqrt(2))*0.5;
459
460 float ampfactor = 1.0;
461 if (rap < 1.0)
462 ampfactor = rap * 0.707;
463 else
464 ampfactor = (out_bufsize / (float)poolsize) * 4.0;
465
466 for (size_t i = 0; i < out_bufsize; i++) {
467 float a = (0.5 + 0.5 * cos(i * tmp));
468 float out = fft_smps[i + out_bufsize] * (1.0 - a) + old_out_smp_buf[i] * a;
469 out_buf[i] =
470 out * (hinv_sqrt2 - (1.0 - hinv_sqrt2) * cos(i * 2.0 * tmp)) *
471 ampfactor;
472 }
473
474 //copy the current output buffer to old buffer
475 for (size_t i = 0; i < out_bufsize * 2; i++)
477}
478
480{
481 double r = out_bufsize / rap;
482 auto ri = (size_t)floor(r);
483 double rf = r - floor(r);
484
485 remained_samples += rf;
486 if (remained_samples >= 1.0){
487 ri += (size_t)floor(remained_samples);
489 }
490
491 if (ri > poolsize) {
492 ri = poolsize;
493 }
494
495 return ri;
496}
497
499{
500 return poolsize;
501}
END_EVENT_TABLE()
#define M_PI
Definition: Distortion.cpp:29
EffectType
@ EffectTypeProcess
void WindowFunc(int whichFunction, size_t NumSamples, float *in)
Definition: FFT.cpp:515
void RealFFT(size_t NumSamples, const float *RealIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:230
void FFT(size_t NumSamples, bool InverseTransform, const float *RealIn, const float *ImagIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:131
@ eWinFuncHann
Definition: FFT.h:114
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
FileConfig * gPrefs
Definition: Prefs.cpp:71
@ floatSample
Definition: SampleFormat.h:34
char * samplePtr
Definition: SampleFormat.h:49
#define S(N)
Definition: ToChars.cpp:64
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:107
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:105
bool IsPreviewing() const
Definition: EffectBase.h:83
double mProjectRate
Definition: EffectBase.h:99
double mT0
Definition: EffectBase.h:106
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: EffectBase.cpp:236
void CopyInputTracks(bool allSyncLockSelected=false)
Definition: Effect.cpp:739
int MessageBox(const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={}) const
Definition: Effect.cpp:869
bool EnableApply(bool enable=true)
Definition: Effect.cpp:613
wxWindow * mUIParent
Definition: Effect.h:270
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:691
Performs effect computation.
Interface for manipulations of an Effect's settings.
An Extreme Time Stretch and Time Smear effect.
Definition: Paulstretch.h:20
std::unique_ptr< EffectUIValidator > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access) override
Add controls to effect panel; always succeeds.
static constexpr EffectParameter Time
Definition: Paulstretch.h:65
float mTime_resolution
Definition: Paulstretch.h:57
const EffectParameterMethods & Parameters() const override
Definition: Paulstretch.cpp:36
virtual ~EffectPaulstretch()
static const ComponentInterfaceSymbol Symbol
Definition: Paulstretch.h:24
TranslatableString GetDescription() const override
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
double CalcPreviewInputLength(const EffectSettings &settings, double previewLength) const override
Default implementation returns previewLength
EffectType GetType() const override
Type determines how it behaves.
bool ProcessOne(WaveTrack *track, double t0, double t1, int count)
void OnText(wxCommandEvent &evt)
bool Process(EffectInstance &instance, EffectSettings &settings) override
Actually do the effect here.
size_t GetBufferSize(double rate) const
ComponentInterfaceSymbol GetSymbol() const override
static constexpr EffectParameter Amount
Definition: Paulstretch.h:63
Abstract base class used in importing a file.
Class that helps EffectPaulStretch. It does the FFTs and inner loop of the effect.
Definition: Paulstretch.cpp:47
const float rap
Definition: Paulstretch.cpp:62
void process(float *smps, size_t nsmps)
const Floats in_pool
Definition: Paulstretch.cpp:76
virtual ~PaulStretch()
double remained_samples
Definition: Paulstretch.cpp:78
const size_t out_bufsize
Definition: Paulstretch.cpp:66
const Floats old_out_smp_buf
Definition: Paulstretch.cpp:70
const Floats fft_tmp
Definition: Paulstretch.cpp:80
size_t get_nsamples_for_fill()
const float samplerate
Definition: Paulstretch.cpp:59
const Floats fft_freq
Definition: Paulstretch.cpp:80
PaulStretch(float rap_, size_t in_bufsize_, float samplerate_)
const Floats fft_smps
Definition: Paulstretch.cpp:80
const size_t in_bufsize
Definition: Paulstretch.cpp:63
const size_t poolsize
Definition: Paulstretch.cpp:73
const Floats out_buf
Definition: Paulstretch.cpp:67
size_t get_nsamples()
void process_spectrum(float *WXUNUSED(freq))
Definition: Paulstretch.cpp:59
const Floats fft_s
Definition: Paulstretch.cpp:80
const Floats fft_c
Definition: Paulstretch.cpp:80
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
Retrieve samples from a track in floating-point format, regardless of the storage format.
Definition: SampleTrack.h:65
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:35
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
Holds a msgid for the translation catalog; may also bind format arguments.
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
void Paste(double t0, const Track *src) override
Definition: WaveTrack.cpp:1565
void Clear(double t0, double t1) override
Definition: WaveTrack.cpp:790
double GetRate() const override
Definition: WaveTrack.cpp:481
Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory={}, bool keepLink=true) const
Definition: WaveTrack.cpp:707
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinEffectsModule::Registration< EffectPaulstretch > reg
Definition: Paulstretch.cpp:90
STL namespace.
const Type min
Minimum value.
Definition: Shuttle.h:30
Externalized state of a plug-in.