Audacity 3.2.0
ChangePitch.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4 Audacity(R) is copyright (c) 1999-2013 Audacity Team.
5 License: GPL v2 or later. See License.txt.
6
7 ChangePitch.cpp
8 Vaughan Johnson, Dominic Mazzoni, Steve Daulton
9
10******************************************************************//*******************************************************************/
17
18
19
20#if USE_SOUNDTOUCH
21#include "ChangePitch.h"
22#include "EffectEditor.h"
23#include "LoadEffects.h"
24#include "PitchName.h"
25
26#if USE_SBSMS
27#include <wx/valgen.h>
28#endif
29
30#include <wx/checkbox.h>
31#include <wx/choice.h>
32#include <wx/slider.h>
33#include <wx/spinctrl.h>
34#include <wx/valtext.h>
35
36#include "ShuttleGui.h"
37#include "../widgets/valnum.h"
38
39enum {
40 ID_PercentChange = 10000,
41 ID_FromPitch,
42 ID_FromOctave,
43 ID_ToPitch,
44 ID_ToOctave,
45 ID_SemitonesChange,
46 ID_FromFrequency,
47 ID_ToFrequency
48};
49
50// We warp the slider to go up to 400%, but user can enter up to 3000%
51static const double kSliderMax =
52 100.0; // warped above zero to actually go up to 400%
53static const double kSliderWarp =
54 1.30105; // warp power takes max from 100 to 400.
55
57
58BEGIN_EVENT_TABLE(EffectChangePitch, wxEvtHandler)
59 EVT_CHOICE(ID_FromPitch, EffectChangePitch::OnChoice_FromPitch)
60 EVT_TEXT(ID_FromOctave, EffectChangePitch::OnSpin_FromOctave)
61 EVT_CHOICE(ID_ToPitch, EffectChangePitch::OnChoice_ToPitch)
62 EVT_TEXT(ID_ToOctave, EffectChangePitch::OnSpin_ToOctave)
63
64 EVT_TEXT(ID_SemitonesChange, EffectChangePitch::OnText_SemitonesChange)
65
66 EVT_TEXT(ID_FromFrequency, EffectChangePitch::OnText_FromFrequency)
67 EVT_TEXT(ID_ToFrequency, EffectChangePitch::OnText_ToFrequency)
68
69 EVT_TEXT(ID_PercentChange, EffectChangePitch::OnText_PercentChange)
70 EVT_SLIDER(ID_PercentChange, EffectChangePitch::OnSlider_PercentChange)
72
73std::unique_ptr<EffectEditor> EffectChangePitch::PopulateOrExchange(
75 const EffectOutputs *)
76{
77 mUIParent = S.GetParent();
78 DeduceFrequencies(); // Set frequency-related control values based on sample.
79
81 for (int ii = 0; ii < 12; ++ii)
82 pitch.push_back( PitchName( ii, PitchNameChoice::Both ) );
83
84 S.SetBorder(5);
85
86 S.StartVerticalLay(0);
87 {
88 S.StartVerticalLay();
89 {
90 S.AddTitle(
91 XO("Estimated Start Pitch: %s%d (%.3f Hz)")
92 .Format( pitch[m_nFromPitch], m_nFromOctave, m_FromFrequency) );
93 }
94 S.EndVerticalLay();
95
96 /* i18n-hint: (noun) Musical pitch.*/
97 S.StartStatic(XO("Pitch"));
98 {
99 S.StartMultiColumn(6, wxALIGN_CENTER); // 6 controls, because each AddChoice adds a wxStaticText and a wxChoice.
100 {
101 m_pChoice_FromPitch = S.Id(ID_FromPitch)
102 /* i18n-hint: changing musical pitch "from" one value "to" another */
103 .Name(XC("from", "change pitch"))
104 .MinSize( { 80, -1 } )
105 /* i18n-hint: changing musical pitch "from" one value "to" another */
106 .AddChoice(XXC("&from", "change pitch"), pitch);
107
108 m_pSpin_FromOctave = S.Id(ID_FromOctave)
109 .Name(XO("from Octave"))
110 .MinSize( { 50, -1 } )
111 .AddSpinCtrl( {}, m_nFromOctave, INT_MAX, INT_MIN);
112
113 m_pChoice_ToPitch = S.Id(ID_ToPitch)
114 /* i18n-hint: changing musical pitch "from" one value "to" another */
115 .Name(XC("to", "change pitch"))
116 .MinSize( { 80, -1 } )
117 /* i18n-hint: changing musical pitch "from" one value "to" another */
118 .AddChoice(XXC("&to", "change pitch"), pitch);
119
120 m_pSpin_ToOctave = S.Id(ID_ToOctave)
121 .Name(XO("to Octave"))
122 .MinSize( { 50, -1 } )
123 .AddSpinCtrl( {}, m_nToOctave, INT_MAX, INT_MIN);
124 }
125 S.EndMultiColumn();
126
127 S.StartHorizontalLay(wxALIGN_CENTER);
128 {
129 m_pTextCtrl_SemitonesChange = S.Id(ID_SemitonesChange)
130 .Name(XO("Semitones (half-steps)"))
131 .Validator<FloatingPointValidator<double>>(
132 2, &m_dSemitonesChange,
133 NumValidatorStyle::TWO_TRAILING_ZEROES
134 )
135 .AddTextBox(XXO("&Semitones (half-steps):"), wxT(""), 12);
136 }
137 S.EndHorizontalLay();
138 }
139 S.EndStatic();
140
141 S.StartStatic(XO("Frequency"));
142 {
143 S.StartMultiColumn(5, wxALIGN_CENTER); // 5, because AddTextBox adds a wxStaticText and a wxTextCtrl.
144 {
145 m_pTextCtrl_FromFrequency = S.Id(ID_FromFrequency)
146 .Name(XO("from (Hz)"))
147 .Validator<FloatingPointValidator<double>>(
148 3, &m_FromFrequency,
149 NumValidatorStyle::THREE_TRAILING_ZEROES,
150 0.0
151 )
152 .AddTextBox(XXO("f&rom"), wxT(""), 12);
153
154 m_pTextCtrl_ToFrequency = S.Id(ID_ToFrequency)
155 .Name(XO("to (Hz)"))
156 .Validator<FloatingPointValidator<double>>(
157 3, &m_ToFrequency,
158 NumValidatorStyle::THREE_TRAILING_ZEROES,
159 0.0
160 )
161 .AddTextBox(XXO("t&o"), wxT(""), 12);
162
163 S.AddUnits(XO("Hz"));
164 }
165 S.EndMultiColumn();
166
167 S.StartHorizontalLay(wxALIGN_CENTER);
168 {
169 m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
170 .Validator<FloatingPointValidator<double>>(
171 3, &m_dPercentChange,
172 NumValidatorStyle::THREE_TRAILING_ZEROES,
173 Percentage.min, Percentage.max )
174 .AddTextBox(XXO("Percent C&hange:"), L"", 12);
175 }
176 S.EndHorizontalLay();
177
178 S.StartHorizontalLay(wxEXPAND);
179 {
180 m_pSlider_PercentChange = S.Id(ID_PercentChange)
181 .Name(XO("Percent Change"))
182 .Style(wxSL_HORIZONTAL)
183 .AddSlider( {}, 0, (int)kSliderMax, (int)Percentage.min);
184 }
185 S.EndHorizontalLay();
186 }
187 S.EndStatic();
188
189#if USE_SBSMS
190 S.StartMultiColumn(2);
191 {
192 mUseSBSMSCheckBox = S.Validator<wxGenericValidator>(&mUseSBSMS)
193 .AddCheckBox(XXO("&Use high quality stretching (slow)"),
194 mUseSBSMS);
195 }
196 S.EndMultiColumn();
197#endif
198
199 }
200 S.EndVerticalLay();
201 return nullptr;
202}
203
204bool EffectChangePitch::TransferDataToWindow(const EffectSettings &)
205{
206 m_bLoopDetect = true;
207
208 if (!mUIParent->TransferDataToWindow())
209 {
210 return false;
211 }
212
213 Calc_SemitonesChange_fromPercentChange();
214 Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
215 Calc_ToFrequency();
216 Calc_ToOctave(); // Call after Calc_ToFrequency().
217
218 Update_Choice_FromPitch();
219 Update_Choice_ToPitch();
220 Update_Spin_FromOctave();
221 Update_Spin_ToOctave();
222 Update_Text_SemitonesChange();
223 Update_Text_FromFrequency();
224 Update_Text_ToFrequency();
225 Update_Text_PercentChange();
226 Update_Slider_PercentChange();
227
228 m_bLoopDetect = false;
229
230 return true;
231}
232
233bool EffectChangePitch::TransferDataFromWindow(EffectSettings &)
234{
235 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
236 {
237 return false;
238 }
239
240 // from/to pitch controls
241 m_nFromPitch = m_pChoice_FromPitch->GetSelection();
242 m_nFromOctave = m_pSpin_FromOctave->GetValue();
243
244 m_nToPitch = m_pChoice_ToPitch->GetSelection();
245
246 // No need to update Slider_PercentChange here because TextCtrl_PercentChange
247 // always tracks it & is more precise (decimal points).
248
249 return true;
250}
251
252// handlers
253void EffectChangePitch::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(evt))
254{
255 if (m_bLoopDetect)
256 return;
257
258 m_nFromPitch = m_pChoice_FromPitch->GetSelection();
259 m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
260
261 Calc_ToPitch();
262 Calc_ToFrequency();
263 Calc_ToOctave(); // Call after Calc_ToFrequency().
264
265 m_bLoopDetect = true;
266 {
267 Update_Choice_ToPitch();
268 Update_Spin_ToOctave();
269 Update_Text_FromFrequency();
270 Update_Text_ToFrequency();
271 }
272 m_bLoopDetect = false;
273}
274
275void EffectChangePitch::OnSpin_FromOctave(wxCommandEvent & WXUNUSED(evt))
276{
277 if (m_bLoopDetect)
278 return;
279
280 m_nFromOctave = m_pSpin_FromOctave->GetValue();
281 //vvv If I change this code to not keep semitones and percent constant,
282 // will need validation code as in OnSpin_ToOctave.
283 m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
284
285 Calc_ToFrequency();
286 Calc_ToOctave(); // Call after Calc_ToFrequency().
287
288 m_bLoopDetect = true;
289 {
290 Update_Spin_ToOctave();
291 Update_Text_FromFrequency();
292 Update_Text_ToFrequency();
293 }
294 m_bLoopDetect = false;
295}
296
297void EffectChangePitch::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(evt))
298{
299 if (m_bLoopDetect)
300 return;
301
302 m_nToPitch = m_pChoice_ToPitch->GetSelection();
303
304 Calc_SemitonesChange_fromPitches();
305 Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
306 Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
307
308 m_bLoopDetect = true;
309 {
310 Update_Text_SemitonesChange();
311 Update_Text_ToFrequency();
312 Update_Text_PercentChange();
313 Update_Slider_PercentChange();
314 }
315 m_bLoopDetect = false;
316}
317
318void EffectChangePitch::OnSpin_ToOctave(wxCommandEvent & WXUNUSED(evt))
319{
320 if (m_bLoopDetect)
321 return;
322
323 int nNewValue = m_pSpin_ToOctave->GetValue();
324 // Validation: Rather than set a range for octave numbers, enforce a range that
325 // keeps m_dPercentChange above -99%, per Soundtouch constraints.
326 if ((nNewValue + 3) < m_nFromOctave)
327 {
328 ::wxBell();
329 m_pSpin_ToOctave->SetValue(m_nFromOctave - 3);
330 return;
331 }
332 m_nToOctave = nNewValue;
333
334 m_ToFrequency = PitchToFreq(m_nToPitch, m_nToOctave);
335
336 Calc_SemitonesChange_fromPitches();
337 Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
338
339 m_bLoopDetect = true;
340 {
341 Update_Text_SemitonesChange();
342 Update_Text_ToFrequency();
343 Update_Text_PercentChange();
344 Update_Slider_PercentChange();
345 }
346 m_bLoopDetect = false;
347}
348
349void EffectChangePitch::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(evt))
350{
351 if (m_bLoopDetect)
352 return;
353
354 if (!m_pTextCtrl_SemitonesChange->GetValidator()->TransferFromWindow())
355 {
356 EffectEditor::EnableApply(mUIParent, false);
357 return;
358 }
359
360 Calc_PercentChange();
361 Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
362 Calc_ToPitch();
363 Calc_ToOctave(); // Call after Calc_ToFrequency().
364
365 m_bLoopDetect = true;
366 {
367 Update_Choice_ToPitch();
368 Update_Spin_ToOctave();
369 Update_Text_ToFrequency();
370 Update_Text_PercentChange();
371 Update_Slider_PercentChange();
372 }
373 m_bLoopDetect = false;
374
375 // If m_dSemitonesChange is a big enough negative, we can go to or below 0 freq.
376 // If m_dSemitonesChange is a big enough positive, we can go to 1.#INF (Windows) or inf (Linux).
377 // But practically, these are best limits for Soundtouch.
378 bool bIsGoodValue = (m_dSemitonesChange > -80.0) && (m_dSemitonesChange <= 60.0);
379 EffectEditor::EnableApply(mUIParent, bIsGoodValue);
380}
381
382void EffectChangePitch::OnText_FromFrequency(wxCommandEvent & WXUNUSED(evt))
383{
384 if (m_bLoopDetect)
385 return;
386
387 // Empty string causes unpredictable results with ToDouble() and later calculations.
388 // Non-positive frequency makes no sense, but user might still be editing,
389 // so it's not an error, but we do not want to update the values/controls.
390 if (!m_pTextCtrl_FromFrequency->GetValidator()->TransferFromWindow())
391 {
392 EffectEditor::EnableApply(mUIParent, false);
393 return;
394 }
395
396 double newFromMIDInote = FreqToMIDInote(m_FromFrequency);
397 m_nFromPitch = PitchIndex(newFromMIDInote);
398 m_nFromOctave = PitchOctave(newFromMIDInote);
399 Calc_ToPitch();
400 Calc_ToFrequency();
401 Calc_ToOctave(); // Call after Calc_ToFrequency().
402
403 m_bLoopDetect = true;
404 {
405 Update_Choice_FromPitch();
406 Update_Spin_FromOctave();
407 Update_Choice_ToPitch();
408 Update_Spin_ToOctave();
409 Update_Text_ToFrequency();
410 }
411 m_bLoopDetect = false;
412
413 // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
414 EffectEditor::EnableApply(mUIParent, true);
415}
416
417void EffectChangePitch::OnText_ToFrequency(wxCommandEvent & WXUNUSED(evt))
418{
419 if (m_bLoopDetect)
420 return;
421
422 // Empty string causes unpredictable results with ToDouble() and later calculations.
423 // Non-positive frequency makes no sense, but user might still be editing,
424 // so it's not an error, but we do not want to update the values/controls.
425 if (!m_pTextCtrl_ToFrequency->GetValidator()->TransferFromWindow())
426 {
427 EffectEditor::EnableApply(mUIParent, false);
428 return;
429 }
430
431 m_dPercentChange = ((m_ToFrequency * 100.0) / m_FromFrequency) - 100.0;
432
433 Calc_ToOctave(); // Call after Calc_ToFrequency().
434 Calc_SemitonesChange_fromPercentChange();
435 Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
436
437 m_bLoopDetect = true;
438 {
439 Update_Choice_ToPitch();
440 Update_Spin_ToOctave();
441 Update_Text_SemitonesChange();
442 Update_Text_PercentChange();
443 Update_Slider_PercentChange();
444 }
445 m_bLoopDetect = false;
446
447 // Success. Make sure OK and Preview are disabled if percent change is out of bounds.
448 // Can happen while editing.
449 // If the value is good, might also need to re-enable because of above clause.
450 bool bIsGoodValue = (m_dPercentChange > Percentage.min) && (m_dPercentChange <= Percentage.max);
451 EffectEditor::EnableApply(mUIParent, bIsGoodValue);
452}
453
454void EffectChangePitch::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
455{
456 if (m_bLoopDetect)
457 return;
458
459 if (!m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow())
460 {
461 EffectEditor::EnableApply(mUIParent, false);
462 return;
463 }
464
465 Calc_SemitonesChange_fromPercentChange();
466 Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
467 Calc_ToFrequency();
468 Calc_ToOctave(); // Call after Calc_ToFrequency().
469
470 m_bLoopDetect = true;
471 {
472 Update_Choice_ToPitch();
473 Update_Spin_ToOctave();
474 Update_Text_SemitonesChange();
475 Update_Text_ToFrequency();
476 Update_Slider_PercentChange();
477 }
478 m_bLoopDetect = false;
479
480 // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
481 EffectEditor::EnableApply(mUIParent, true);
482}
483
484void EffectChangePitch::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
485{
486 if (m_bLoopDetect)
487 return;
488
489 m_dPercentChange = (double)(m_pSlider_PercentChange->GetValue());
490 // Warp positive values to actually go up faster & further than negatives.
491 if (m_dPercentChange > 0.0)
492 m_dPercentChange = pow(m_dPercentChange, kSliderWarp);
493
494 Calc_SemitonesChange_fromPercentChange();
495 Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
496 Calc_ToFrequency();
497 Calc_ToOctave(); // Call after Calc_ToFrequency().
498
499 m_bLoopDetect = true;
500 {
501 Update_Choice_ToPitch();
502 Update_Spin_ToOctave();
503 Update_Text_SemitonesChange();
504 Update_Text_ToFrequency();
505 Update_Text_PercentChange();
506 }
507 m_bLoopDetect = false;
508}
509
510// helper fns for controls
511
512void EffectChangePitch::Update_Choice_FromPitch()
513{
514 m_pChoice_FromPitch->SetSelection(m_nFromPitch);
515}
516
517void EffectChangePitch::Update_Spin_FromOctave()
518{
519 m_pSpin_FromOctave->SetValue(m_nFromOctave);
520}
521
522void EffectChangePitch::Update_Choice_ToPitch()
523{
524 m_pChoice_ToPitch->SetSelection(m_nToPitch);
525}
526
527void EffectChangePitch::Update_Spin_ToOctave()
528{
529 m_pSpin_ToOctave->SetValue(m_nToOctave);
530}
531
532void EffectChangePitch::Update_Text_SemitonesChange()
533{
534 m_pTextCtrl_SemitonesChange->GetValidator()->TransferToWindow();
535}
536
537void EffectChangePitch::Update_Text_FromFrequency()
538{
539 m_pTextCtrl_FromFrequency->GetValidator()->TransferToWindow();
540}
541
542void EffectChangePitch::Update_Text_ToFrequency()
543{
544 m_pTextCtrl_ToFrequency->GetValidator()->TransferToWindow();
545}
546
547void EffectChangePitch::Update_Text_PercentChange()
548{
549 m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
550}
551
552void EffectChangePitch::Update_Slider_PercentChange()
553{
554 double unwarped = m_dPercentChange;
555 if (unwarped > 0.0)
556 // Un-warp values above zero to actually go up to kSliderMax.
557 unwarped = pow(m_dPercentChange, (1.0 / kSliderWarp));
558
559 // Add 0.5 to unwarped so trunc -> round.
560 m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
561}
562
563#endif // USE_SOUNDTOUCH
564
wxT("CloseDown"))
END_EVENT_TABLE()
@ ID_PercentChange
Definition: ChangeSpeed.cpp:33
static const double kSliderMax
Definition: ChangeSpeed.cpp:49
static const double kSliderWarp
Definition: ChangeSpeed.cpp:50
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define XXC(s, c)
Definition: Internat.h:47
#define XC(s, c)
Definition: Internat.h:37
unsigned int PitchIndex(const double dMIDInote)
Definition: PitchName.cpp:34
double PitchToFreq(const unsigned int nPitchIndex, const int nPitchOctave)
Definition: PitchName.cpp:160
int PitchOctave(const double dMIDInote)
Definition: PitchName.cpp:47
double FreqToMIDInote(const double freq)
Definition: PitchName.cpp:23
TranslatableString PitchName(const double dMIDInote, const PitchNameChoice choice)
Definition: PitchName.cpp:54
#define S(N)
Definition: ToChars.cpp:64
std::vector< TranslatableString > TranslatableStrings
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
Performs effect computation.
Hold values to send to effect output meters.
Abstract base class used in importing a file.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
A Validator is an object which checks whether a wxVariant satisfies a certain criterion....
Definition: Validators.h:54
BuiltinCommandsModule::Registration< CompareAudioCommand > reg
STL namespace.
Externalized state of a plug-in.