Audacity  2.2.2
ChangeTempo.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ChangeTempo.cpp
6 
7  Vaughan Johnson,
8  Dominic Mazzoni
9 
10 *******************************************************************//*******************************************************************/
17 
18 #include "../Audacity.h" // for USE_SOUNDTOUCH
19 
20 #if USE_SOUNDTOUCH
21 
22 #if USE_SBSMS
23 #include "../../../lib-src/header-substitutes/sbsms.h"
24 #include <wx/valgen.h>
25 #endif
26 
27 #include <math.h>
28 
29 #include <wx/intl.h>
30 
31 #include "../ShuttleGui.h"
32 #include "../widgets/valnum.h"
33 #include "TimeWarper.h"
34 
35 #include "ChangeTempo.h"
36 
37 enum
38 {
39  ID_PercentChange = 10000,
40  ID_FromBPM,
41  ID_ToBPM,
42  ID_FromLength,
44 };
45 
46 // Soundtouch is not reasonable below -99% or above 3000%.
47 
48 // Define keys, defaults, minimums, and maximums for the effect parameters
49 //
50 // Name Type Key Def Min Max Scale
51 Param( Percentage, double, wxT("Percentage"), 0.0, -99.0, 3000.0, 1 );
52 Param( UseSBSMS, bool, wxT("SBSMS"), false, false, true, 1 );
53 
54 // We warp the slider to go up to 400%, but user can enter higher values.
55 static const double kSliderMax = 100.0; // warped above zero to actually go up to 400%
56 static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400.
57 
58 //
59 // EffectChangeTempo
60 //
61 
62 BEGIN_EVENT_TABLE(EffectChangeTempo, wxEvtHandler)
63  EVT_TEXT(ID_PercentChange, EffectChangeTempo::OnText_PercentChange)
64  EVT_SLIDER(ID_PercentChange, EffectChangeTempo::OnSlider_PercentChange)
65  EVT_TEXT(ID_FromBPM, EffectChangeTempo::OnText_FromBPM)
66  EVT_TEXT(ID_ToBPM, EffectChangeTempo::OnText_ToBPM)
67  EVT_TEXT(ID_ToLength, EffectChangeTempo::OnText_ToLength)
69 
71 {
72  m_PercentChange = DEF_Percentage;
73  m_FromBPM = 0.0; // indicates not yet set
74  m_ToBPM = 0.0; // indicates not yet set
75  m_FromLength = 0.0;
76  m_ToLength = 0.0;
77 
78  m_bLoopDetect = false;
79 
80 #if USE_SBSMS
81  mUseSBSMS = DEF_UseSBSMS;
82 #else
83  mUseSBSMS = false;
84 #endif
85 
86  SetLinearEffectFlag(true);
87 }
88 
89 EffectChangeTempo::~EffectChangeTempo()
90 {
91 }
92 
93 // IdentInterface implementation
94 
95 wxString EffectChangeTempo::GetSymbol()
96 {
97  return CHANGETEMPO_PLUGIN_SYMBOL;
98 }
99 
100 wxString EffectChangeTempo::GetDescription()
101 {
102  return _("Change the tempo of a selection without changing its pitch");
103 }
104 
105 wxString EffectChangeTempo::ManualPage()
106 {
107  return wxT("Change_Tempo");
108 }
109 
110 // EffectIdentInterface implementation
111 
112 EffectType EffectChangeTempo::GetType()
113 {
114  return EffectTypeProcess;
115 }
116 
117 bool EffectChangeTempo::SupportsAutomation()
118 {
119  return true;
120 }
121 
122 // EffectClientInterface implementation
123 
124 bool EffectChangeTempo::GetAutomationParameters(EffectAutomationParameters & parms)
125 {
126  parms.Write(KEY_Percentage, m_PercentChange);
127  parms.Write(KEY_UseSBSMS, mUseSBSMS);
128 
129  return true;
130 }
131 
132 bool EffectChangeTempo::SetAutomationParameters(EffectAutomationParameters & parms)
133 {
134  ReadAndVerifyDouble(Percentage);
135  m_PercentChange = Percentage;
136 
137 #if USE_SBSMS
138  ReadAndVerifyBool(UseSBSMS);
139  mUseSBSMS = UseSBSMS;
140 #else
141  mUseSBSMS = false;
142 #endif
143 
144  return true;
145 }
146 
147 // Effect implementation
148 
149 double EffectChangeTempo::CalcPreviewInputLength(double previewLength)
150 {
151  return previewLength * (100.0 + m_PercentChange) / 100.0;
152 }
153 
154 bool EffectChangeTempo::CheckWhetherSkipEffect()
155 {
156  return (m_PercentChange == 0.0);
157 }
158 
159 bool EffectChangeTempo::Init()
160 {
161  // The selection might have changed since the last time EffectChangeTempo
162  // was invoked, so recalculate the Length parameters.
163  m_FromLength = mT1 - mT0;
164  m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
165 
166  mSoundTouch.reset();
167 
168  return true;
169 }
170 
171 bool EffectChangeTempo::Process()
172 {
173  bool success = false;
174 
175 #if USE_SBSMS
176  if (mUseSBSMS)
177  {
178  double tempoRatio = 1.0 + m_PercentChange / 100.0;
179  EffectSBSMS proxy;
180  proxy.mProxyEffectName = XO("High Quality Tempo Change");
181  proxy.setParameters(tempoRatio, 1.0);
182  success = Delegate(proxy, mUIParent, false);
183  }
184  else
185 #endif
186  {
187  mSoundTouch = std::make_unique<SoundTouch>();
188  mSoundTouch->setTempoChange(m_PercentChange);
189  double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0);
190  RegionTimeWarper warper{ mT0, mT1,
191  std::make_unique<LinearTimeWarper>(mT0, mT0, mT1, mT1Dashed ) };
192  success = EffectSoundTouch::ProcessWithTimeWarper(warper);
193  }
194 
195  if(success)
196  mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.);
197 
198  return success;
199 }
200 
201 void EffectChangeTempo::PopulateOrExchange(ShuttleGui & S)
202 {
203  S.StartVerticalLay(0);
204  {
205  S.AddSpace(0, 5);
206  S.AddTitle(_("Change Tempo without Changing Pitch"));
207  S.SetBorder(5);
208 
209  //
210  S.StartMultiColumn(2, wxCENTER);
211  {
212  FloatingPointValidator<double> vldPercentage(3, &m_PercentChange, NumValidatorStyle::THREE_TRAILING_ZEROES);
213  vldPercentage.SetRange(MIN_Percentage, MAX_Percentage);
214  m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
215  .AddTextBox(_("Percent Change:"), wxT(""), 12);
216  m_pTextCtrl_PercentChange->SetValidator(vldPercentage);
217  }
218  S.EndMultiColumn();
219 
220  //
221  S.StartHorizontalLay(wxEXPAND);
222  {
223  S.SetStyle(wxSL_HORIZONTAL);
224  m_pSlider_PercentChange = S.Id(ID_PercentChange)
225  .AddSlider( {}, 0, (int)kSliderMax, (int)MIN_Percentage);
226  m_pSlider_PercentChange->SetName(_("Percent Change"));
227  }
228  S.EndHorizontalLay();
229 
230  S.StartStatic(_("Beats per minute"));
231  {
232  S.StartHorizontalLay(wxALIGN_CENTER);
233  {
234  FloatingPointValidator<double> vldFromBPM(3, &m_FromBPM, NumValidatorStyle::THREE_TRAILING_ZEROES | NumValidatorStyle::ZERO_AS_BLANK);
235  m_pTextCtrl_FromBPM = S.Id(ID_FromBPM)
236  .AddTextBox(_("from"), wxT(""), 12);
237  m_pTextCtrl_FromBPM->SetName(_("Beats per minute, from"));
238  m_pTextCtrl_FromBPM->SetValidator(vldFromBPM);
239 
240  FloatingPointValidator<double> vldToBPM(3, &m_ToBPM, NumValidatorStyle::THREE_TRAILING_ZEROES | NumValidatorStyle::ZERO_AS_BLANK);
241  m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
242  .AddTextBox(_("to"), wxT(""), 12);
243  m_pTextCtrl_ToBPM->SetName(_("Beats per minute, to"));
244  m_pTextCtrl_ToBPM->SetValidator(vldToBPM);
245  }
246  S.EndHorizontalLay();
247  }
248  S.EndStatic();
249 
250  //
251  S.StartStatic(_("Length (seconds)"));
252  {
253  S.StartHorizontalLay(wxALIGN_CENTER);
254  {
255  int precission = 2;
256  FloatingPointValidator<double> vldFromLength(precission, &m_FromLength, NumValidatorStyle::TWO_TRAILING_ZEROES);
257  m_pTextCtrl_FromLength = S.Id(ID_FromLength)
258  .AddTextBox(_("from"), wxT(""), 12);
259  m_pTextCtrl_FromLength->SetValidator(vldFromLength);
260  m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection.
261 
262  FloatingPointValidator<double> vldToLength(2, &m_ToLength, NumValidatorStyle::TWO_TRAILING_ZEROES);
263 
264  // min and max need same precision as what we're validating (bug 963)
265  double minLength = (m_FromLength * 100.0) / (100.0 + MAX_Percentage);
266  double maxLength = (m_FromLength * 100.0) / (100.0 + MIN_Percentage);
267  minLength = Internat::CompatibleToDouble(Internat::ToString(minLength, precission));
268  maxLength = Internat::CompatibleToDouble(Internat::ToString(maxLength, precission));
269 
270  vldToLength.SetRange(minLength, maxLength);
271  m_pTextCtrl_ToLength = S.Id(ID_ToLength)
272  .AddTextBox(_("to"), wxT(""), 12);
273  m_pTextCtrl_ToLength->SetValidator(vldToLength);
274  }
275  S.EndHorizontalLay();
276  }
277  S.EndStatic();
278 
279 #if USE_SBSMS
280  S.StartMultiColumn(2);
281  {
282  mUseSBSMSCheckBox = S.AddCheckBox(_("Use high quality stretching (slow)"),
283  mUseSBSMS? wxT("true") : wxT("false"));
284  mUseSBSMSCheckBox->SetValidator(wxGenericValidator(&mUseSBSMS));
285  }
286  S.EndMultiColumn();
287 #endif
288 
289  }
290  S.EndVerticalLay();
291 
292  return;
293 }
294 
295 bool EffectChangeTempo::TransferDataToWindow()
296 {
297  // Reset from length because it can be changed by Preview
298  m_FromLength = mT1 - mT0;
299 
300  m_bLoopDetect = true;
301 
302  if (!mUIParent->TransferDataToWindow())
303  {
304  return false;
305  }
306 
307  // percent change controls
308  Update_Slider_PercentChange();
309  Update_Text_ToBPM();
310  Update_Text_ToLength();
311 
312  m_bLoopDetect = false;
313 
314  // Set the accessibility name here because we need m_pTextCtrl_FromLength to have had its value set
315  m_pTextCtrl_ToLength->SetName(
316  wxString::Format( _("Length in seconds from %s, to"),
317  m_pTextCtrl_FromLength->GetValue() ) );
318 
319  return true;
320 }
321 
322 bool EffectChangeTempo::TransferDataFromWindow()
323 {
324  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
325  {
326  return false;
327  }
328 
329  return true;
330 }
331 
332 // handler implementations for EffectChangeTempo
333 
334 void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
335 {
336  if (m_bLoopDetect)
337  return;
338 
339  m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow();
340 
341  m_bLoopDetect = true;
342  Update_Slider_PercentChange();
343  Update_Text_ToBPM();
344  Update_Text_ToLength();
345  m_bLoopDetect = false;
346 }
347 
348 void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
349 {
350  if (m_bLoopDetect)
351  return;
352 
353  m_PercentChange = (double)(m_pSlider_PercentChange->GetValue());
354  // Warp positive values to actually go up faster & further than negatives.
355  if (m_PercentChange > 0.0)
356  m_PercentChange = pow(m_PercentChange, kSliderWarp);
357 
358  m_bLoopDetect = true;
359  Update_Text_PercentChange();
360  Update_Text_ToBPM();
361  Update_Text_ToLength();
362  m_bLoopDetect = false;
363 }
364 
365 void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt))
366 {
367  if (m_bLoopDetect)
368  return;
369 
370  m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow();
371 
372  m_bLoopDetect = true;
373 
374  Update_Text_ToBPM();
375 
376  m_bLoopDetect = false;
377 }
378 
379 void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt))
380 {
381  if (m_bLoopDetect)
382  return;
383 
384  m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow();
385 
386  m_bLoopDetect = true;
387 
388  // If FromBPM has already been set, then there's a NEW percent change.
389  if (m_FromBPM != 0.0 && m_ToBPM != 0.0)
390  {
391  m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0;
392 
393  Update_Text_PercentChange();
394  Update_Slider_PercentChange();
395 
396  Update_Text_ToLength();
397  }
398 
399  m_bLoopDetect = false;
400 }
401 
402 void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt))
403 {
404  if (m_bLoopDetect)
405  return;
406 
407  m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow();
408 
409  if (m_ToLength != 0.0)
410  {
411  m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0;
412  }
413 
414  m_bLoopDetect = true;
415 
416  Update_Text_PercentChange();
417  Update_Slider_PercentChange();
418 
419  Update_Text_ToBPM();
420 
421  m_bLoopDetect = false;
422 }
423 
424 // helper fns
425 
426 void EffectChangeTempo::Update_Text_PercentChange()
427 {
428  m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
429 }
430 
431 void EffectChangeTempo::Update_Slider_PercentChange()
432 {
433  double unwarped = m_PercentChange;
434  if (unwarped > 0.0)
435  // Un-warp values above zero to actually go up to kSliderMax.
436  unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));
437 
438  // Add 0.5 to unwarped so trunc -> round.
439  m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
440 }
441 
442 void EffectChangeTempo::Update_Text_ToBPM()
443 // Use m_FromBPM & m_PercentChange to set NEW m_ToBPM & control.
444 {
445  m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0));
446  m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow();
447 }
448 
449 void EffectChangeTempo::Update_Text_ToLength()
450 // Use m_FromLength & m_PercentChange to set NEW m_ToLength & control.
451 {
452  m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
453  m_pTextCtrl_ToLength->GetValidator()->TransferToWindow();
454 }
455 
456 #endif // USE_SOUNDTOUCH
static const double kSliderMax
Definition: ChangeSpeed.cpp:70
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
void EndMultiColumn()
#define XO(s)
Definition: Internat.h:30
No change before the specified region; during the region, warp according to the given warper; after t...
Definition: TimeWarper.h:192
void EndHorizontalLay()
Definition: ShuttleGui.cpp:975
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper, LinearInputRateSlideTimeWarper, LinearOutputRateSlideTimeWarper, LinearInputInverseRateTimeWarper, GeometricInputRateTimeWarper, GeometricOutputRateTimeWarper classes.
void EndVerticalLay()
Definition: ShuttleGui.cpp:991
static const double kSliderWarp
Definition: ChangeSpeed.cpp:71
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:137
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:493
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:267
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:121
An EffectSoundTouch provides speeding up or slowing down tempo without changing pitch.
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:966
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
ShuttleGui & Id(int id)
void SetStyle(int Style)
Definition: ShuttleGui.h:252
#define ReadAndVerifyDouble(name)
Definition: Effect.h:791
void AddTitle(const wxString &Prompt)
Centred text string.
Definition: ShuttleGui.cpp:243
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:701
#define ReadAndVerifyBool(name)
Definition: Effect.h:793
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
#define Param(name, type, key, def, min, max, scale)
Definition: Effect.h:752
void SetBorder(int Border)
Definition: ShuttleGui.h:251
wxSlider * AddSlider(const wxString &Prompt, int pos, int Max, int Min=0)
Definition: ShuttleGui.cpp:456
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:982