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#include "TimeWarper.h"
34
35#include "WaveTrack.h"
36
38{
41 > parameters;
42 return parameters;
43}
44
48{
49public:
50 PaulStretch(float rap_, size_t in_bufsize_, float samplerate_);
51 //in_bufsize is also a half of a FFT buffer (in samples)
52 virtual ~PaulStretch();
53
54 void process(float *smps, size_t nsmps);
55
56 size_t get_nsamples();//how many samples are required to be added in the pool next time
57 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)
58
59private:
60 void process_spectrum(float *WXUNUSED(freq)) {};
61
62 const float samplerate;
63 const float rap;
64 const size_t in_bufsize;
65
66public:
67 const size_t out_bufsize;
69
70private:
72
73public:
74 const size_t poolsize;//how many samples are inside the input_pool size (need to know how many samples to fill when seeking)
75
76private:
77 const Floats in_pool;//de marimea in_bufsize
78
79 double remained_samples;//how many fraction of samples has remained (0..1)
80
82};
83
84//
85// EffectPaulstretch
86//
87
89{ XO("Paulstretch") };
90
92
93BEGIN_EVENT_TABLE(EffectPaulstretch, wxEvtHandler)
94 EVT_TEXT(wxID_ANY, EffectPaulstretch::OnText)
96
98{
99 Parameters().Reset(*this);
100
101 SetLinearEffectFlag(true);
102}
103
105{
106}
107
108// ComponentInterface implementation
109
111{
112 return Symbol;
113}
114
116{
117 return XO("Paulstretch is only for an extreme time-stretch or \"stasis\" effect");
118}
119
121{
122 return L"Paulstretch";
123}
124
125// EffectDefinitionInterface implementation
126
128{
129 return EffectTypeProcess;
130}
131
132// Effect implementation
133
135 const EffectSettings &, double previewLength) const
136{
137 // FIXME: Preview is currently at the project rate, but should really be
138 // at the track rate (bugs 1284 and 852).
139 auto minDuration = GetBufferSize(mProjectRate) * 2 + 1;
140
141 // Preview playback may need to be trimmed but this is the smallest selection that we can use.
142 double minLength = std::max<double>(minDuration / mProjectRate, previewLength / mAmount);
143
144 return minLength;
145}
146
147
149{
150 // Pass true because sync lock adjustment is needed
151 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } }, true };
152 auto newT1 = mT1;
153 int count = 0;
154 // Process selected wave tracks first, to find the new t1 value
155 for (const auto track : outputs.Get().Selected<WaveTrack>()) {
156 double trackStart = track->GetStartTime();
157 double trackEnd = track->GetEndTime();
158 double t0 = mT0 < trackStart ? trackStart : mT0;
159 double t1 = mT1 > trackEnd ? trackEnd : mT1;
160 if (t1 > t0) {
161 auto tempTrack = track->EmptyCopy();
162 const auto channels = track->Channels();
163 auto iter = tempTrack->Channels().begin();
164 for (const auto pChannel : channels) {
165 if (!ProcessOne(*pChannel, **iter++, t0, t1, count++))
166 return false;
167 }
168 tempTrack->Flush();
169 newT1 = std::max(newT1, mT0 + tempTrack->GetEndTime());
170 PasteTimeWarper warper { t1, t0 + tempTrack->GetEndTime() };
171 constexpr auto preserve = false;
172 constexpr auto merge = true;
173 track->ClearAndPaste(t0, t1, *tempTrack, preserve, merge, &warper);
174 }
175 else
176 count += track->NChannels();
177 }
178
179 // Sync lock adjustment of other tracks
180 outputs.Get().Any().Visit(
181 [&](auto &&fallthrough){ return [&](WaveTrack &track) {
182 if (!track.IsSelected())
183 fallthrough();
184 }; },
185 [&](Track &track) {
187 track.SyncLockAdjust(mT1, newT1);
188 }
189 );
190 mT1 = newT1;
191
192 outputs.Commit();
193
194 return true;
195}
196
197
198std::unique_ptr<EffectEditor> EffectPaulstretch::PopulateOrExchange(
200 const EffectOutputs *)
201{
202 mUIParent = S.GetParent();
203 S.StartMultiColumn(2, wxALIGN_CENTER);
204 {
205 S
206 .Validator<FloatingPointValidator<float>>(
207 1, &mAmount, NumValidatorStyle::DEFAULT, Amount.min)
208 /* i18n-hint: This is how many times longer the sound will be, e.g. applying
209 * the effect to a 1-second sample, with the default Stretch Factor of 10.0
210 * will give an (approximately) 10 second sound
211 */
212 .AddTextBox(XXO("&Stretch Factor:"), wxT(""), 10);
213
214 S
215 .Validator<FloatingPointValidator<float>>(
216 3, &mTime_resolution, NumValidatorStyle::ONE_TRAILING_ZERO, Time.min)
217 .AddTextBox(XXO("&Time Resolution (seconds):"), L"", 10);
218 }
219 S.EndMultiColumn();
220 return nullptr;
221};
222
224{
225 if (!mUIParent->TransferDataToWindow())
226 {
227 return false;
228 }
229
230 return true;
231}
232
234{
235 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
236 {
237 return false;
238 }
239
240 return true;
241}
242
243// EffectPaulstretch implementation
244
245void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt))
246{
248 mUIParent, mUIParent->TransferDataFromWindow());
249}
250
251size_t EffectPaulstretch::GetBufferSize(double rate) const
252{
253 // Audacity's fft requires a power of 2
254 float tmp = rate * mTime_resolution / 2.0;
255 tmp = log(tmp) / log(2.0);
256 tmp = pow(2.0, floor(tmp + 0.5));
257
258 auto stmp = size_t(tmp);
259 if (stmp != tmp)
260 // overflow
261 return 0;
262 if (stmp >= 2 * stmp)
263 // overflow
264 return 0;
265
266 return std::max<size_t>(stmp, 128);
267}
268
270 WaveChannel &outputTrack, double t0, double t1, int count)
271{
272 const auto badAllocMessage =
273 XO("Requested value exceeds memory capacity.");
274
275 const auto rate = track.GetTrack().GetRate();
276 const auto stretch_buf_size = GetBufferSize(rate);
277 if (stretch_buf_size == 0) {
278 EffectUIServices::DoMessageBox(*this, badAllocMessage);
279 return {};
280 }
281
282 double amount = this->mAmount;
283
284 auto start = track.TimeToLongSamples(t0);
285 auto end = track.TimeToLongSamples(t1);
286 auto len = end - start;
287
288 const auto minDuration = stretch_buf_size * 2 + 1;
289 if (minDuration < stretch_buf_size) {
290 // overflow!
291 EffectUIServices::DoMessageBox(*this, badAllocMessage);
292 return {};
293 }
294
295 if (len < minDuration) { //error because the selection is too short
296
297 float maxTimeRes = log( len.as_double() ) / log(2.0);
298 maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
299 maxTimeRes = maxTimeRes / rate;
300
301 if (this->IsPreviewing()) {
302 double defaultPreviewLen;
303 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
304
305 if ((minDuration / mProjectRate) < defaultPreviewLen) {
307 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
308 XO("Audio selection too short to preview.\n\n"
309 "Try increasing the audio selection to at least %.1f seconds,\n"
310 "or reducing the 'Time Resolution' to less than %.1f seconds.")
311 .Format(
312 (minDuration / rate) + 0.05, // round up to 1/10 s.
313 floor(maxTimeRes * 10.0) / 10.0),
314 wxOK | wxICON_EXCLAMATION );
315 }
316 else {
318 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
319 XO("Unable to Preview.\n\n"
320 "For the current audio selection, the maximum\n"
321 "'Time Resolution' is %.1f seconds.")
322 .Format( floor(maxTimeRes * 10.0) / 10.0 ),
323 wxOK | wxICON_EXCLAMATION );
324 }
325 }
326 else {
328 /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
329 XO("The 'Time Resolution' is too long for the selection.\n\n"
330 "Try increasing the audio selection to at least %.1f seconds,\n"
331 "or reducing the 'Time Resolution' to less than %.1f seconds.")
332 .Format(
333 (minDuration / rate) + 0.05, // round up to 1/10 s.
334 floor(maxTimeRes * 10.0) / 10.0),
335 wxOK | wxICON_EXCLAMATION );
336 }
337
338 return {};
339 }
340
341 auto dlen = len.as_double();
342 double adjust_amount = dlen /
343 (dlen - ((double)stretch_buf_size * 2.0));
344 amount = 1.0 + (amount - 1.0) * adjust_amount;
345
346 try {
347 // This encloses all the allocations of buffers, including those in
348 // the constructor of the PaulStretch object
349
350 PaulStretch stretch(amount, stretch_buf_size, rate);
351
352 auto nget = stretch.get_nsamples_for_fill();
353
354 auto bufsize = stretch.poolsize;
355 Floats buffer0{ bufsize };
356 float *bufferptr0 = buffer0.get();
357 bool first_time = true;
358
359 const auto fade_len = std::min<size_t>(100, bufsize / 2 - 1);
360 bool cancelled = false;
361
362 {
363 Floats fade_track_smps{ fade_len };
364 decltype(len) s=0;
365
366 while (s < len) {
367 track.GetFloats(bufferptr0, start + s, nget);
368 stretch.process(buffer0.get(), nget);
369
370 if (first_time) {
371 stretch.process(buffer0.get(), 0);
372 };
373
374 s += nget;
375
376 if (first_time){//blend the start of the selection
377 track.GetFloats(fade_track_smps.get(), start, fade_len);
378 first_time = false;
379 for (size_t i = 0; i < fade_len; i++){
380 float fi = (float)i / (float)fade_len;
381 stretch.out_buf[i] =
382 stretch.out_buf[i] * fi + (1.0 - fi) * fade_track_smps[i];
383 }
384 }
385 if (s >= len){//blend the end of the selection
386 track.GetFloats(fade_track_smps.get(), end - fade_len, fade_len);
387 for (size_t i = 0; i < fade_len; i++){
388 float fi = (float)i / (float)fade_len;
389 auto i2 = bufsize / 2 - 1 - i;
390 stretch.out_buf[i2] =
391 stretch.out_buf[i2] * fi + (1.0 - fi) *
392 fade_track_smps[fade_len - 1 - i];
393 }
394 }
395
396 outputTrack.Append((samplePtr)stretch.out_buf.get(), floatSample, stretch.out_bufsize);
397
398 nget = stretch.get_nsamples();
399 if (TrackProgress(count,
400 s.as_double() / len.as_double()
401 )) {
402 cancelled = true;
403 break;
404 }
405 }
406 }
407
408 if (!cancelled)
409 return true;
410 }
411 catch ( const std::bad_alloc& ) {
412 EffectUIServices::DoMessageBox(*this, badAllocMessage);
413 }
414 return false;
415};
416
417/*************************************************************/
418
419
420PaulStretch::PaulStretch(float rap_, size_t in_bufsize_, float samplerate_ )
421 : samplerate { samplerate_ }
422 , rap { std::max(1.0f, rap_) }
423 , in_bufsize { in_bufsize_ }
424 , out_bufsize { std::max(size_t{ 8 }, in_bufsize) }
425 , out_buf { out_bufsize }
426 , old_out_smp_buf { out_bufsize * 2, true }
427 , poolsize { in_bufsize_ * 2 }
428 , in_pool { poolsize, true }
429 , remained_samples { 0.0 }
430 , fft_smps { poolsize, true }
431 , fft_c { poolsize, true }
432 , fft_s { poolsize, true }
433 , fft_freq { poolsize, true }
434 , fft_tmp { poolsize }
435{
436}
437
439{
440}
441
442void PaulStretch::process(float *smps, size_t nsmps)
443{
444 //add NEW samples to the pool
445 if ((smps != NULL) && (nsmps != 0)) {
446 if (nsmps > poolsize) {
447 nsmps = poolsize;
448 }
449 int nleft = poolsize - nsmps;
450
451 //move left the samples from the pool to make room for NEW samples
452 for (int i = 0; i < nleft; i++)
453 in_pool[i] = in_pool[i + nsmps];
454
455 //add NEW samples to the pool
456 for (size_t i = 0; i < nsmps; i++)
457 in_pool[i + nleft] = smps[i];
458 }
459
460 //get the samples from the pool
461 for (size_t i = 0; i < poolsize; i++)
462 fft_smps[i] = in_pool[i];
464
465 RealFFT(poolsize, fft_smps.get(), fft_c.get(), fft_s.get());
466
467 for (size_t i = 0; i < poolsize / 2; i++)
468 fft_freq[i] = sqrt(fft_c[i] * fft_c[i] + fft_s[i] * fft_s[i]);
470
471
472 //put randomize phases to frequencies and do a IFFT
473 float inv_2p15_2pi = 1.0 / 16384.0 * (float)M_PI;
474 for (size_t i = 1; i < poolsize / 2; i++) {
475 unsigned int random = (rand()) & 0x7fff;
476 float phase = random * inv_2p15_2pi;
477 float s = fft_freq[i] * sin(phase);
478 float c = fft_freq[i] * cos(phase);
479
480 fft_c[i] = fft_c[poolsize - i] = c;
481
482 fft_s[i] = s; fft_s[poolsize - i] = -s;
483 }
484 fft_c[0] = fft_s[0] = 0.0;
485 fft_c[poolsize / 2] = fft_s[poolsize / 2] = 0.0;
486
487 FFT(poolsize, true, fft_c.get(), fft_s.get(), fft_smps.get(), fft_tmp.get());
488
489 float max = 0.0, max2 = 0.0;
490 for (size_t i = 0; i < poolsize; i++) {
491 max = std::max(max, fabsf(fft_tmp[i]));
492 max2 = std::max(max2, fabsf(fft_smps[i]));
493 }
494
495
496 //make the output buffer
497 float tmp = 1.0 / (float) out_bufsize * M_PI;
498 float hinv_sqrt2 = 0.853553390593f;//(1.0+1.0/sqrt(2))*0.5;
499
500 float ampfactor = 1.0;
501 if (rap < 1.0)
502 ampfactor = rap * 0.707;
503 else
504 ampfactor = (out_bufsize / (float)poolsize) * 4.0;
505
506 for (size_t i = 0; i < out_bufsize; i++) {
507 float a = (0.5 + 0.5 * cos(i * tmp));
508 float out = fft_smps[i + out_bufsize] * (1.0 - a) + old_out_smp_buf[i] * a;
509 out_buf[i] =
510 out * (hinv_sqrt2 - (1.0 - hinv_sqrt2) * cos(i * 2.0 * tmp)) *
511 ampfactor;
512 }
513
514 //copy the current output buffer to old buffer
515 for (size_t i = 0; i < out_bufsize * 2; i++)
517}
518
520{
521 double r = out_bufsize / rap;
522 auto ri = (size_t)floor(r);
523 double rf = r - floor(r);
524
525 remained_samples += rf;
526 if (remained_samples >= 1.0){
527 ri += (size_t)floor(remained_samples);
529 }
530
531 if (ri > poolsize) {
532 ri = poolsize;
533 }
534
535 return ri;
536}
537
539{
540 return poolsize;
541}
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:57
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
#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:114
bool IsPreviewing() const
Definition: EffectBase.h:89
double mProjectRate
Definition: EffectBase.h:110
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:107
double mT0
Definition: EffectBase.h:113
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:343
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:37
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.
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.
Definition: Paulstretch.cpp:48
const float rap
Definition: Paulstretch.cpp:63
void process(float *smps, size_t nsmps)
const Floats in_pool
Definition: Paulstretch.cpp:77
virtual ~PaulStretch()
double remained_samples
Definition: Paulstretch.cpp:79
const size_t out_bufsize
Definition: Paulstretch.cpp:67
const Floats old_out_smp_buf
Definition: Paulstretch.cpp:71
const Floats fft_tmp
Definition: Paulstretch.cpp:81
size_t get_nsamples_for_fill()
const float samplerate
Definition: Paulstretch.cpp:60
const Floats fft_freq
Definition: Paulstretch.cpp:81
PaulStretch(float rap_, size_t in_bufsize_, float samplerate_)
const Floats fft_smps
Definition: Paulstretch.cpp:81
const size_t in_bufsize
Definition: Paulstretch.cpp:64
const size_t poolsize
Definition: Paulstretch.cpp:74
const Floats out_buf
Definition: Paulstretch.cpp:68
size_t get_nsamples()
void process_spectrum(float *WXUNUSED(freq))
Definition: Paulstretch.cpp:60
const Floats fft_s
Definition: Paulstretch.cpp:81
const Floats fft_c
Definition: Paulstretch.cpp:81
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
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: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
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
double GetRate() const override
Definition: WaveTrack.cpp:798
sampleCount TimeToLongSamples(double t0) const
virtual bool Read(const wxString &key, bool *value) const =0
BuiltinEffectsModule::Registration< EffectPaulstretch > reg
Definition: Paulstretch.cpp:91
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.
const Type min
Minimum value.
Externalized state of a plug-in.