Audacity  2.2.2
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 #include "../Audacity.h"
18 #include "Paulstretch.h"
19 
20 #include <algorithm>
21 
22 #include <math.h>
23 #include <float.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/ErrorDialog.h"
32 #include "../Prefs.h"
33 
34 #include "../WaveTrack.h"
35 
36 // Define keys, defaults, minimums, and maximums for the effect parameters
37 //
38 // Name Type Key Def Min Max Scale
39 Param( Amount, float, wxT("Stretch Factor"), 10.0, 1.0, FLT_MAX, 1 );
40 Param( Time, float, wxT("Time Resolution"), 0.25f, 0.00099f, FLT_MAX, 1 );
41 
43 {
44 public:
45  PaulStretch(float rap_, size_t in_bufsize_, float samplerate_);
46  //in_bufsize is also a half of a FFT buffer (in samples)
47  virtual ~PaulStretch();
48 
49  void process(float *smps, size_t nsmps);
50 
51  size_t get_nsamples();//how many samples are required to be added in the pool next time
52  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)
53 
54 private:
55  void process_spectrum(float *WXUNUSED(freq)) {};
56 
57  const float samplerate;
58  const float rap;
59  const size_t in_bufsize;
60 
61 public:
62  const size_t out_bufsize;
63  const Floats out_buf;
64 
65 private:
67 
68 public:
69  const size_t poolsize;//how many samples are inside the input_pool size (need to know how many samples to fill when seeking)
70 
71 private:
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 
79 //
80 // EffectPaulstretch
81 //
82 
83 BEGIN_EVENT_TABLE(EffectPaulstretch, wxEvtHandler)
84  EVT_TEXT(wxID_ANY, EffectPaulstretch::OnText)
86 
88 {
89  mAmount = DEF_Amount;
90  mTime_resolution = DEF_Time;
91 
92  SetLinearEffectFlag(true);
93 }
94 
96 {
97 }
98 
99 // IdentInterface implementation
100 
102 {
104 }
105 
107 {
108  return _("Paulstretch is only for an extreme time-stretch or \"stasis\" effect");
109 }
110 
112 {
113  return wxT("Paulstretch");
114 }
115 
116 // EffectDefinitionInterface implementation
117 
119 {
120  return EffectTypeProcess;
121 }
122 
123 // EffectClientInterface implementation
125  S.SHUTTLE_PARAM( mAmount, Amount );
126  S.SHUTTLE_PARAM( mTime_resolution, Time );
127  return true;
128 }
129 
131 {
132  parms.WriteFloat(KEY_Amount, mAmount);
133  parms.WriteFloat(KEY_Time, mTime_resolution);
134 
135  return true;
136 }
137 
139 {
140  ReadAndVerifyFloat(Amount);
141  ReadAndVerifyFloat(Time);
142 
143  mAmount = Amount;
144  mTime_resolution = Time;
145 
146  return true;
147 }
148 
149 // Effect implementation
150 
151 double EffectPaulstretch::CalcPreviewInputLength(double previewLength)
152 {
153  // FIXME: Preview is currently at the project rate, but should really be
154  // at the track rate (bugs 1284 and 852).
155  auto minDuration = GetBufferSize(mProjectRate) * 2 + 1;
156 
157  // Preview playback may need to be trimmed but this is the smallest selection that we can use.
158  double minLength = std::max<double>(minDuration / mProjectRate, previewLength / mAmount);
159 
160  return minLength;
161 }
162 
163 
165 {
166  CopyInputTracks();
168  WaveTrack *track = (WaveTrack *) iter.First();
169  m_t1=mT1;
170  int count=0;
171  while (track) {
172  double trackStart = track->GetStartTime();
173  double trackEnd = track->GetEndTime();
174  double t0 = mT0 < trackStart? trackStart: mT0;
175  double t1 = mT1 > trackEnd? trackEnd: mT1;
176 
177  if (t1 > t0) {
178  if (!ProcessOne(track, t0,t1,count))
179  return false;
180  }
181 
182  track = (WaveTrack *) iter.Next();
183  count++;
184  }
185  mT1=m_t1;
186 
188 
189  return true;
190 }
191 
192 
194 {
195  S.StartMultiColumn(2, wxALIGN_CENTER);
196  {
197  FloatingPointValidator<float> vldAmount(1, &mAmount);
198  vldAmount.SetMin(MIN_Amount);
199 
200  /* i18n-hint: This is how many times longer the sound will be, e.g. applying
201  * the effect to a 1-second sample, with the default Stretch Factor of 10.0
202  * will give an (approximately) 10 second sound
203  */
204  S.AddTextBox(_("Stretch Factor:"), wxT(""), 10)->SetValidator(vldAmount);
205 
206  FloatingPointValidator<float> vldTime(3, &mTime_resolution, NumValidatorStyle::ONE_TRAILING_ZERO);
207  vldTime.SetMin(MIN_Time);
208  S.AddTextBox(_("Time Resolution (seconds):"), wxT(""), 10)->SetValidator(vldTime);
209  }
210  S.EndMultiColumn();
211 };
212 
214 {
215  if (!mUIParent->TransferDataToWindow())
216  {
217  return false;
218  }
219 
220  return true;
221 }
222 
224 {
225  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
226  {
227  return false;
228  }
229 
230  return true;
231 }
232 
233 // EffectPaulstretch implementation
234 
235 void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt))
236 {
237  EnableApply(mUIParent->TransferDataFromWindow());
238 }
239 
241 {
242  // Audacity's fft requires a power of 2
243  float tmp = rate * mTime_resolution / 2.0;
244  tmp = log(tmp) / log(2.0);
245  tmp = pow(2.0, floor(tmp + 0.5));
246 
247  auto stmp = size_t(tmp);
248  if (stmp != tmp)
249  // overflow
250  return 0;
251  if (stmp >= 2 * stmp)
252  // overflow
253  return 0;
254 
255  return std::max<size_t>(stmp, 128);
256 }
257 
258 bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count)
259 {
260  auto badAllocMessage = _("Requested value exceeds memory capacity.");
261 
262  const auto stretch_buf_size = GetBufferSize(track->GetRate());
263  if (stretch_buf_size == 0) {
264  ::Effect::MessageBox( badAllocMessage );
265  return false;
266  }
267 
268  double amount = this->mAmount;
269 
270  auto start = track->TimeToLongSamples(t0);
271  auto end = track->TimeToLongSamples(t1);
272  auto len = end - start;
273 
274  const auto minDuration = stretch_buf_size * 2 + 1;
275  if (minDuration < stretch_buf_size) {
276  // overflow!
277  ::Effect::MessageBox( badAllocMessage );
278  return false;
279  }
280 
281  if (len < minDuration) { //error because the selection is too short
282 
283  float maxTimeRes = log( len.as_double() ) / log(2.0);
284  maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
285  maxTimeRes = maxTimeRes / track->GetRate();
286 
287  if (this->IsPreviewing()) {
288  double defaultPreviewLen;
289  gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
290 
291  /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
292  if ((minDuration / mProjectRate) < defaultPreviewLen) {
293  ::Effect::MessageBox (wxString::Format(_("Audio selection too short to preview.\n\n"
294  "Try increasing the audio selection to at least %.1f seconds,\n"
295  "or reducing the 'Time Resolution' to less than %.1f seconds."),
296  (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
297  floor(maxTimeRes * 10.0) / 10.0),
298  wxOK | wxICON_EXCLAMATION);
299  }
300  else {
301  /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
302  ::Effect::MessageBox (wxString::Format(_("Unable to Preview.\n\n"
303  "For the current audio selection, the maximum\n"
304  "'Time Resolution' is %.1f seconds."),
305  floor(maxTimeRes * 10.0) / 10.0),
306  wxOK | wxICON_EXCLAMATION);
307  }
308  }
309  else {
310  /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
311  ::Effect::MessageBox (wxString::Format(_("The 'Time Resolution' is too long for the selection.\n\n"
312  "Try increasing the audio selection to at least %.1f seconds,\n"
313  "or reducing the 'Time Resolution' to less than %.1f seconds."),
314  (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
315  floor(maxTimeRes * 10.0) / 10.0),
316  wxOK | wxICON_EXCLAMATION);
317  }
318 
319  return false;
320  }
321 
322 
323  auto dlen = len.as_double();
324  double adjust_amount = dlen /
325  (dlen - ((double)stretch_buf_size * 2.0));
326  amount = 1.0 + (amount - 1.0) * adjust_amount;
327 
328  auto outputTrack = mFactory->NewWaveTrack(track->GetSampleFormat(),track->GetRate());
329 
330  try {
331  // This encloses all the allocations of buffers, including those in
332  // the constructor of the PaulStretch object
333 
334  PaulStretch stretch(amount, stretch_buf_size, track->GetRate());
335 
336  auto nget = stretch.get_nsamples_for_fill();
337 
338  auto bufsize = stretch.poolsize;
339  Floats buffer0{ bufsize };
340  float *bufferptr0 = buffer0.get();
341  bool first_time = true;
342 
343  const auto fade_len = std::min<size_t>(100, bufsize / 2 - 1);
344  bool cancelled = false;
345 
346  {
347  Floats fade_track_smps{ fade_len };
348  decltype(len) s=0;
349 
350  while (s < len) {
351  track->Get((samplePtr)bufferptr0, floatSample, start + s, nget);
352  stretch.process(buffer0.get(), nget);
353 
354  if (first_time) {
355  stretch.process(buffer0.get(), 0);
356  };
357 
358  s += nget;
359 
360  if (first_time){//blend the the start of the selection
361  track->Get((samplePtr)fade_track_smps.get(), floatSample, start, fade_len);
362  first_time = false;
363  for (size_t i = 0; i < fade_len; i++){
364  float fi = (float)i / (float)fade_len;
365  stretch.out_buf[i] =
366  stretch.out_buf[i] * fi + (1.0 - fi) * fade_track_smps[i];
367  }
368  }
369  if (s >= len){//blend the end of the selection
370  track->Get((samplePtr)fade_track_smps.get(), floatSample, end - fade_len, fade_len);
371  for (size_t i = 0; i < fade_len; i++){
372  float fi = (float)i / (float)fade_len;
373  auto i2 = bufsize / 2 - 1 - i;
374  stretch.out_buf[i2] =
375  stretch.out_buf[i2] * fi + (1.0 - fi) *
376  fade_track_smps[fade_len - 1 - i];
377  }
378  }
379 
380  outputTrack->Append((samplePtr)stretch.out_buf.get(), floatSample, stretch.out_bufsize);
381 
382  nget = stretch.get_nsamples();
383  if (TrackProgress(count,
384  s.as_double() / len.as_double()
385  )) {
386  cancelled = true;
387  break;
388  }
389  }
390  }
391 
392  if (!cancelled){
393  outputTrack->Flush();
394 
395  track->Clear(t0,t1);
396  track->Paste(t0, outputTrack.get());
397  m_t1 = mT0 + outputTrack->GetEndTime();
398  }
399 
400  return !cancelled;
401  }
402  catch ( const std::bad_alloc& ) {
403  ::Effect::MessageBox( badAllocMessage );
404  return false;
405  }
406 };
407 
408 /*************************************************************/
409 
410 
411 PaulStretch::PaulStretch(float rap_, size_t in_bufsize_, float samplerate_ )
412  : samplerate { samplerate_ }
413  , rap { std::max(1.0f, rap_) }
414  , in_bufsize { in_bufsize_ }
415  , out_bufsize { std::max(size_t{ 8 }, in_bufsize) }
416  , out_buf { out_bufsize }
417  , old_out_smp_buf { out_bufsize * 2, true }
418  , poolsize { in_bufsize_ * 2 }
419  , in_pool { poolsize, true }
420  , remained_samples { 0.0 }
421  , fft_smps { poolsize, true }
422  , fft_c { poolsize, true }
423  , fft_s { poolsize, true }
424  , fft_freq { poolsize, true }
425  , fft_tmp { poolsize }
426 {
427 }
428 
430 {
431 }
432 
433 void PaulStretch::process(float *smps, size_t nsmps)
434 {
435  //add NEW samples to the pool
436  if ((smps != NULL) && (nsmps != 0)) {
437  if (nsmps > poolsize) {
438  nsmps = poolsize;
439  }
440  int nleft = poolsize - nsmps;
441 
442  //move left the samples from the pool to make room for NEW samples
443  for (int i = 0; i < nleft; i++)
444  in_pool[i] = in_pool[i + nsmps];
445 
446  //add NEW samples to the pool
447  for (size_t i = 0; i < nsmps; i++)
448  in_pool[i + nleft] = smps[i];
449  }
450 
451  //get the samples from the pool
452  for (size_t i = 0; i < poolsize; i++)
453  fft_smps[i] = in_pool[i];
454  WindowFunc(eWinFuncHanning, poolsize, fft_smps.get());
455 
456  RealFFT(poolsize, fft_smps.get(), fft_c.get(), fft_s.get());
457 
458  for (size_t i = 0; i < poolsize / 2; i++)
459  fft_freq[i] = sqrt(fft_c[i] * fft_c[i] + fft_s[i] * fft_s[i]);
460  process_spectrum(fft_freq.get());
461 
462 
463  //put randomize phases to frequencies and do a IFFT
464  float inv_2p15_2pi = 1.0 / 16384.0 * (float)M_PI;
465  for (size_t i = 1; i < poolsize / 2; i++) {
466  unsigned int random = (rand()) & 0x7fff;
467  float phase = random * inv_2p15_2pi;
468  float s = fft_freq[i] * sin(phase);
469  float c = fft_freq[i] * cos(phase);
470 
471  fft_c[i] = fft_c[poolsize - i] = c;
472 
473  fft_s[i] = s; fft_s[poolsize - i] = -s;
474  }
475  fft_c[0] = fft_s[0] = 0.0;
476  fft_c[poolsize / 2] = fft_s[poolsize / 2] = 0.0;
477 
478  FFT(poolsize, true, fft_c.get(), fft_s.get(), fft_smps.get(), fft_tmp.get());
479 
480  float max = 0.0, max2 = 0.0;
481  for (size_t i = 0; i < poolsize; i++) {
482  max = std::max(max, fabsf(fft_tmp[i]));
483  max2 = std::max(max2, fabsf(fft_smps[i]));
484  }
485 
486 
487  //make the output buffer
488  float tmp = 1.0 / (float) out_bufsize * M_PI;
489  float hinv_sqrt2 = 0.853553390593f;//(1.0+1.0/sqrt(2))*0.5;
490 
491  float ampfactor = 1.0;
492  if (rap < 1.0)
493  ampfactor = rap * 0.707;
494  else
495  ampfactor = (out_bufsize / (float)poolsize) * 4.0;
496 
497  for (size_t i = 0; i < out_bufsize; i++) {
498  float a = (0.5 + 0.5 * cos(i * tmp));
499  float out = fft_smps[i + out_bufsize] * (1.0 - a) + old_out_smp_buf[i] * a;
500  out_buf[i] =
501  out * (hinv_sqrt2 - (1.0 - hinv_sqrt2) * cos(i * 2.0 * tmp)) *
502  ampfactor;
503  }
504 
505  //copy the current output buffer to old buffer
506  for (size_t i = 0; i < out_bufsize * 2; i++)
507  old_out_smp_buf[i] = fft_smps[i];
508 }
509 
511 {
512  double r = out_bufsize / rap;
513  auto ri = (size_t)floor(r);
514  double rf = r - floor(r);
515 
516  remained_samples += rf;
517  if (remained_samples >= 1.0){
518  ri += (size_t)floor(remained_samples);
520  }
521 
522  if (ri > poolsize) {
523  ri = poolsize;
524  }
525 
526  return ri;
527 }
528 
530 {
531  return poolsize;
532 }
double mT1
Definition: Effect.h:461
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
int MessageBox(const wxString &message, long style=DefaultMessageBoxStyle, const wxString &titleStr=wxString{})
Definition: Effect.cpp:2660
bool TrackProgress(int whichTrack, double frac, const wxString &=wxEmptyString)
Definition: Effect.cpp:1985
IdentInterfaceSymbol GetSymbol() override
bool TransferDataToWindow() override
const float rap
Definition: Paulstretch.cpp:58
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
const size_t poolsize
Definition: Paulstretch.cpp:69
bool GetAutomationParameters(CommandParameters &parms) override
bool IsPreviewing()
Definition: Effect.h:361
#define PAULSTRETCH_PLUGIN_SYMBOL
Definition: Paulstretch.h:19
void CopyInputTracks()
Definition: Effect.cpp:2036
#define ReadAndVerifyFloat(name)
Definition: Effect.h:799
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: Effect.cpp:2162
bool Process() override
Param(Amount, float, wxT("Stretch Factor"), 10.0, 1.0, FLT_MAX, 1)
void EndMultiColumn()
PaulStretch(float rap_, size_t in_bufsize_, float samplerate_)
double remained_samples
Definition: Paulstretch.cpp:74
void Clear(double t0, double t1) override
Definition: WaveTrack.cpp:678
Shuttle that deals with parameters. This is a base class with lots of virtual functions that do nothi...
Definition: Shuttle.h:60
EffectType GetType() override
bool WriteFloat(const wxString &key, float f)
bool ProcessOne(WaveTrack *track, double t0, double t1, int count)
wxString ManualPage() override
virtual ~EffectPaulstretch()
Definition: Paulstretch.cpp:95
const Floats out_buf
Definition: Paulstretch.cpp:63
const Floats fft_freq
Definition: Paulstretch.cpp:76
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:540
void FFT(size_t NumSamples, bool InverseTransform, const float *RealIn, const float *ImagIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:132
virtual ~PaulStretch()
An Extreme Time Stretch and Time Smear effect.
Definition: Paulstretch.h:21
void RealFFT(size_t NumSamples, const float *RealIn, float *RealOut, float *ImagOut)
Definition: FFT.cpp:231
void WindowFunc(int whichFunction, size_t NumSamples, float *in)
Definition: FFT.cpp:507
const Floats fft_smps
Definition: Paulstretch.cpp:76
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the Shuttle cla...
char * samplePtr
Definition: Types.h:203
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
void process(float *smps, size_t nsmps)
float mTime_resolution
Definition: Paulstretch.h:61
void Paste(double t0, const Track *src) override
Definition: WaveTrack.cpp:1192
wxString GetDescription() override
void process_spectrum(float *WXUNUSED(freq))
Definition: Paulstretch.cpp:55
const size_t in_bufsize
Definition: Paulstretch.cpp:59
IdentInterfaceSymbol pairs a persistent string identifier used internally with an optional...
const Floats fft_tmp
Definition: Paulstretch.cpp:76
double CalcPreviewInputLength(double previewLength) override
std::unique_ptr< WaveTrack > NewWaveTrack(sampleFormat format=(sampleFormat) 0, double rate=0)
Definition: WaveTrack.cpp:78
wxWindow * mUIParent
Definition: Effect.h:472
double mProjectRate
Definition: Effect.h:453
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
TrackFactory * mFactory
Definition: Effect.h:457
void PopulateOrExchange(ShuttleGui &S) override
EffectType
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: WaveTrack.cpp:1843
#define M_PI
Definition: Distortion.cpp:28
sampleFormat GetSampleFormat() const
Definition: WaveTrack.h:146
Memory.h template class for making an array of float, bool, etc.
Definition: MemoryX.h:86
const size_t out_bufsize
Definition: Paulstretch.cpp:62
size_t get_nsamples()
bool SetAutomationParameters(CommandParameters &parms) override
size_t GetBufferSize(double rate)
const Floats fft_c
Definition: Paulstretch.cpp:76
const Floats fft_s
Definition: Paulstretch.cpp:76
bool TransferDataFromWindow() override
END_EVENT_TABLE()
double GetRate() const
Definition: WaveTrack.cpp:398
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumCopied=nullptr) const
Definition: WaveTrack.cpp:1971
bool DefineParams(ShuttleParams &S) override
const Floats in_pool
Definition: Paulstretch.cpp:72
void OnText(wxCommandEvent &evt)
const Floats old_out_smp_buf
Definition: Paulstretch.cpp:66
std::shared_ptr< TrackList > mOutputTracks
Definition: Effect.h:459
const float samplerate
Definition: Paulstretch.cpp:55
virtual bool EnableApply(bool enable=true)
Definition: Effect.cpp:1886
double mT0
Definition: Effect.h:460
size_t get_nsamples_for_fill()