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