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, -95.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 IdentInterfaceSymbol EffectChangeTempo::GetSymbol()
96 {
97  return CHANGETEMPO_PLUGIN_SYMBOL;
98 }
99 
100 wxString EffectChangeTempo::GetDescription()
101 {
102  return _("Changes the tempo of a selection without changing its pitch");
103 }
104 
105 wxString EffectChangeTempo::ManualPage()
106 {
107  return wxT("Change_Tempo");
108 }
109 
110 // EffectDefinitionInterface 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 bool EffectChangeTempo::DefineParams( ShuttleParams & S ){
124  S.SHUTTLE_PARAM( m_PercentChange, Percentage );
125  S.SHUTTLE_PARAM( mUseSBSMS, UseSBSMS );
126  return true;
127 }
128 
129 bool EffectChangeTempo::GetAutomationParameters(CommandParameters & parms)
130 {
131  parms.Write(KEY_Percentage, m_PercentChange);
132  parms.Write(KEY_UseSBSMS, mUseSBSMS);
133 
134  return true;
135 }
136 
137 bool EffectChangeTempo::SetAutomationParameters(CommandParameters & parms)
138 {
139  ReadAndVerifyDouble(Percentage);
140  m_PercentChange = Percentage;
141 
142 #if USE_SBSMS
143  ReadAndVerifyBool(UseSBSMS);
144  mUseSBSMS = UseSBSMS;
145 #else
146  mUseSBSMS = false;
147 #endif
148 
149  return true;
150 }
151 
152 // Effect implementation
153 
154 double EffectChangeTempo::CalcPreviewInputLength(double previewLength)
155 {
156  return previewLength * (100.0 + m_PercentChange) / 100.0;
157 }
158 
159 bool EffectChangeTempo::CheckWhetherSkipEffect()
160 {
161  return (m_PercentChange == 0.0);
162 }
163 
164 bool EffectChangeTempo::Init()
165 {
166  // The selection might have changed since the last time EffectChangeTempo
167  // was invoked, so recalculate the Length parameters.
168  m_FromLength = mT1 - mT0;
169  m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
170 
171  mSoundTouch.reset();
172 
173  return true;
174 }
175 
176 bool EffectChangeTempo::Process()
177 {
178  bool success = false;
179 
180 #if USE_SBSMS
181  if (mUseSBSMS)
182  {
183  double tempoRatio = 1.0 + m_PercentChange / 100.0;
184  EffectSBSMS proxy;
185  proxy.mProxyEffectName = XO("High Quality Tempo Change");
186  proxy.setParameters(tempoRatio, 1.0);
187  success = Delegate(proxy, mUIParent, false);
188  }
189  else
190 #endif
191  {
192  mSoundTouch = std::make_unique<SoundTouch>();
193  mSoundTouch->setTempoChange(m_PercentChange);
194  double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0);
195  RegionTimeWarper warper{ mT0, mT1,
196  std::make_unique<LinearTimeWarper>(mT0, mT0, mT1, mT1Dashed ) };
197  success = EffectSoundTouch::ProcessWithTimeWarper(warper);
198  }
199 
200  if(success)
201  mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.);
202 
203  return success;
204 }
205 
206 void EffectChangeTempo::PopulateOrExchange(ShuttleGui & S)
207 {
208  S.StartVerticalLay(0);
209  {
210  S.AddSpace(0, 5);
211  S.AddTitle(_("Change Tempo without Changing Pitch"));
212  S.SetBorder(5);
213 
214  //
215  S.StartMultiColumn(2, wxCENTER);
216  {
217  FloatingPointValidator<double> vldPercentage(3, &m_PercentChange, NumValidatorStyle::THREE_TRAILING_ZEROES);
218  vldPercentage.SetRange(MIN_Percentage, MAX_Percentage);
219  m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
220  .AddTextBox(_("Percent Change:"), wxT(""), 12);
221  m_pTextCtrl_PercentChange->SetValidator(vldPercentage);
222  }
223  S.EndMultiColumn();
224 
225  //
226  S.StartHorizontalLay(wxEXPAND);
227  {
228  S.SetStyle(wxSL_HORIZONTAL);
229  m_pSlider_PercentChange = S.Id(ID_PercentChange)
230  .AddSlider( {}, 0, (int)kSliderMax, (int)MIN_Percentage);
231  m_pSlider_PercentChange->SetName(_("Percent Change"));
232  }
233  S.EndHorizontalLay();
234 
235  S.StartStatic(_("Beats per minute"));
236  {
237  S.StartHorizontalLay(wxALIGN_CENTER);
238  {
239  FloatingPointValidator<double> vldFromBPM(3, &m_FromBPM, NumValidatorStyle::THREE_TRAILING_ZEROES | NumValidatorStyle::ZERO_AS_BLANK);
240  m_pTextCtrl_FromBPM = S.Id(ID_FromBPM)
241  .AddTextBox(_("from"), wxT(""), 12);
242  m_pTextCtrl_FromBPM->SetName(_("Beats per minute, from"));
243  m_pTextCtrl_FromBPM->SetValidator(vldFromBPM);
244 
245  FloatingPointValidator<double> vldToBPM(3, &m_ToBPM, NumValidatorStyle::THREE_TRAILING_ZEROES | NumValidatorStyle::ZERO_AS_BLANK);
246  m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
247  .AddTextBox(_("to"), wxT(""), 12);
248  m_pTextCtrl_ToBPM->SetName(_("Beats per minute, to"));
249  m_pTextCtrl_ToBPM->SetValidator(vldToBPM);
250  }
251  S.EndHorizontalLay();
252  }
253  S.EndStatic();
254 
255  //
256  S.StartStatic(_("Length (seconds)"));
257  {
258  S.StartHorizontalLay(wxALIGN_CENTER);
259  {
260  int precission = 2;
261  FloatingPointValidator<double> vldFromLength(precission, &m_FromLength, NumValidatorStyle::TWO_TRAILING_ZEROES);
262  m_pTextCtrl_FromLength = S.Id(ID_FromLength)
263  .AddTextBox(_("from"), wxT(""), 12);
264  m_pTextCtrl_FromLength->SetValidator(vldFromLength);
265  m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection.
266 
267  FloatingPointValidator<double> vldToLength(2, &m_ToLength, NumValidatorStyle::TWO_TRAILING_ZEROES);
268 
269  // min and max need same precision as what we're validating (bug 963)
270  double minLength = (m_FromLength * 100.0) / (100.0 + MAX_Percentage);
271  double maxLength = (m_FromLength * 100.0) / (100.0 + MIN_Percentage);
272  minLength = Internat::CompatibleToDouble(Internat::ToString(minLength, precission));
273  maxLength = Internat::CompatibleToDouble(Internat::ToString(maxLength, precission));
274 
275  vldToLength.SetRange(minLength, maxLength);
276  m_pTextCtrl_ToLength = S.Id(ID_ToLength)
277  .AddTextBox(_("to"), wxT(""), 12);
278  m_pTextCtrl_ToLength->SetValidator(vldToLength);
279  }
280  S.EndHorizontalLay();
281  }
282  S.EndStatic();
283 
284 #if USE_SBSMS
285  S.StartMultiColumn(2);
286  {
287  mUseSBSMSCheckBox = S.AddCheckBox(_("Use high quality stretching (slow)"),
288  mUseSBSMS? wxT("true") : wxT("false"));
289  mUseSBSMSCheckBox->SetValidator(wxGenericValidator(&mUseSBSMS));
290  }
291  S.EndMultiColumn();
292 #endif
293 
294  }
295  S.EndVerticalLay();
296 
297  return;
298 }
299 
300 bool EffectChangeTempo::TransferDataToWindow()
301 {
302  // Reset from length because it can be changed by Preview
303  m_FromLength = mT1 - mT0;
304 
305  m_bLoopDetect = true;
306 
307  if (!mUIParent->TransferDataToWindow())
308  {
309  return false;
310  }
311 
312  // percent change controls
313  Update_Slider_PercentChange();
314  Update_Text_ToBPM();
315  Update_Text_ToLength();
316 
317  m_bLoopDetect = false;
318 
319  // Set the accessibility name here because we need m_pTextCtrl_FromLength to have had its value set
320  m_pTextCtrl_ToLength->SetName(
321  wxString::Format( _("Length in seconds from %s, to"),
322  m_pTextCtrl_FromLength->GetValue() ) );
323 
324  return true;
325 }
326 
327 bool EffectChangeTempo::TransferDataFromWindow()
328 {
329  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
330  {
331  return false;
332  }
333 
334  return true;
335 }
336 
337 // handler implementations for EffectChangeTempo
338 
339 void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
340 {
341  if (m_bLoopDetect)
342  return;
343 
344  m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow();
345 
346  m_bLoopDetect = true;
347  Update_Slider_PercentChange();
348  Update_Text_ToBPM();
349  Update_Text_ToLength();
350  m_bLoopDetect = false;
351 }
352 
353 void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
354 {
355  if (m_bLoopDetect)
356  return;
357 
358  m_PercentChange = (double)(m_pSlider_PercentChange->GetValue());
359  // Warp positive values to actually go up faster & further than negatives.
360  if (m_PercentChange > 0.0)
361  m_PercentChange = pow(m_PercentChange, kSliderWarp);
362 
363  m_bLoopDetect = true;
364  Update_Text_PercentChange();
365  Update_Text_ToBPM();
366  Update_Text_ToLength();
367  m_bLoopDetect = false;
368 }
369 
370 void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt))
371 {
372  if (m_bLoopDetect)
373  return;
374 
375  m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow();
376 
377  m_bLoopDetect = true;
378 
379  Update_Text_ToBPM();
380 
381  m_bLoopDetect = false;
382 }
383 
384 void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt))
385 {
386  if (m_bLoopDetect)
387  return;
388 
389  m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow();
390 
391  m_bLoopDetect = true;
392 
393  // If FromBPM has already been set, then there's a NEW percent change.
394  if (m_FromBPM != 0.0 && m_ToBPM != 0.0)
395  {
396  m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0;
397 
398  Update_Text_PercentChange();
399  Update_Slider_PercentChange();
400 
401  Update_Text_ToLength();
402  }
403 
404  m_bLoopDetect = false;
405 }
406 
407 void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt))
408 {
409  if (m_bLoopDetect)
410  return;
411 
412  m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow();
413 
414  if (m_ToLength != 0.0)
415  {
416  m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0;
417  }
418 
419  m_bLoopDetect = true;
420 
421  Update_Text_PercentChange();
422  Update_Slider_PercentChange();
423 
424  Update_Text_ToBPM();
425 
426  m_bLoopDetect = false;
427 }
428 
429 // helper fns
430 
431 void EffectChangeTempo::Update_Text_PercentChange()
432 {
433  m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
434 }
435 
436 void EffectChangeTempo::Update_Slider_PercentChange()
437 {
438  double unwarped = m_PercentChange;
439  if (unwarped > 0.0)
440  // Un-warp values above zero to actually go up to kSliderMax.
441  unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));
442 
443  // Add 0.5 to unwarped so trunc -> round.
444  m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
445 }
446 
447 void EffectChangeTempo::Update_Text_ToBPM()
448 // Use m_FromBPM & m_PercentChange to set NEW m_ToBPM & control.
449 {
450  m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0));
451  m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow();
452 }
453 
454 void EffectChangeTempo::Update_Text_ToLength()
455 // Use m_FromLength & m_PercentChange to set NEW m_ToLength & control.
456 {
457  m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
458  m_pTextCtrl_ToLength->GetValidator()->TransferToWindow();
459 }
460 
461 #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:409
void EndMultiColumn()
#define XO(s)
Definition: Internat.h:33
Shuttle that deals with parameters. This is a base class with lots of virtual functions that do nothi...
Definition: Shuttle.h:60
No change before the specified region; during the region, warp according to the given warper; after t...
Definition: TimeWarper.h:192
void EndHorizontalLay()
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper, LinearInputRateSlideTimeWarper, LinearOutputRateSlideTimeWarper, LinearInputInverseRateTimeWarper, GeometricInputRateTimeWarper, GeometricOutputRateTimeWarper classes.
void EndVerticalLay()
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:138
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:540
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:298
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:122
An EffectSoundTouch provides speeding up or slowing down tempo without changing pitch.
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the Shuttle cla...
ShuttleGui & Id(int id)
void SetStyle(int Style)
Definition: ShuttleGui.h:287
#define ReadAndVerifyDouble(name)
Definition: Effect.h:798
void AddTitle(const wxString &Prompt)
Centred text string.
Definition: ShuttleGui.cpp:274
IdentInterfaceSymbol pairs a persistent string identifier used internally with an optional...
_("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
EffectType
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:763
#define ReadAndVerifyBool(name)
Definition: Effect.h:800
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
#define Param(name, type, key, def, min, max, scale)
Definition: Effect.h:753
void SetBorder(int Border)
Definition: ShuttleGui.h:286
wxSlider * AddSlider(const wxString &Prompt, int pos, int Max, int Min=0)
Definition: ShuttleGui.cpp:497
void StartVerticalLay(int iProp=1)