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