Audacity  2.2.2
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. See License.txt.
6 
7  ChangePitch.cpp
8  Vaughan Johnson, Dominic Mazzoni, Steve Daulton
9 
10 ******************************************************************//*******************************************************************/
17 
18 #include "../Audacity.h" // for USE_SOUNDTOUCH
19 
20 #if USE_SOUNDTOUCH
21 #include "ChangePitch.h"
22 
23 #if USE_SBSMS
24 #include <wx/valgen.h>
25 #endif
26 
27 #include <float.h>
28 #include <math.h>
29 
30 #include <wx/intl.h>
31 #include <wx/valtext.h>
32 
33 #include "../PitchName.h"
34 #include "../ShuttleGui.h"
35 #include "../Spectrum.h"
36 #include "../WaveTrack.h"
37 #include "../widgets/valnum.h"
38 #include "TimeWarper.h"
39 
40 enum {
41  ID_PercentChange = 10000,
42  ID_FromPitch,
43  ID_FromOctave,
44  ID_ToPitch,
45  ID_ToOctave,
46  ID_SemitonesChange,
47  ID_FromFrequency,
48  ID_ToFrequency
49 };
50 
51 // Soundtouch is not reasonable below -99% or above 3000%.
52 
53 // Define keys, defaults, minimums, and maximums for the effect parameters
54 //
55 // Name Type Key Def Min Max Scale
56 Param( Percentage, double, wxT("Percentage"), 0.0, -99.0, 3000.0, 1 );
57 Param( UseSBSMS, bool, wxT("SBSMS"), false, false, true, 1 );
58 
59 // We warp the slider to go up to 400%, but user can enter up to 3000%
60 static const double kSliderMax = 100.0; // warped above zero to actually go up to 400%
61 static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400.
62 
63 // EffectChangePitch
64 
65 BEGIN_EVENT_TABLE(EffectChangePitch, wxEvtHandler)
66  EVT_CHOICE(ID_FromPitch, EffectChangePitch::OnChoice_FromPitch)
67  EVT_TEXT(ID_FromOctave, EffectChangePitch::OnSpin_FromOctave)
68  EVT_CHOICE(ID_ToPitch, EffectChangePitch::OnChoice_ToPitch)
69  EVT_TEXT(ID_ToOctave, EffectChangePitch::OnSpin_ToOctave)
70 
71  EVT_TEXT(ID_SemitonesChange, EffectChangePitch::OnText_SemitonesChange)
72 
73  EVT_TEXT(ID_FromFrequency, EffectChangePitch::OnText_FromFrequency)
74  EVT_TEXT(ID_ToFrequency, EffectChangePitch::OnText_ToFrequency)
75 
76  EVT_TEXT(ID_PercentChange, EffectChangePitch::OnText_PercentChange)
77  EVT_SLIDER(ID_PercentChange, EffectChangePitch::OnSlider_PercentChange)
79 
80 EffectChangePitch::EffectChangePitch()
81 {
82  m_dPercentChange = DEF_Percentage;
83  m_dSemitonesChange = 0.0;
84  m_dStartFrequency = 0.0; // 0.0 => uninitialized
85  m_bLoopDetect = false;
86 
87 #if USE_SBSMS
88  mUseSBSMS = DEF_UseSBSMS;
89 #else
90  mUseSBSMS = false;
91 #endif
92 
93  // NULL out these control members because there are some cases where the
94  // event table handlers get called during this method, and those handlers that
95  // can cause trouble check for NULL.
96  m_pChoice_FromPitch = NULL;
97  m_pSpin_FromOctave = NULL;
98  m_pChoice_ToPitch = NULL;
99  m_pSpin_ToOctave = NULL;
100 
101  m_pTextCtrl_SemitonesChange = NULL;
102 
103  m_pTextCtrl_FromFrequency = NULL;
104  m_pTextCtrl_ToFrequency = NULL;
105 
106  m_pTextCtrl_PercentChange = NULL;
107  m_pSlider_PercentChange = NULL;
108 
109  SetLinearEffectFlag(true);
110 }
111 
112 EffectChangePitch::~EffectChangePitch()
113 {
114 }
115 
116 // IdentInterface implementation
117 
118 IdentInterfaceSymbol EffectChangePitch::GetSymbol()
119 {
120  return CHANGEPITCH_PLUGIN_SYMBOL;
121 }
122 
123 wxString EffectChangePitch::GetDescription()
124 {
125  return _("Changes the pitch of a track without changing its tempo");
126 }
127 
128 wxString EffectChangePitch::ManualPage()
129 {
130  return wxT("Change_Pitch");
131 }
132 
133 // EffectDefinitionInterface implementation
134 
135 EffectType EffectChangePitch::GetType()
136 {
137  return EffectTypeProcess;
138 }
139 
140 // EffectClientInterface implementation
141 bool EffectChangePitch::DefineParams( ShuttleParams & S ){
142  S.SHUTTLE_PARAM( m_dPercentChange, Percentage );
143  S.SHUTTLE_PARAM( mUseSBSMS, UseSBSMS );
144  return true;
145 }
146 
147 bool EffectChangePitch::GetAutomationParameters(CommandParameters & parms)
148 {
149  parms.Write(KEY_Percentage, m_dPercentChange);
150  parms.Write(KEY_UseSBSMS, mUseSBSMS);
151 
152  return true;
153 }
154 
155 bool EffectChangePitch::SetAutomationParameters(CommandParameters & parms)
156 {
157  // Vaughan, 2013-06: Long lost to history, I don't see why m_dPercentChange was chosen to be shuttled.
158  // Only m_dSemitonesChange is used in Process().
159  ReadAndVerifyDouble(Percentage);
160 
161  m_dPercentChange = Percentage;
162  Calc_SemitonesChange_fromPercentChange();
163 
164 #if USE_SBSMS
165  ReadAndVerifyBool(UseSBSMS);
166  mUseSBSMS = UseSBSMS;
167 #else
168  mUseSBSMS = false;
169 #endif
170 
171  return true;
172 }
173 
174 bool EffectChangePitch::LoadFactoryDefaults()
175 {
176  DeduceFrequencies();
177 
179 }
180 
181 // Effect implementation
182 
183 bool EffectChangePitch::Init()
184 {
185  mSoundTouch.reset();
186  return true;
187 }
188 
189 bool EffectChangePitch::Process()
190 {
191 #if USE_SBSMS
192  if (mUseSBSMS)
193  {
194  double pitchRatio = 1.0 + m_dPercentChange / 100.0;
195  EffectSBSMS proxy;
196  proxy.mProxyEffectName = XO("High Quality Pitch Change");
197  proxy.setParameters(1.0, pitchRatio);
198 
199  return Delegate(proxy, mUIParent, false);
200  }
201  else
202 #endif
203  {
204  mSoundTouch = std::make_unique<SoundTouch>();
205  IdentityTimeWarper warper;
206  mSoundTouch->setPitchSemiTones((float)(m_dSemitonesChange));
207 #ifdef USE_MIDI
208  // Pitch shifting note tracks is currently only supported by SoundTouchEffect
209  // and non-real-time-preview effects require an audio track selection.
210  //
211  // Note: m_dSemitonesChange is private to ChangePitch because it only
212  // needs to pass it along to mSoundTouch (above). I added mSemitones
213  // to SoundTouchEffect (the super class) to convey this value
214  // to process Note tracks. This approach minimizes changes to existing
215  // code, but it would be cleaner to change all m_dSemitonesChange to
216  // mSemitones, make mSemitones exist with or without USE_MIDI, and
217  // eliminate the next line:
218  mSemitones = m_dSemitonesChange;
219 #endif
220  return EffectSoundTouch::ProcessWithTimeWarper(warper);
221  }
222 }
223 
224 bool EffectChangePitch::CheckWhetherSkipEffect()
225 {
226  return (m_dPercentChange == 0.0);
227 }
228 
229 void EffectChangePitch::PopulateOrExchange(ShuttleGui & S)
230 {
231  DeduceFrequencies(); // Set frequency-related control values based on sample.
232 
233  wxArrayString pitch;
234  for (int ii = 0; ii < 12; ++ii)
235  pitch.Add( PitchName( ii, PitchNameChoice::Both ) );
236 
237  S.SetBorder(5);
238 
239  S.StartVerticalLay(0);
240  {
241  S.StartVerticalLay();
242  {
243  S.AddTitle(_("Change Pitch without Changing Tempo"));
244  S.AddTitle(
245  wxString::Format(_("Estimated Start Pitch: %s%d (%.3f Hz)"),
246  pitch[m_nFromPitch], m_nFromOctave, m_FromFrequency));
247  }
248  S.EndVerticalLay();
249 
250  /* i18n-hint: (noun) Musical pitch.*/
251  S.StartStatic(_("Pitch"));
252  {
253  S.StartMultiColumn(6, wxALIGN_CENTER); // 6 controls, because each AddChoice adds a wxStaticText and a wxChoice.
254  {
255  m_pChoice_FromPitch = S.Id(ID_FromPitch).AddChoice(_("from"), wxT(""), &pitch);
256  m_pChoice_FromPitch->SetName(_("from"));
257  m_pChoice_FromPitch->SetSizeHints(80, -1);
258 
259  m_pSpin_FromOctave = S.Id(ID_FromOctave).AddSpinCtrl( {}, m_nFromOctave, INT_MAX, INT_MIN);
260  m_pSpin_FromOctave->SetName(_("from Octave"));
261  m_pSpin_FromOctave->SetSizeHints(50, -1);
262 
263  m_pChoice_ToPitch = S.Id(ID_ToPitch).AddChoice(_("to"), wxT(""), &pitch);
264  m_pChoice_ToPitch->SetName(_("to"));
265  m_pChoice_ToPitch->SetSizeHints(80, -1);
266 
267  m_pSpin_ToOctave =
268  S.Id(ID_ToOctave).AddSpinCtrl( {}, m_nToOctave, INT_MAX, INT_MIN);
269  m_pSpin_ToOctave->SetName(_("to Octave"));
270  m_pSpin_ToOctave->SetSizeHints(50, -1);
271  }
272  S.EndMultiColumn();
273 
274  S.StartHorizontalLay(wxALIGN_CENTER);
275  {
276  FloatingPointValidator<double> vldSemitones(2, &m_dSemitonesChange, NumValidatorStyle::TWO_TRAILING_ZEROES);
277  m_pTextCtrl_SemitonesChange =
278  S.Id(ID_SemitonesChange).AddTextBox(_("Semitones (half-steps):"), wxT(""), 12);
279  m_pTextCtrl_SemitonesChange->SetName(_("Semitones (half-steps)"));
280  m_pTextCtrl_SemitonesChange->SetValidator(vldSemitones);
281  }
282  S.EndHorizontalLay();
283  }
284  S.EndStatic();
285 
286  S.StartStatic(_("Frequency"));
287  {
288  S.StartMultiColumn(5, wxALIGN_CENTER); // 5, because AddTextBox adds a wxStaticText and a wxTextCtrl.
289  {
290  FloatingPointValidator<double> vldFromFrequency(3, &m_FromFrequency, NumValidatorStyle::THREE_TRAILING_ZEROES);
291  vldFromFrequency.SetMin(0.0);
292  m_pTextCtrl_FromFrequency = S.Id(ID_FromFrequency).AddTextBox(_("from"), wxT(""), 12);
293  m_pTextCtrl_FromFrequency->SetName(_("from (Hz)"));
294  m_pTextCtrl_FromFrequency->SetValidator(vldFromFrequency);
295 
296  FloatingPointValidator<double> vldToFrequency(3, &m_ToFrequency, NumValidatorStyle::THREE_TRAILING_ZEROES);
297  vldToFrequency.SetMin(0.0);
298  m_pTextCtrl_ToFrequency = S.Id(ID_ToFrequency).AddTextBox(_("to"), wxT(""), 12);
299  m_pTextCtrl_ToFrequency->SetName(_("to (Hz)"));
300  m_pTextCtrl_ToFrequency->SetValidator(vldToFrequency);
301 
302  S.AddUnits(_("Hz"));
303  }
304  S.EndMultiColumn();
305 
306  S.StartHorizontalLay(wxALIGN_CENTER);
307  {
308  FloatingPointValidator<double> vldPercentage(3, &m_dPercentChange, NumValidatorStyle::THREE_TRAILING_ZEROES);
309  vldPercentage.SetRange(MIN_Percentage, MAX_Percentage);
310  m_pTextCtrl_PercentChange = S.Id(ID_PercentChange).AddTextBox(_("Percent Change:"), wxT(""), 12);
311  m_pTextCtrl_PercentChange->SetValidator(vldPercentage);
312  }
313  S.EndHorizontalLay();
314 
315  S.StartHorizontalLay(wxEXPAND);
316  {
317  S.SetStyle(wxSL_HORIZONTAL);
318  m_pSlider_PercentChange = S.Id(ID_PercentChange)
319  .AddSlider( {}, 0, (int)kSliderMax, (int)MIN_Percentage);
320  m_pSlider_PercentChange->SetName(_("Percent Change"));
321  }
322  S.EndHorizontalLay();
323  }
324  S.EndStatic();
325 
326 #if USE_SBSMS
327  S.StartMultiColumn(2);
328  {
329  mUseSBSMSCheckBox = S.AddCheckBox(_("Use high quality stretching (slow)"),
330  mUseSBSMS? wxT("true") : wxT("false"));
331  mUseSBSMSCheckBox->SetValidator(wxGenericValidator(&mUseSBSMS));
332  }
333  S.EndMultiColumn();
334 #endif
335 
336  }
337  S.EndVerticalLay();
338 
339  return;
340 }
341 
342 bool EffectChangePitch::TransferDataToWindow()
343 {
344  m_bLoopDetect = true;
345 
346  if (!mUIParent->TransferDataToWindow())
347  {
348  return false;
349  }
350 
351  Calc_SemitonesChange_fromPercentChange();
352  Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
353  Calc_ToFrequency();
354  Calc_ToOctave(); // Call after Calc_ToFrequency().
355 
356  Update_Choice_FromPitch();
357  Update_Choice_ToPitch();
358  Update_Spin_FromOctave();
359  Update_Spin_ToOctave();
360  Update_Text_SemitonesChange();
361  Update_Text_FromFrequency();
362  Update_Text_ToFrequency();
363  Update_Text_PercentChange();
364  Update_Slider_PercentChange();
365 
366  m_bLoopDetect = false;
367 
368  return true;
369 }
370 
371 bool EffectChangePitch::TransferDataFromWindow()
372 {
373  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
374  {
375  return false;
376  }
377 
378  // from/to pitch controls
379  m_nFromPitch = m_pChoice_FromPitch->GetSelection();
380  m_nFromOctave = m_pSpin_FromOctave->GetValue();
381 
382  m_nToPitch = m_pChoice_ToPitch->GetSelection();
383 
384  // No need to update Slider_PercentChange here because TextCtrl_PercentChange
385  // always tracks it & is more precise (decimal points).
386 
387  return true;
388 }
389 
390 // EffectChangePitch implementation
391 
392 // Deduce m_FromFrequency from the samples at the beginning of
393 // the selection. Then set some other params accordingly.
394 void EffectChangePitch::DeduceFrequencies()
395 {
396  // As a neat trick, attempt to get the frequency of the note at the
397  // beginning of the selection.
398  SelectedTrackListOfKindIterator iter(Track::Wave, inputTracks());
399  WaveTrack *track = (WaveTrack *) iter.First();
400  if (track) {
401  double rate = track->GetRate();
402 
403  // Auto-size window -- high sample rates require larger windowSize.
404  // Aim for around 2048 samples at 44.1 kHz (good down to about 100 Hz).
405  // To detect single notes, analysis period should be about 0.2 seconds.
406  // windowSize must be a power of 2.
407  const size_t windowSize =
408  // windowSize < 256 too inaccurate
409  std::max(256, wxRound(pow(2.0, floor((log(rate / 20.0)/log(2.0)) + 0.5))));
410 
411  // we want about 0.2 seconds to catch the first note.
412  // number of windows rounded to nearest integer >= 1.
413  const unsigned numWindows =
414  std::max(1, wxRound((double)(rate / (5.0f * windowSize))));
415 
416  double trackStart = track->GetStartTime();
417  double t0 = mT0 < trackStart? trackStart: mT0;
418  auto start = track->TimeToLongSamples(t0);
419 
420  auto analyzeSize = windowSize * numWindows;
421  Floats buffer{ analyzeSize };
422 
423  Floats freq{ windowSize / 2 };
424  Floats freqa{ windowSize / 2, true };
425 
426  track->Get((samplePtr) buffer.get(), floatSample, start, analyzeSize);
427  for(unsigned i = 0; i < numWindows; i++) {
428  ComputeSpectrum(buffer.get() + i * windowSize, windowSize,
429  windowSize, rate, freq.get(), true);
430  for(size_t j = 0; j < windowSize / 2; j++)
431  freqa[j] += freq[j];
432  }
433  size_t argmax = 0;
434  for(size_t j = 1; j < windowSize / 2; j++)
435  if (freqa[j] > freqa[argmax])
436  argmax = j;
437 
438  auto lag = (windowSize / 2 - 1) - argmax;
439  m_dStartFrequency = rate / lag;
440  }
441 
442  double dFromMIDInote = FreqToMIDInote(m_dStartFrequency);
443  double dToMIDInote = dFromMIDInote + m_dSemitonesChange;
444  m_nFromPitch = PitchIndex(dFromMIDInote);
445  m_nFromOctave = PitchOctave(dFromMIDInote);
446  m_nToPitch = PitchIndex(dToMIDInote);
447  m_nToOctave = PitchOctave(dToMIDInote);
448 
449  m_FromFrequency = m_dStartFrequency;
450  Calc_PercentChange();
451  Calc_ToFrequency();
452 }
453 
454 // calculations
455 
456 void EffectChangePitch::Calc_ToPitch()
457 {
458  int nSemitonesChange =
459  (int)(m_dSemitonesChange + ((m_dSemitonesChange < 0.0) ? -0.5 : 0.5));
460  m_nToPitch = (m_nFromPitch + nSemitonesChange) % 12;
461  if (m_nToPitch < 0)
462  m_nToPitch += 12;
463 }
464 
465 void EffectChangePitch::Calc_ToOctave()
466 {
467  m_nToOctave = PitchOctave(FreqToMIDInote(m_ToFrequency));
468 }
469 
470 void EffectChangePitch::Calc_SemitonesChange_fromPitches()
471 {
472  m_dSemitonesChange =
473  PitchToMIDInote(m_nToPitch, m_nToOctave) - PitchToMIDInote(m_nFromPitch, m_nFromOctave);
474 }
475 
476 void EffectChangePitch::Calc_SemitonesChange_fromPercentChange()
477 {
478  // Use m_dPercentChange rather than m_FromFrequency & m_ToFrequency, because
479  // they start out uninitialized, but m_dPercentChange is always valid.
480  m_dSemitonesChange = (12.0 * log((100.0 + m_dPercentChange) / 100.0)) / log(2.0);
481 }
482 
483 void EffectChangePitch::Calc_ToFrequency()
484 {
485  m_ToFrequency = (m_FromFrequency * (100.0 + m_dPercentChange)) / 100.0;
486 }
487 
488 void EffectChangePitch::Calc_PercentChange()
489 {
490  m_dPercentChange = 100.0 * (pow(2.0, (m_dSemitonesChange / 12.0)) - 1.0);
491 }
492 
493 
494 // handlers
495 void EffectChangePitch::OnChoice_FromPitch(wxCommandEvent & WXUNUSED(evt))
496 {
497  if (m_bLoopDetect)
498  return;
499 
500  m_nFromPitch = m_pChoice_FromPitch->GetSelection();
501  m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
502 
503  Calc_ToPitch();
504  Calc_ToFrequency();
505  Calc_ToOctave(); // Call after Calc_ToFrequency().
506 
507  m_bLoopDetect = true;
508  {
509  Update_Choice_ToPitch();
510  Update_Spin_ToOctave();
511  Update_Text_FromFrequency();
512  Update_Text_ToFrequency();
513  }
514  m_bLoopDetect = false;
515 }
516 
517 void EffectChangePitch::OnSpin_FromOctave(wxCommandEvent & WXUNUSED(evt))
518 {
519  if (m_bLoopDetect)
520  return;
521 
522  m_nFromOctave = m_pSpin_FromOctave->GetValue();
523  //vvv If I change this code to not keep semitones and percent constant,
524  // will need validation code as in OnSpin_ToOctave.
525  m_FromFrequency = PitchToFreq(m_nFromPitch, m_nFromOctave);
526 
527  Calc_ToFrequency();
528  Calc_ToOctave(); // Call after Calc_ToFrequency().
529 
530  m_bLoopDetect = true;
531  {
532  Update_Spin_ToOctave();
533  Update_Text_FromFrequency();
534  Update_Text_ToFrequency();
535  }
536  m_bLoopDetect = false;
537 }
538 
539 void EffectChangePitch::OnChoice_ToPitch(wxCommandEvent & WXUNUSED(evt))
540 {
541  if (m_bLoopDetect)
542  return;
543 
544  m_nToPitch = m_pChoice_ToPitch->GetSelection();
545 
546  Calc_SemitonesChange_fromPitches();
547  Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
548  Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
549 
550  m_bLoopDetect = true;
551  {
552  Update_Text_SemitonesChange();
553  Update_Text_ToFrequency();
554  Update_Text_PercentChange();
555  Update_Slider_PercentChange();
556  }
557  m_bLoopDetect = false;
558 }
559 
560 void EffectChangePitch::OnSpin_ToOctave(wxCommandEvent & WXUNUSED(evt))
561 {
562  if (m_bLoopDetect)
563  return;
564 
565  int nNewValue = m_pSpin_ToOctave->GetValue();
566  // Validation: Rather than set a range for octave numbers, enforce a range that
567  // keeps m_dPercentChange above -99%, per Soundtouch constraints.
568  if ((nNewValue + 3) < m_nFromOctave)
569  {
570  ::wxBell();
571  m_pSpin_ToOctave->SetValue(m_nFromOctave - 3);
572  return;
573  }
574  m_nToOctave = nNewValue;
575 
576  m_ToFrequency = PitchToFreq(m_nToPitch, m_nToOctave);
577 
578  Calc_SemitonesChange_fromPitches();
579  Calc_PercentChange(); // Call *after* m_dSemitonesChange is updated.
580 
581  m_bLoopDetect = true;
582  {
583  Update_Text_SemitonesChange();
584  Update_Text_ToFrequency();
585  Update_Text_PercentChange();
586  Update_Slider_PercentChange();
587  }
588  m_bLoopDetect = false;
589 }
590 
591 void EffectChangePitch::OnText_SemitonesChange(wxCommandEvent & WXUNUSED(evt))
592 {
593  if (m_bLoopDetect)
594  return;
595 
596  if (!m_pTextCtrl_SemitonesChange->GetValidator()->TransferFromWindow())
597  {
598  EnableApply(false);
599  return;
600  }
601 
602  Calc_PercentChange();
603  Calc_ToFrequency(); // Call *after* m_dPercentChange is updated.
604  Calc_ToPitch();
605  Calc_ToOctave(); // Call after Calc_ToFrequency().
606 
607  m_bLoopDetect = true;
608  {
609  Update_Choice_ToPitch();
610  Update_Spin_ToOctave();
611  Update_Text_ToFrequency();
612  Update_Text_PercentChange();
613  Update_Slider_PercentChange();
614  }
615  m_bLoopDetect = false;
616 
617  // If m_dSemitonesChange is a big enough negative, we can go to or below 0 freq.
618  // If m_dSemitonesChange is a big enough positive, we can go to 1.#INF (Windows) or inf (Linux).
619  // But practically, these are best limits for Soundtouch.
620  bool bIsGoodValue = (m_dSemitonesChange > -80.0) && (m_dSemitonesChange <= 60.0);
621  EnableApply(bIsGoodValue);
622 }
623 
624 void EffectChangePitch::OnText_FromFrequency(wxCommandEvent & WXUNUSED(evt))
625 {
626  if (m_bLoopDetect)
627  return;
628 
629  // Empty string causes unpredictable results with ToDouble() and later calculations.
630  // Non-positive frequency makes no sense, but user might still be editing,
631  // so it's not an error, but we do not want to update the values/controls.
632  if (!m_pTextCtrl_FromFrequency->GetValidator()->TransferFromWindow())
633  {
634  EnableApply(false);
635  return;
636  }
637 
638  double newFromMIDInote = FreqToMIDInote(m_FromFrequency);
639  m_nFromPitch = PitchIndex(newFromMIDInote);
640  m_nFromOctave = PitchOctave(newFromMIDInote);
641  Calc_ToPitch();
642  Calc_ToFrequency();
643  Calc_ToOctave(); // Call after Calc_ToFrequency().
644 
645  m_bLoopDetect = true;
646  {
647  Update_Choice_FromPitch();
648  Update_Spin_FromOctave();
649  Update_Choice_ToPitch();
650  Update_Spin_ToOctave();
651  Update_Text_ToFrequency();
652  }
653  m_bLoopDetect = false;
654 
655  // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
656  EnableApply(true);
657 }
658 
659 void EffectChangePitch::OnText_ToFrequency(wxCommandEvent & WXUNUSED(evt))
660 {
661  if (m_bLoopDetect)
662  return;
663 
664  // Empty string causes unpredictable results with ToDouble() and later calculations.
665  // Non-positive frequency makes no sense, but user might still be editing,
666  // so it's not an error, but we do not want to update the values/controls.
667  if (!m_pTextCtrl_ToFrequency->GetValidator()->TransferFromWindow())
668  {
669  EnableApply(false);
670  return;
671  }
672 
673  m_dPercentChange = ((m_ToFrequency * 100.0) / m_FromFrequency) - 100.0;
674 
675  Calc_ToOctave(); // Call after Calc_ToFrequency().
676  Calc_SemitonesChange_fromPercentChange();
677  Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
678 
679  m_bLoopDetect = true;
680  {
681  Update_Choice_ToPitch();
682  Update_Spin_ToOctave();
683  Update_Text_SemitonesChange();
684  Update_Text_PercentChange();
685  Update_Slider_PercentChange();
686  }
687  m_bLoopDetect = false;
688 
689  // Success. Make sure OK and Preview are disabled if percent change is out of bounds.
690  // Can happen while editing.
691  // If the value is good, might also need to re-enable because of above clause.
692  bool bIsGoodValue = (m_dPercentChange > MIN_Percentage) && (m_dPercentChange <= MAX_Percentage);
693  EnableApply(bIsGoodValue);
694 }
695 
696 void EffectChangePitch::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
697 {
698  if (m_bLoopDetect)
699  return;
700 
701  if (!m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow())
702  {
703  EnableApply(false);
704  return;
705  }
706 
707  Calc_SemitonesChange_fromPercentChange();
708  Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
709  Calc_ToFrequency();
710  Calc_ToOctave(); // Call after Calc_ToFrequency().
711 
712  m_bLoopDetect = true;
713  {
714  Update_Choice_ToPitch();
715  Update_Spin_ToOctave();
716  Update_Text_SemitonesChange();
717  Update_Text_ToFrequency();
718  Update_Slider_PercentChange();
719  }
720  m_bLoopDetect = false;
721 
722  // Success. Make sure OK and Preview are enabled, in case we disabled above during editing.
723  EnableApply(true);
724 }
725 
726 void EffectChangePitch::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
727 {
728  if (m_bLoopDetect)
729  return;
730 
731  m_dPercentChange = (double)(m_pSlider_PercentChange->GetValue());
732  // Warp positive values to actually go up faster & further than negatives.
733  if (m_dPercentChange > 0.0)
734  m_dPercentChange = pow(m_dPercentChange, kSliderWarp);
735 
736  Calc_SemitonesChange_fromPercentChange();
737  Calc_ToPitch(); // Call *after* m_dSemitonesChange is updated.
738  Calc_ToFrequency();
739  Calc_ToOctave(); // Call after Calc_ToFrequency().
740 
741  m_bLoopDetect = true;
742  {
743  Update_Choice_ToPitch();
744  Update_Spin_ToOctave();
745  Update_Text_SemitonesChange();
746  Update_Text_ToFrequency();
747  Update_Text_PercentChange();
748  }
749  m_bLoopDetect = false;
750 }
751 
752 // helper fns for controls
753 
754 void EffectChangePitch::Update_Choice_FromPitch()
755 {
756  m_pChoice_FromPitch->SetSelection(m_nFromPitch);
757 }
758 
759 void EffectChangePitch::Update_Spin_FromOctave()
760 {
761  m_pSpin_FromOctave->SetValue(m_nFromOctave);
762 }
763 
764 void EffectChangePitch::Update_Choice_ToPitch()
765 {
766  m_pChoice_ToPitch->SetSelection(m_nToPitch);
767 }
768 
769 void EffectChangePitch::Update_Spin_ToOctave()
770 {
771  m_pSpin_ToOctave->SetValue(m_nToOctave);
772 }
773 
774 void EffectChangePitch::Update_Text_SemitonesChange()
775 {
776  m_pTextCtrl_SemitonesChange->GetValidator()->TransferToWindow();
777 }
778 
779 void EffectChangePitch::Update_Text_FromFrequency()
780 {
781  m_pTextCtrl_FromFrequency->GetValidator()->TransferToWindow();
782 }
783 
784 void EffectChangePitch::Update_Text_ToFrequency()
785 {
786  m_pTextCtrl_ToFrequency->GetValidator()->TransferToWindow();
787 }
788 
789 void EffectChangePitch::Update_Text_PercentChange()
790 {
791  m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
792 }
793 
794 void EffectChangePitch::Update_Slider_PercentChange()
795 {
796  double unwarped = m_dPercentChange;
797  if (unwarped > 0.0)
798  // Un-warp values above zero to actually go up to kSliderMax.
799  unwarped = pow(m_dPercentChange, (1.0 / kSliderWarp));
800 
801  // Add 0.5 to unwarped so trunc -> round.
802  m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
803 }
804 
805 #endif // USE_SOUNDTOUCH
806 
wxSpinCtrl * AddSpinCtrl(const wxString &Prompt, int Value, int Max, int Min)
Definition: ShuttleGui.cpp:520
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
wxString PitchName(const double dMIDInote, const PitchNameChoice choice)
Definition: PitchName.cpp:59
No change to time at all.
Definition: TimeWarper.h:68
void EndMultiColumn()
int PitchOctave(const double dMIDInote)
Definition: PitchName.cpp:52
#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
double FreqToMIDInote(const double freq)
Definition: PitchName.cpp:28
void EndHorizontalLay()
void AddUnits(const wxString &Prompt)
Left aligned text string.
Definition: ShuttleGui.cpp:260
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper, LinearInputRateSlideTimeWarper, LinearOutputRateSlideTimeWarper, LinearInputInverseRateTimeWarper, GeometricInputRateTimeWarper, GeometricOutputRateTimeWarper classes.
void EndVerticalLay()
double GetStartTime() const override
Get the time at which the first clip in the track starts.
Definition: WaveTrack.cpp:1853
static const double kSliderWarp
Definition: ChangeSpeed.cpp:71
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
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:371
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
ShuttleGui & Id(int id)
void SetStyle(int Style)
Definition: ShuttleGui.h:287
#define ReadAndVerifyDouble(name)
Definition: Effect.h:798
double PitchToFreq(const unsigned int nPitchIndex, const int nPitchOctave)
Definition: PitchName.cpp:165
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
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: WaveTrack.cpp:1843
bool LoadFactoryDefaults() override
Definition: Effect.cpp:636
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:763
#define ReadAndVerifyBool(name)
Definition: Effect.h:800
bool ComputeSpectrum(const float *data, size_t width, size_t windowSize, double WXUNUSED(rate), float *output, bool autocorrelation, int windowFunc)
Definition: Spectrum.cpp:24
double PitchToMIDInote(const unsigned int nPitchIndex, const int nPitchOctave)
Definition: PitchName.cpp:160
Change Pitch effect provides raising or lowering the pitch without changing the tempo.
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
#define Param(name, type, key, def, min, max, scale)
Definition: Effect.h:753
void SetBorder(int Border)
Definition: ShuttleGui.h:286
unsigned int PitchIndex(const double dMIDInote)
Definition: PitchName.cpp:39
wxSlider * AddSlider(const wxString &Prompt, int pos, int Max, int Min=0)
Definition: ShuttleGui.cpp:497
void StartVerticalLay(int iProp=1)