Audacity 3.2.0
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#if USE_SOUNDTOUCH
18#include "ChangeTempo.h"
19#include "EffectEditor.h"
20
21#if USE_SBSMS
22#include <wx/valgen.h>
23#endif
24
25#include <math.h>
26
27#include <wx/checkbox.h>
28#include <wx/slider.h>
29
30#include "ShuttleGui.h"
31#include "../widgets/valnum.h"
32#include "TimeWarper.h"
33
34#include "LoadEffects.h"
35
36// Soundtouch defines these as well, which are also in generated configmac.h
37// and configunix.h, so get rid of them before including,
38// to avoid compiler warnings, and be sure to do this
39// after all other #includes, to avoid any mischief that might result
40// from doing the un-definitions before seeing any wx headers.
41#undef PACKAGE_NAME
42#undef PACKAGE_STRING
43#undef PACKAGE_TARNAME
44#undef PACKAGE_VERSION
45#undef PACKAGE_BUGREPORT
46#undef PACKAGE
47#undef VERSION
48#include "SoundTouch.h"
49
50enum
51{
52 ID_PercentChange = 10000,
53 ID_FromBPM,
54 ID_ToBPM,
55 ID_FromLength,
57};
58
59// Soundtouch is not reasonable below -99% or above 3000%.
60
61const EffectParameterMethods& EffectChangeTempo::Parameters() const
62{
64 Percentage, UseSBSMS
65 > parameters;
66 return parameters;
67}
68
69// We warp the slider to go up to 400%, but user can enter higher values.
70static const double kSliderMax = 100.0; // warped above zero to actually go up to 400%
71static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400.
72
73//
74// EffectChangeTempo
75//
76
77const ComponentInterfaceSymbol EffectChangeTempo::Symbol
78{ XO("Change Tempo") };
79
81
82BEGIN_EVENT_TABLE(EffectChangeTempo, wxEvtHandler)
83 EVT_TEXT(ID_PercentChange, EffectChangeTempo::OnText_PercentChange)
84 EVT_SLIDER(ID_PercentChange, EffectChangeTempo::OnSlider_PercentChange)
85 EVT_TEXT(ID_FromBPM, EffectChangeTempo::OnText_FromBPM)
86 EVT_TEXT(ID_ToBPM, EffectChangeTempo::OnText_ToBPM)
87 EVT_TEXT(ID_ToLength, EffectChangeTempo::OnText_ToLength)
89
91{
92 // mUseSBSMS always defaults to false and its value is used only if USE_SBSMS
93 // is defined
94 Parameters().Reset(*this);
95 m_FromBPM = 0.0; // indicates not yet set
96 m_ToBPM = 0.0; // indicates not yet set
97 m_FromLength = 0.0;
98 m_ToLength = 0.0;
99
100 m_bLoopDetect = false;
101
102 SetLinearEffectFlag(true);
103}
104
105EffectChangeTempo::~EffectChangeTempo()
106{
107}
108
109// ComponentInterface implementation
110
111ComponentInterfaceSymbol EffectChangeTempo::GetSymbol() const
112{
113 return Symbol;
114}
115
116TranslatableString EffectChangeTempo::GetDescription() const
117{
118 return XO("Changes the tempo of a selection without changing its pitch");
119}
120
121ManualPageID EffectChangeTempo::ManualPage() const
122{
123 return L"Change_Tempo";
124}
125
126// EffectDefinitionInterface implementation
127
128EffectType EffectChangeTempo::GetType() const
129{
130 return EffectTypeProcess;
131}
132
133bool EffectChangeTempo::SupportsAutomation() const
134{
135 return true;
136}
137
138// Effect implementation
139
140double EffectChangeTempo::CalcPreviewInputLength(
141 const EffectSettings &, double previewLength) const
142{
143 return previewLength * (100.0 + m_PercentChange) / 100.0;
144}
145
146bool EffectChangeTempo::CheckWhetherSkipEffect(const EffectSettings &) const
147{
148 return (m_PercentChange == 0.0);
149}
150
151bool EffectChangeTempo::Init()
152{
153 // The selection might have changed since the last time EffectChangeTempo
154 // was invoked, so recalculate the Length parameters.
155 m_FromLength = mT1 - mT0;
156 m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
157
158 return true;
159}
160
161bool EffectChangeTempo::Process(EffectInstance &, EffectSettings &settings)
162{
163 bool success = false;
164
165#if USE_SBSMS
166 if (mUseSBSMS)
167 {
168 double tempoRatio = 1.0 + m_PercentChange / 100.0;
169 EffectSBSMS proxy;
170 proxy.mProxyEffectName = XO("High Quality Tempo Change");
171 proxy.setParameters(tempoRatio, 1.0);
173 success = Delegate(proxy, settings);
174 }
175 else
176#endif
177 {
178 auto initer = [&](soundtouch::SoundTouch *soundtouch)
179 {
180 soundtouch->setTempoChange(m_PercentChange);
181 };
182 double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0);
183 RegionTimeWarper warper{ mT0, mT1,
184 std::make_unique<LinearTimeWarper>(mT0, mT0, mT1, mT1Dashed ) };
185 success = EffectSoundTouch::ProcessWithTimeWarper(initer, warper, false);
186 }
187
188 if(success)
189 mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.);
190
191 return success;
192}
193
194std::unique_ptr<EffectEditor> EffectChangeTempo::PopulateOrExchange(
196 const EffectOutputs *)
197{
198 mUIParent = S.GetParent();
199
200 enum { precision = 2 };
201
202 S.StartVerticalLay(0);
203 {
204 //
205 S.StartMultiColumn(2, wxCENTER);
206 {
207 m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
208 .Validator<FloatingPointValidator<double>>(
209 3, &m_PercentChange, NumValidatorStyle::THREE_TRAILING_ZEROES,
210 Percentage.min, Percentage.max )
211 .AddTextBox(XXO("Percent C&hange:"), L"", 12);
212 }
213 S.EndMultiColumn();
214
215 //
216 S.StartHorizontalLay(wxEXPAND);
217 {
218 m_pSlider_PercentChange = S.Id(ID_PercentChange)
219 .Name(XO("Percent Change"))
220 .Style(wxSL_HORIZONTAL)
221 .AddSlider( {}, 0, (int)kSliderMax, (int)Percentage.min);
222 }
223 S.EndHorizontalLay();
224
225 S.StartStatic(XO("Beats per minute"));
226 {
227 S.StartHorizontalLay(wxALIGN_CENTER);
228 {
229 m_pTextCtrl_FromBPM = S.Id(ID_FromBPM)
230 /* i18n-hint: changing tempo "from" one value "to" another */
231 .Name(XO("Beats per minute, from"))
232 .Validator<FloatingPointValidator<double>>(
233 3, &m_FromBPM,
234 NumValidatorStyle::THREE_TRAILING_ZEROES
235 | NumValidatorStyle::ZERO_AS_BLANK)
236 /* i18n-hint: changing tempo "from" one value "to" another */
237 .AddTextBox(XXC("&from", "change tempo"), wxT(""), 12);
238
239 m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
240 /* i18n-hint: changing tempo "from" one value "to" another */
241 .Name(XO("Beats per minute, to"))
242 .Validator<FloatingPointValidator<double>>(
243 3, &m_ToBPM,
244 NumValidatorStyle::THREE_TRAILING_ZEROES
245 | NumValidatorStyle::ZERO_AS_BLANK)
246 /* i18n-hint: changing tempo "from" one value "to" another */
247 .AddTextBox(XXC("&to", "change tempo"), wxT(""), 12);
248 }
249 S.EndHorizontalLay();
250 }
251 S.EndStatic();
252
253 //
254 S.StartStatic(XO("Length (seconds)"));
255 {
256 S.StartHorizontalLay(wxALIGN_CENTER);
257 {
258 m_pTextCtrl_FromLength = S.Id(ID_FromLength)
259 .Disable() // Disable because the value comes from the
260 // user selection.
261 .Validator<FloatingPointValidator<double>>(
262 precision, &m_FromLength,
263 NumValidatorStyle::TWO_TRAILING_ZEROES)
264 /* i18n-hint: changing tempo "from" one value "to" another */
265 .AddTextBox(XXC("from", "change tempo"), wxT(""), 12);
266 m_pTextCtrl_ToLength = S.Id(ID_ToLength)
267 .Validator<FloatingPointValidator<double>>(
268 2, &m_ToLength, NumValidatorStyle::TWO_TRAILING_ZEROES,
269 // min and max need same precision as what we're validating (bug 963)
270 RoundValue( precision,
271 (m_FromLength * 100.0) / (100.0 + Percentage.max) ),
272 RoundValue( precision,
273 (m_FromLength * 100.0) / (100.0 + Percentage.min) ) )
274 /* i18n-hint: changing tempo "from" one value "to" another */
275 .AddTextBox(XXC("t&o", "change tempo"), wxT(""), 12);
276 }
277 S.EndHorizontalLay();
278 }
279 S.EndStatic();
280
281#if USE_SBSMS
282 S.StartMultiColumn(2);
283 {
284 mUseSBSMSCheckBox = S.Validator<wxGenericValidator>(&mUseSBSMS)
285 .AddCheckBox(XXO("&Use high quality stretching (slow)"),
286 mUseSBSMS);
287 }
288 S.EndMultiColumn();
289#endif
290
291 }
292 S.EndVerticalLay();
293
294 return nullptr;
295}
296
297bool EffectChangeTempo::TransferDataToWindow(const EffectSettings &)
298{
299 // Reset from length because it can be changed by Preview
300 m_FromLength = mT1 - mT0;
301
302 m_bLoopDetect = true;
303
304 if (!mUIParent->TransferDataToWindow())
305 {
306 return false;
307 }
308
309 // percent change controls
310 Update_Slider_PercentChange();
311 Update_Text_ToBPM();
312 Update_Text_ToLength();
313
314 m_bLoopDetect = false;
315
316 // Set the accessibility name here because we need m_pTextCtrl_FromLength to have had its value set
317 m_pTextCtrl_ToLength->SetName(
318 wxString::Format( _("Length in seconds from %s, to"),
319 m_pTextCtrl_FromLength->GetValue() ) );
320
321 return true;
322}
323
324bool EffectChangeTempo::TransferDataFromWindow(EffectSettings &)
325{
326 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
327 {
328 return false;
329 }
330
331 return true;
332}
333
334// handler implementations for EffectChangeTempo
335
336void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
337{
338 if (m_bLoopDetect)
339 return;
340
341 m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow();
342
343 m_bLoopDetect = true;
344 Update_Slider_PercentChange();
345 Update_Text_ToBPM();
346 Update_Text_ToLength();
347 m_bLoopDetect = false;
348}
349
350void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
351{
352 if (m_bLoopDetect)
353 return;
354
355 m_PercentChange = (double)(m_pSlider_PercentChange->GetValue());
356 // Warp positive values to actually go up faster & further than negatives.
357 if (m_PercentChange > 0.0)
358 m_PercentChange = pow(m_PercentChange, kSliderWarp);
359
360 m_bLoopDetect = true;
361 Update_Text_PercentChange();
362 Update_Text_ToBPM();
363 Update_Text_ToLength();
364 m_bLoopDetect = false;
365}
366
367void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt))
368{
369 if (m_bLoopDetect)
370 return;
371
372 m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow();
373
374 m_bLoopDetect = true;
375
376 Update_Text_ToBPM();
377
378 m_bLoopDetect = false;
379}
380
381void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt))
382{
383 if (m_bLoopDetect)
384 return;
385
386 m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow();
387
388 m_bLoopDetect = true;
389
390 // If FromBPM has already been set, then there's a NEW percent change.
391 if (m_FromBPM != 0.0 && m_ToBPM != 0.0)
392 {
393 m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0;
394
395 Update_Text_PercentChange();
396 Update_Slider_PercentChange();
397
398 Update_Text_ToLength();
399 }
400
401 m_bLoopDetect = false;
402}
403
404void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt))
405{
406 if (m_bLoopDetect)
407 return;
408
409 m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow();
410
411 if (m_ToLength != 0.0)
412 {
413 m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0;
414 }
415
416 m_bLoopDetect = true;
417
418 Update_Text_PercentChange();
419 Update_Slider_PercentChange();
420
421 Update_Text_ToBPM();
422
423 m_bLoopDetect = false;
424}
425
426// helper fns
427
428void EffectChangeTempo::Update_Text_PercentChange()
429{
430 m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
431}
432
433void EffectChangeTempo::Update_Slider_PercentChange()
434{
435 double unwarped = m_PercentChange;
436 if (unwarped > 0.0)
437 // Un-warp values above zero to actually go up to kSliderMax.
438 unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));
439
440 // Add 0.5 to unwarped so trunc -> round.
441 m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
442}
443
444void EffectChangeTempo::Update_Text_ToBPM()
445// Use m_FromBPM & m_PercentChange to set NEW m_ToBPM & control.
446{
447 m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0));
448 m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow();
449}
450
451void EffectChangeTempo::Update_Text_ToLength()
452// Use m_FromLength & m_PercentChange to set NEW m_ToLength & control.
453{
454 m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
455 m_pTextCtrl_ToLength->GetValidator()->TransferToWindow();
456}
457
458#endif // USE_SOUNDTOUCH
wxT("CloseDown"))
END_EVENT_TABLE()
@ ID_ToLength
Definition: ChangeSpeed.cpp:48
@ ID_PercentChange
Definition: ChangeSpeed.cpp:44
static const double kSliderMax
Definition: ChangeSpeed.cpp:80
static const double kSliderWarp
Definition: ChangeSpeed.cpp:81
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define XXC(s, c)
Definition: Internat.h:47
#define _(s)
Definition: Internat.h:73
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:69
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
An EffectSoundTouch provides speeding up or slowing down tempo without changing pitch.
Performs effect computation.
Hold values to send to effect output meters.
Interface for manipulations of an Effect's settings.
void setParameters(double rateStart, double rateEnd, double pitchStart, double pitchEnd, SlideType rateSlideType, SlideType pitchSlideType, bool bLinkRatePitch, bool bRateReferenceInput, bool bPitchReferenceInput)
TranslatableString mProxyEffectName
Definition: SBSMSEffect.h:41
No change before the specified region; during the region, warp according to the given warper; after t...
Definition: TimeWarper.h:192
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
A Validator is an object which checks whether a wxVariant satisfies a certain criterion....
Definition: Validators.h:54
BuiltinCommandsModule::Registration< CompareAudioCommand > reg
Externalized state of a plug-in.