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#include "Paulstretch.h"
17#include "EffectEditor.h"
18#include "EffectOutputTracks.h"
19#include "LoadEffects.h"
20
21#include <algorithm>
22
23#include <math.h>
24
25#include <wx/valgen.h>
26
27#include "ShuttleGui.h"
28#include "FFT.h"
29#include "../widgets/valnum.h"
30#include "AudacityMessageBox.h"
31#include "Prefs.h"
32#include "SyncLock.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{
149 // Pass true because sync lock adjustment is needed
150 EffectOutputTracks outputs{ *mTracks, {{ mT0, mT1 }}, true };
151 auto newT1 = mT1;
152 int count = 0;
153 // Process selected wave tracks first, to find the new t1 value
154 for (const auto track : outputs.Get().Selected<WaveTrack>()) {
155 double trackStart = track->GetStartTime();
156 double trackEnd = track->GetEndTime();
157 double t0 = mT0 < trackStart ? trackStart : mT0;
158 double t1 = mT1 > trackEnd ? trackEnd : mT1;
159 if (t1 > t0) {
160 auto tempList = track->WideEmptyCopy();
161 const auto channels = track->Channels();
162 auto iter = (*tempList->Any<WaveTrack>().begin())->Channels().begin();
163 for (const auto pChannel : channels) {
164 if (!ProcessOne(*pChannel, **iter++, t0, t1, count++))
165 return false;
166 }
167 const auto pNewTrack = *tempList->Any<WaveTrack>().begin();
168 pNewTrack->Flush();
169 newT1 = std::max(newT1, mT0 + pNewTrack->GetEndTime());
170 track->Clear(t0, t1);
171 track->Paste(t0, *tempList);
172 }
173 else
174 count += track->NChannels();
175 }
176
177 // Sync lock adjustment of other tracks
178 outputs.Get().Any().Visit(
179 [&](auto &&fallthrough){ return [&](WaveTrack &track) {
180 if (!track.IsSelected())
181 fallthrough();
182 }; },
183 [&](Track &track) {
185 track.SyncLockAdjust(mT1, newT1);
186 }
187 );
188 mT1 = newT1;
189
190 outputs.Commit();
191
192 return true;
193}
194
195
196std::unique_ptr<EffectEditor> EffectPaulstretch::PopulateOrExchange(
198 const EffectOutputs *)
199{
200 mUIParent = S.GetParent();
201 S.StartMultiColumn(2, wxALIGN_CENTER);
202 {
203 S
204 .Validator<FloatingPointValidator<float>>(
205 1, &mAmount, NumValidatorStyle::DEFAULT, Amount.min)
206 /* i18n-hint: This is how many times longer the sound will be, e.g. applying
207 * the effect to a 1-second sample, with the default Stretch Factor of 10.0
208 * will give an (approximately) 10 second sound
209 */
210 .AddTextBox(XXO("&Stretch Factor:"), wxT(""), 10);
211
212 S
213 .Validator<FloatingPointValidator<float>>(
214 3, &mTime_resolution, NumValidatorStyle::ONE_TRAILING_ZERO, Time.min)
215 .AddTextBox(XXO("&Time Resolution (seconds):"), L"", 10);
216 }
217 S.EndMultiColumn();
218 return nullptr;
219};
220
222{
223 if (!mUIParent->TransferDataToWindow())
224 {
225 return false;
226 }
227
228 return true;
229}
230
232{
233 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
234 {
235 return false;
236 }
237
238 return true;
239}
240
241// EffectPaulstretch implementation
242
243void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt))
244{
246 mUIParent, mUIParent->TransferDataFromWindow());
247}
248
249size_t EffectPaulstretch::GetBufferSize(double rate) const
250{
251 // Audacity's fft requires a power of 2
252 float tmp = rate * mTime_resolution / 2.0;
253 tmp = log(tmp) / log(2.0);
254 tmp = pow(2.0, floor(tmp + 0.5));
255
256 auto stmp = size_t(tmp);
257 if (stmp != tmp)
258 // overflow
259 return 0;
260 if (stmp >= 2 * stmp)
261 // overflow
262 return 0;
263
264 return std::max<size_t>(stmp, 128);
265}
266
268 WaveChannel &outputTrack, double t0, double t1, int count)
269{
270 const auto badAllocMessage =
271 XO("Requested value exceeds memory capacity.");
272
273 const auto rate = track.GetTrack().GetRate();
274 const auto stretch_buf_size = GetBufferSize(rate);
275 if (stretch_buf_size == 0) {
276 EffectUIServices::DoMessageBox(*this, badAllocMessage);
277 return {};
278 }
279
280 double amount = this->mAmount;
281
282 auto start = track.TimeToLongSamples(t0);
283 auto end = track.TimeToLongSamples(t1);
284 auto len = end - start;
285
286 const auto minDuration = stretch_buf_size * 2 + 1;
287 if (minDuration < stretch_buf_size) {
288 // overflow!
289 EffectUIServices::DoMessageBox(*this, badAllocMessage);
290 return {};
291 }
292
293 if (len < minDuration) { //error because the selection is too short
294
295 float maxTimeRes = log( len.as_double() ) / log(2.0);
296 maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
297 maxTimeRes = maxTimeRes / rate;
298
299 if (this->IsPreviewing()) {
300 double defaultPreviewLen;
301 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
302
303 if ((minDuration / mProjectRate) < defaultPreviewLen) {
305 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
306 XO("Audio selection too short to preview.\n\n"
307 "Try increasing the audio selection to at least %.1f seconds,\n"
308 "or reducing the 'Time Resolution' to less than %.1f seconds.")
309 .Format(
310 (minDuration / rate) + 0.05, // round up to 1/10 s.
311 floor(maxTimeRes * 10.0) / 10.0),
312 wxOK | wxICON_EXCLAMATION );
313 }
314 else {
316 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
317 XO("Unable to Preview.\n\n"
318 "For the current audio selection, the maximum\n"
319 "'Time Resolution' is %.1f seconds.")
320 .Format( floor(maxTimeRes * 10.0) / 10.0 ),
321 wxOK | wxICON_EXCLAMATION );
322 }
323 }
324 else {
326 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
327 XO("The 'Time Resolution' is too long for the selection.\n\n"
328 "Try increasing the audio selection to at least %.1f seconds,\n"
329 "or reducing the 'Time Resolution' to less than %.1f seconds.")
330 .Format(
331 (minDuration / rate) + 0.05, // round up to 1/10 s.
332 floor(maxTimeRes * 10.0) / 10.0),
333 wxOK | wxICON_EXCLAMATION );
334 }
335
336 return {};
337 }
338
339 auto dlen = len.as_double();
340 double adjust_amount = dlen /
341 (dlen - ((double)stretch_buf_size * 2.0));
342 amount = 1.0 + (amount - 1.0) * adjust_amount;
343
344 try {
345 // This encloses all the allocations of buffers, including those in
346 // the constructor of the PaulStretch object
347
348 PaulStretch stretch(amount, stretch_buf_size, rate);
349
350 auto nget = stretch.get_nsamples_for_fill();
351
352 auto bufsize = stretch.poolsize;
353 Floats buffer0{ bufsize };
354 float *bufferptr0 = buffer0.get();
355 bool first_time = true;
356
357 const auto fade_len = std::min<size_t>(100, bufsize / 2 - 1);
358 bool cancelled = false;
359
360 {
361 Floats fade_track_smps{ fade_len };
362 decltype(len) s=0;
363
364 while (s < len) {
365 track.GetFloats(bufferptr0, start + s, nget);
366 stretch.process(buffer0.get(), nget);
367
368 if (first_time) {
369 stretch.process(buffer0.get(), 0);
370 };
371
372 s += nget;
373
374 if (first_time){//blend the start of the selection
375 track.GetFloats(fade_track_smps.get(), start, fade_len);
376 first_time = false;
377 for (size_t i = 0; i < fade_len; i++){
378 float fi = (float)i / (float)fade_len;
379 stretch.out_buf[i] =
380 stretch.out_buf[i] * fi + (1.0 - fi) * fade_track_smps[i];
381 }
382 }
383 if (s >= len){//blend the end of the selection
384 track.GetFloats(fade_track_smps.get(), end - fade_len, fade_len);
385 for (size_t i = 0; i < fade_len; i++){
386 float fi = (float)i / (float)fade_len;
387 auto i2 = bufsize / 2 - 1 - i;
388 stretch.out_buf[i2] =
389 stretch.out_buf[i2] * fi + (1.0 - fi) *
390 fade_track_smps[fade_len - 1 - i];
391 }
392 }
393
394 outputTrack.Append((samplePtr)stretch.out_buf.get(), floatSample, stretch.out_bufsize);
395
396 nget = stretch.get_nsamples();
397 if (TrackProgress(count,
398 s.as_double() / len.as_double()
399 )) {
400 cancelled = true;
401 break;
402 }
403 }
404 }
405
406 if (!cancelled)
407 return true;
408 }
409 catch ( const std::bad_alloc& ) {
410 EffectUIServices::DoMessageBox(*this, badAllocMessage);
411 }
412 return false;
413};
414
415/*************************************************************/
416
417
418PaulStretch::PaulStretch(float rap_, size_t in_bufsize_, float samplerate_ )
419 : samplerate { samplerate_ }
420 , rap { std::max(1.0f, rap_) }
421 , in_bufsize { in_bufsize_ }
422 , out_bufsize { std::max(size_t{ 8 }, in_bufsize) }
423 , out_buf { out_bufsize }
424 , old_out_smp_buf { out_bufsize * 2, true }
425 , poolsize { in_bufsize_ * 2 }
426 , in_pool { poolsize, true }
427 , remained_samples { 0.0 }
428 , fft_smps { poolsize, true }
429 , fft_c { poolsize, true }
430 , fft_s { poolsize, true }
431 , fft_freq { poolsize, true }
432 , fft_tmp { poolsize }
433{
434}
435
437{
438}
439
440void PaulStretch::process(float *smps, size_t nsmps)
441{
442 //add NEW samples to the pool
443 if ((smps != NULL) && (nsmps != 0)) {
444 if (nsmps > poolsize) {
445 nsmps = poolsize;
446 }
447 int nleft = poolsize - nsmps;
448
449 //move left the samples from the pool to make room for NEW samples
450 for (int i = 0; i < nleft; i++)
451 in_pool[i] = in_pool[i + nsmps];
452
453 //add NEW samples to the pool
454 for (size_t i = 0; i < nsmps; i++)
455 in_pool[i + nleft] = smps[i];
456 }
457
458 //get the samples from the pool
459 for (size_t i = 0; i < poolsize; i++)
460 fft_smps[i] = in_pool[i];
462
463 RealFFT(poolsize, fft_smps.get(), fft_c.get(), fft_s.get());
464
465 for (size_t i = 0; i < poolsize / 2; i++)
466 fft_freq[i] = sqrt(fft_c[i] * fft_c[i] + fft_s[i] * fft_s[i]);
468
469
470 //put randomize phases to frequencies and do a IFFT
471 float inv_2p15_2pi = 1.0 / 16384.0 * (float)M_PI;
472 for (size_t i = 1; i < poolsize / 2; i++) {
473 unsigned int random = (rand()) & 0x7fff;
474 float phase = random * inv_2p15_2pi;
475 float s = fft_freq[i] * sin(phase);
476 float c = fft_freq[i] * cos(phase);
477
478 fft_c[i] = fft_c[poolsize - i] = c;
479
480 fft_s[i] = s; fft_s[poolsize - i] = -s;
481 }
482 fft_c[0] = fft_s[0] = 0.0;
483 fft_c[poolsize / 2] = fft_s[poolsize / 2] = 0.0;
484
485 FFT(poolsize, true, fft_c.get(), fft_s.get(), fft_smps.get(), fft_tmp.get());
486
487 float max = 0.0, max2 = 0.0;
488 for (size_t i = 0; i < poolsize; i++) {
489 max = std::max(max, fabsf(fft_tmp[i]));
490 max2 = std::max(max2, fabsf(fft_smps[i]));
491 }
492
493
494 //make the output buffer
495 float tmp = 1.0 / (float) out_bufsize * M_PI;
496 float hinv_sqrt2 = 0.853553390593f;//(1.0+1.0/sqrt(2))*0.5;
497
498 float ampfactor = 1.0;
499 if (rap < 1.0)
500 ampfactor = rap * 0.707;
501 else
502 ampfactor = (out_bufsize / (float)poolsize) * 4.0;
503
504 for (size_t i = 0; i < out_bufsize; i++) {
505 float a = (0.5 + 0.5 * cos(i * tmp));
506 float out = fft_smps[i + out_bufsize] * (1.0 - a) + old_out_smp_buf[i] * a;
507 out_buf[i] =
508 out * (hinv_sqrt2 - (1.0 - hinv_sqrt2) * cos(i * 2.0 * tmp)) *
509 ampfactor;
510 }
511
512 //copy the current output buffer to old buffer
513 for (size_t i = 0; i < out_bufsize * 2; i++)
515}
516
518{
519 double r = out_bufsize / rap;
520 auto ri = (size_t)floor(r);
521 double rf = r - floor(r);
522
523 remained_samples += rf;
524 if (remained_samples >= 1.0){
525 ri += (size_t)floor(remained_samples);
527 }
528
529 if (ri > poolsize) {
530 ri = poolsize;
531 }
532
533 return ri;
534}
535
537{
538 return poolsize;
539}
wxT("CloseDown"))
END_EVENT_TABLE()
#define M_PI
Definition: Distortion.cpp:30
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")
XXO("&Cut/Copy/Paste Toolbar")
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
char * samplePtr
Definition: SampleFormat.h:55
#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:116
bool IsPreviewing() const
Definition: EffectBase.h:89
double mProjectRate
Definition: EffectBase.h:112
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:109
double mT0
Definition: EffectBase.h:115
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:349
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
Hold values to send to effect output meters.
Interface for manipulations of an Effect's settings.
An Extreme Time Stretch and Time Smear effect.
Definition: Paulstretch.h:22
bool TransferDataToWindow(const EffectSettings &settings) override
static constexpr EffectParameter Time
Definition: Paulstretch.h:71
float mTime_resolution
Definition: Paulstretch.h:64
const EffectParameterMethods & Parameters() const override
Definition: Paulstretch.cpp:36
virtual ~EffectPaulstretch()
bool ProcessOne(const WaveChannel &track, WaveChannel &outputTrack, double t0, double t1, int count)
bool TransferDataFromWindow(EffectSettings &settings) override
static const ComponentInterfaceSymbol Symbol
Definition: Paulstretch.h:26
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
EffectType GetType() const override
Type determines how it behaves.
wxWeakRef< wxWindow > mUIParent
Definition: Paulstretch.h:61
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
void OnText(wxCommandEvent &evt)
bool Process(EffectInstance &instance, EffectSettings &settings) override
size_t GetBufferSize(double rate) const
ComponentInterfaceSymbol GetSymbol() const override
static constexpr EffectParameter Amount
Definition: Paulstretch.h:69
static int DoMessageBox(const EffectPlugin &plugin, const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={})
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
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
static bool IsSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:82
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
Holds a msgid for the translation catalog; may also bind format arguments.
WaveTrack & GetTrack()
Definition: WaveTrack.h:1117
bool Append(constSamplePtr buffer, sampleFormat format, size_t len)
Definition: WaveTrack.cpp:2347
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:152
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
double GetRate() const override
Definition: WaveTrack.cpp:868
sampleCount TimeToLongSamples(double t0) const
virtual bool Read(const wxString &key, bool *value) const =0
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
BuiltinEffectsModule::Registration< EffectPaulstretch > reg
Definition: Paulstretch.cpp:90
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.
const Type min
Minimum value.
Externalized state of a plug-in.