/********************************************************************** Audacity: A Digital Audio Editor ChangePitch.cpp Vaughan Johnson, Dominic Mazzoni Change Pitch effect provides raising or lowering the pitch without changing the tempo. **********************************************************************/ #include "../Audacity.h" // for USE_SOUNDTOUCH #if USE_SOUNDTOUCH #include "ChangePitch.h" #include "../ShuttleGui.h" #include "../PitchName.h" #include "../Spectrum.h" #include "../WaveTrack.h" #include "TimeWarper.h" #include #include #include #include #include #include #include #include // // EffectChangePitch // EffectChangePitch::EffectChangePitch() { m_FromPitchIndex = -1; // -1 => uninitialized m_bWantPitchDown = false; m_ToPitchIndex = -1; // -1 => uninitialized m_SemitonesChange = 0.0; m_FromFrequency = 0.0; // 0.0 => uninitialized m_ToFrequency = 0.0; // 0.0 => uninitialized m_PercentChange = 0.0; } wxString EffectChangePitch::GetEffectDescription() { // Note: This is useful only after change amount has been set. return wxString::Format(_("Applied effect: %s %.2f semitones"), this->GetEffectName().c_str(), m_SemitonesChange); } bool EffectChangePitch::Init() { mSoundTouch = NULL; return true; } // DeduceFrequencies is Dominic's extremely cool trick (Vaughan sez so!) // to set deduce m_FromFrequency from the samples at the beginning of // the selection. Then we set some other params accordingly. void EffectChangePitch::DeduceFrequencies() { // As a neat trick, attempt to get the frequency of the note at the // beginning of the selection. SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *track = (WaveTrack *) iter.First(); if (track) { const int windowSize = 1024; const int analyzeSize = 8192; const int numWindows = analyzeSize / windowSize; double trackStart = track->GetStartTime(); double t0 = mT0 < trackStart? trackStart: mT0; sampleCount start = track->TimeToLongSamples(t0); double rate = track->GetRate(); float buffer[analyzeSize]; float freq[windowSize/2]; float freqa[windowSize/2]; int i, j, argmax; int lag; for(j=0; jGet((samplePtr) buffer, floatSample, start, analyzeSize); for(i=0; i freqa[argmax]) argmax = j; lag = (windowSize/2 - 1) - argmax; m_FromFrequency = rate / lag; m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0; // Now we can set the pitch control values. m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_ToPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_ToFrequency)); } } bool EffectChangePitch::PromptUser() { this->DeduceFrequencies(); // Set frequency-related control values based on sample. ChangePitchDialog dlog(this, mParent); dlog.m_FromPitchIndex = m_FromPitchIndex; dlog.m_bWantPitchDown = m_bWantPitchDown; dlog.m_ToPitchIndex = m_ToPitchIndex; dlog.m_SemitonesChange = m_SemitonesChange; dlog.m_FromFrequency = m_FromFrequency; dlog.m_ToFrequency = m_ToFrequency; dlog.m_PercentChange = m_PercentChange; // Don't need to call TransferDataToWindow, although other // Audacity dialogs (from which I derived this one) do it, because // ShowModal calls stuff that eventually calls wxWindowBase::OnInitDialog, // which calls dlog.TransferDataToWindow(); dlog.CentreOnParent(); dlog.ShowModal(); if (dlog.GetReturnCode() == wxID_CANCEL) return false; m_FromPitchIndex = dlog.m_FromPitchIndex; m_bWantPitchDown = dlog.m_bWantPitchDown; m_ToPitchIndex = dlog.m_ToPitchIndex; m_SemitonesChange = dlog.m_SemitonesChange; m_FromFrequency = dlog.m_FromFrequency; m_ToFrequency = dlog.m_ToFrequency; m_PercentChange = dlog.m_PercentChange; return true; } bool EffectChangePitch::TransferParameters( Shuttle & shuttle ) { shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0); m_SemitonesChange = (12.0 * log((100.0 + m_PercentChange) / 100.0)) / log(2.0); return true; } bool EffectChangePitch::Process() { mSoundTouch = new SoundTouch(); SetTimeWarper(new IdentityTimeWarper()); mSoundTouch->setPitchSemiTones((float)(m_SemitonesChange)); return this->EffectSoundTouch::Process(); } //---------------------------------------------------------------------------- // ChangePitchDialog //---------------------------------------------------------------------------- #define PERCENTCHANGE_MIN -99 #define PERCENTCHANGE_MAX 100 // warped above zero to actually go up to 400% #define PERCENTCHANGE_SLIDER_WARP 1.30105 // warp power takes max from 100 to 400. enum { ID_TEXT_PERCENTCHANGE = 10001, ID_SLIDER_PERCENTCHANGE, ID_CHOICE_FROMPITCH, ID_RADIOBUTTON_PITCHUPDOWN, ID_CHOICE_TOPITCH, ID_TEXT_SEMITONESCHANGE, ID_TEXT_FROMFREQUENCY, ID_TEXT_TOFREQUENCY }; // event table for ChangePitchDialog BEGIN_EVENT_TABLE(ChangePitchDialog, EffectDialog) EVT_CHOICE(ID_CHOICE_FROMPITCH, ChangePitchDialog::OnChoice_FromPitch) EVT_RADIOBUTTON(ID_RADIOBUTTON_PITCHUPDOWN, ChangePitchDialog::OnRadioButton_PitchUpDown) EVT_CHOICE(ID_CHOICE_TOPITCH, ChangePitchDialog::OnChoice_ToPitch) EVT_TEXT(ID_TEXT_SEMITONESCHANGE, ChangePitchDialog::OnText_SemitonesChange) EVT_TEXT(ID_TEXT_FROMFREQUENCY, ChangePitchDialog::OnText_FromFrequency) EVT_TEXT(ID_TEXT_TOFREQUENCY, ChangePitchDialog::OnText_ToFrequency) EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangePitchDialog::OnText_PercentChange) EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangePitchDialog::OnSlider_PercentChange) EVT_BUTTON(ID_EFFECT_PREVIEW, ChangePitchDialog::OnPreview) END_EVENT_TABLE() ChangePitchDialog::ChangePitchDialog(EffectChangePitch *effect, wxWindow *parent) : EffectDialog(parent, _("Change Pitch"), PROCESS_EFFECT), mEffect(effect) { m_bLoopDetect = false; // NULL out these control members because there are some cases where the // event table handlers get called during this method, and those handlers that // can cause trouble check for NULL. m_pChoice_FromPitch = NULL; m_pRadioButton_PitchUp = NULL; m_pRadioButton_PitchDown = NULL; m_pChoice_ToPitch = NULL; m_pTextCtrl_SemitonesChange = NULL; m_pTextCtrl_FromFrequency = NULL; m_pTextCtrl_ToFrequency = NULL; m_pTextCtrl_PercentChange = NULL; m_pSlider_PercentChange = NULL; // effect parameters m_FromPitchIndex = -1; // -1 => uninitialized m_bWantPitchDown = false; m_ToPitchIndex = -1; // -1 => uninitialized m_SemitonesChange = 0.0; m_FromFrequency = 0.0; // 0.0 => uninitialized m_ToFrequency = 0.0; // 0.0 => uninitialized m_PercentChange = 0.0; Init(); } void ChangePitchDialog::PopulateOrExchange(ShuttleGui & S) { wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST); wxTextValidator numvld(wxFILTER_NUMERIC); wxArrayString pitch; pitch.Add(wxT("C")); pitch.Add(wxT("C#/Db")); pitch.Add(wxT("D")); pitch.Add(wxT("D#/Eb")); pitch.Add(wxT("E")); pitch.Add(wxT("F")); pitch.Add(wxT("F#/Gb")); pitch.Add(wxT("G")); pitch.Add(wxT("G#/Ab")); pitch.Add(wxT("A")); pitch.Add(wxT("A#/Bb")); pitch.Add(wxT("B")); S.SetBorder(10); S.StartHorizontalLay(wxCENTER, false); { S.AddTitle(_("Change Pitch without Changing Tempo") + wxString(wxT("\n\n")) + _("by Vaughan Johnson && Dominic Mazzoni") + wxString(wxT("\n")) + _("using SoundTouch, by Olli Parviainen")); } S.EndHorizontalLay(); S.SetBorder(5); // S.StartMultiColumn(6, wxCENTER); { // S.AddUnits(_("Pitch:")); S.StartHorizontalLay(wxALIGN_CENTER_VERTICAL); { m_pChoice_FromPitch = S.Id(ID_CHOICE_FROMPITCH) .AddChoice(_("From:"), wxT(""), &pitch); m_pChoice_FromPitch->SetName(_("From Pitch")); m_pChoice_FromPitch->SetSizeHints(100, -1); } S.EndHorizontalLay(); S.StartStatic(wxT("")); { S.StartVerticalLay(); { S.SetBorder(3); m_pRadioButton_PitchUp = S.Id(ID_RADIOBUTTON_PITCHUPDOWN) .AddRadioButton(_("Up")); m_pRadioButton_PitchDown = S.Id(ID_RADIOBUTTON_PITCHUPDOWN) .AddRadioButtonToGroup(_("Down")); S.SetBorder(5); } S.EndVerticalLay(); } S.EndStatic(); S.StartHorizontalLay(wxALIGN_CENTER_VERTICAL); { m_pChoice_ToPitch = S.Id(ID_CHOICE_TOPITCH) .AddChoice(_("To:"), wxT(""), &pitch); m_pChoice_ToPitch->SetName(_("To Pitch")); m_pChoice_ToPitch->SetSizeHints(100, -1); } S.EndHorizontalLay(); } S.EndMultiColumn(); // S.StartMultiColumn(2, wxCENTER); { // S.AddPrompt(_("Semitones (half-steps):")); S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, false); { m_pTextCtrl_SemitonesChange = S.Id(ID_TEXT_SEMITONESCHANGE) .AddTextBox(wxT(""), wxT(""), 12); m_pTextCtrl_SemitonesChange->SetName(_("Semitones in half-steps")); m_pTextCtrl_SemitonesChange->SetValidator(numvld); } S.EndHorizontalLay(); // S.AddPrompt(_("Frequency (Hz):")); S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, false); { m_pTextCtrl_FromFrequency = S.Id(ID_TEXT_FROMFREQUENCY) .AddTextBox(_("from"), wxT(""), 12); m_pTextCtrl_FromFrequency->SetName(_("From frequency in hertz")); m_pTextCtrl_FromFrequency->SetValidator(nullvld); m_pTextCtrl_ToFrequency = S.Id(ID_TEXT_TOFREQUENCY) .AddTextBox(_("to"), wxT(""), 12); m_pTextCtrl_ToFrequency->SetName(_("To frequency in seconds")); m_pTextCtrl_ToFrequency->SetValidator(numvld); } S.EndHorizontalLay(); // S.AddPrompt(_("Percent Change:")); S.StartHorizontalLay(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, false); { m_pTextCtrl_PercentChange = S.Id(ID_TEXT_PERCENTCHANGE) .AddTextBox(wxT(""), wxT(""), 12); m_pTextCtrl_PercentChange->SetName(_("Percent Change")); m_pTextCtrl_PercentChange->SetValidator(numvld); } S.EndHorizontalLay(); } S.EndMultiColumn(); // S.StartHorizontalLay(wxEXPAND); { S.SetStyle(wxSL_HORIZONTAL); m_pSlider_PercentChange = S.Id(ID_SLIDER_PERCENTCHANGE) .AddSlider(wxT(""), 0, (int)PERCENTCHANGE_MAX, (int)PERCENTCHANGE_MIN); m_pSlider_PercentChange->SetName(_("Percent Change")); } S.EndHorizontalLay(); return; } bool ChangePitchDialog::TransferDataToWindow() { m_bLoopDetect = true; // from/to pitch controls if (m_pChoice_FromPitch) m_pChoice_FromPitch->SetSelection(m_FromPitchIndex); this->Update_RadioButton_PitchUpDown(); this->Update_Choice_ToPitch(); // semitones change control this->Update_Text_SemitonesChange(); // from/to frequency controls if (m_pTextCtrl_FromFrequency) { wxString str; if (m_FromFrequency > 0.0) str.Printf(wxT("%.3f"), m_FromFrequency); else str = wxT(""); m_pTextCtrl_FromFrequency->SetValue(str); } this->Update_Text_ToFrequency(); // percent change controls this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; return true; } bool ChangePitchDialog::TransferDataFromWindow() { double newDouble; wxString str; // from/to pitch controls if (m_pChoice_FromPitch) m_FromPitchIndex = m_pChoice_FromPitch->GetSelection(); if (m_pRadioButton_PitchUp) m_bWantPitchDown = (m_pRadioButton_PitchUp->GetValue() == false); if (m_pChoice_ToPitch) m_ToPitchIndex = m_pChoice_ToPitch->GetSelection(); // semitones change control if (m_pTextCtrl_SemitonesChange) { str = m_pTextCtrl_SemitonesChange->GetValue(); str.ToDouble(&newDouble); m_SemitonesChange = newDouble; } // from/to frequency controls if (m_pTextCtrl_FromFrequency) { str = m_pTextCtrl_FromFrequency->GetValue(); str.ToDouble(&newDouble); m_FromFrequency = newDouble; } if (m_pTextCtrl_ToFrequency) { str = m_pTextCtrl_ToFrequency->GetValue(); str.ToDouble(&newDouble); m_ToFrequency = newDouble; } // percent change controls if (m_pTextCtrl_PercentChange) { str = m_pTextCtrl_PercentChange->GetValue(); str.ToDouble(&newDouble); m_PercentChange = newDouble; } // Ignore Slider_PercentChange because TextCtrl_PercentChange // always tracks it & is more precise (decimal points). return true; } // calculations void ChangePitchDialog::Calc_ToFrequency() { m_ToFrequency = (m_FromFrequency * (100.0 + m_PercentChange)) / 100.0; } void ChangePitchDialog::Calc_ToPitchIndex() { m_ToPitchIndex = (m_FromPitchIndex + (int)(m_SemitonesChange + // Round in the right direction. ((m_bWantPitchDown ? -1.0 : 1.0) * 0.5))) % 12; } void ChangePitchDialog::Calc_SemitonesChange_fromPitches() { int sign = m_bWantPitchDown ? -1 : 1; m_SemitonesChange = sign * (((sign * (m_ToPitchIndex - m_FromPitchIndex)) + 12) % 12); } void ChangePitchDialog::Calc_SemitonesChange_fromPercentChange() { // Use m_PercentChange rather than m_FromFrequency & m_ToFrequency, because // they start out uninitialized, but m_PercentChange is always valid. m_SemitonesChange = (12.0 * log((100.0 + m_PercentChange) / 100.0)) / log(2.0); } void ChangePitchDialog::Calc_PercentChange() { m_PercentChange = 100.0 * (pow(2.0, (m_SemitonesChange / 12.0)) - 1.0); } // handlers void ChangePitchDialog::OnChoice_FromPitch(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pChoice_FromPitch) { m_FromPitchIndex = m_pChoice_FromPitch->GetSelection(); this->Calc_ToPitchIndex(); m_bLoopDetect = true; this->Update_Choice_ToPitch(); m_bLoopDetect = false; } } void ChangePitchDialog::OnRadioButton_PitchUpDown(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pRadioButton_PitchUp) { m_bWantPitchDown = (m_pRadioButton_PitchUp->GetValue() == false); this->Calc_SemitonesChange_fromPitches(); this->Calc_PercentChange(); // Call *after* m_SemitonesChange is updated. this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated. m_bLoopDetect = true; this->Update_Text_SemitonesChange(); this->Update_Text_ToFrequency(); this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; } } void ChangePitchDialog::OnChoice_ToPitch(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pChoice_ToPitch) { m_ToPitchIndex = m_pChoice_ToPitch->GetSelection(); this->Calc_SemitonesChange_fromPitches(); this->Calc_PercentChange(); // Call *after* m_SemitonesChange is updated. this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated. m_bLoopDetect = true; this->Update_Text_SemitonesChange(); this->Update_Text_ToFrequency(); this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; } } void ChangePitchDialog::OnText_SemitonesChange(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pTextCtrl_SemitonesChange) { wxString str = m_pTextCtrl_SemitonesChange->GetValue(); double newValue = 0; str.ToDouble(&newValue); m_SemitonesChange = newValue; this->Calc_PercentChange(); this->Calc_ToFrequency(); // Call *after* m_PercentChange is updated. m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); this->Calc_ToPitchIndex(); // Call *after* m_bWantPitchDown is updated. m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); if (m_pTextCtrl_SemitonesChange->IsModified()) // See note at implementation of Update_RadioButton_PitchUpDown. m_pTextCtrl_SemitonesChange->SetFocus(); this->Update_Choice_ToPitch(); this->Update_Text_ToFrequency(); this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; } } void ChangePitchDialog::OnText_FromFrequency(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pTextCtrl_FromFrequency) { wxString str = m_pTextCtrl_FromFrequency->GetValue(); double newDouble; str.ToDouble(&newDouble); m_FromFrequency = newDouble; m_FromPitchIndex = PitchIndex(FreqToMIDInoteNumber(m_FromFrequency)); this->Calc_ToFrequency(); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); this->Calc_ToPitchIndex(); // Call *after* m_bWantPitchDown is updated. m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); if (m_pTextCtrl_FromFrequency->IsModified()) // See note at implementation of Update_RadioButton_PitchUpDown. m_pTextCtrl_FromFrequency->SetFocus(); this->Update_Choice_ToPitch(); this->Update_Text_ToFrequency(); m_bLoopDetect = false; } } void ChangePitchDialog::OnText_ToFrequency(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pTextCtrl_ToFrequency) { wxString str = m_pTextCtrl_ToFrequency->GetValue(); double newDouble; str.ToDouble(&newDouble); m_ToFrequency = newDouble; m_PercentChange = (((double)(m_ToFrequency) * 100.0) / (double)(m_FromFrequency)) - 100.0; this->Calc_SemitonesChange_fromPercentChange(); this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); if (m_pTextCtrl_ToFrequency->IsModified()) // See note at implementation of Update_RadioButton_PitchUpDown. m_pTextCtrl_ToFrequency->SetFocus(); this->Update_Choice_ToPitch(); this->Update_Text_SemitonesChange(); this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; } } void ChangePitchDialog::OnText_PercentChange(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pTextCtrl_PercentChange) { wxString str = m_pTextCtrl_PercentChange->GetValue(); double newValue = 0; str.ToDouble(&newValue); m_PercentChange = newValue; this->Calc_SemitonesChange_fromPercentChange(); this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. this->Calc_ToFrequency(); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); if (m_pTextCtrl_PercentChange->IsModified()) // See note at implementation of Update_RadioButton_PitchUpDown. m_pTextCtrl_PercentChange->SetFocus(); this->Update_Choice_ToPitch(); this->Update_Text_SemitonesChange(); this->Update_Text_ToFrequency(); this->Update_Slider_PercentChange(); m_bLoopDetect = false; //v Probably better to override wxTextValidator to disallow negative values. // See comment in ChangePitchDialog::ChangePitchDialog. this->FindWindow(wxID_OK)->Enable(m_PercentChange > -100.0); } } void ChangePitchDialog::OnSlider_PercentChange(wxCommandEvent & event) { if (m_bLoopDetect) return; if (m_pSlider_PercentChange) { m_PercentChange = (double)(m_pSlider_PercentChange->GetValue()); // Warp positive values to actually go up faster & further than negatives. if (m_PercentChange > 0.0) m_PercentChange = pow(m_PercentChange, PERCENTCHANGE_SLIDER_WARP); this->Calc_SemitonesChange_fromPercentChange(); this->Calc_ToPitchIndex(); // Call *after* m_SemitonesChange is updated. this->Calc_ToFrequency(); m_bWantPitchDown = (m_ToFrequency < m_FromFrequency); m_bLoopDetect = true; this->Update_RadioButton_PitchUpDown(); this->Update_Choice_ToPitch(); this->Update_Text_SemitonesChange(); this->Update_Text_ToFrequency(); this->Update_Text_PercentChange(); m_bLoopDetect = false; } } void ChangePitchDialog::OnPreview(wxCommandEvent &event) { TransferDataFromWindow(); // Save & restore parameters around Preview, because we didn't do OK. double oldSemitonesChange = m_SemitonesChange; if( m_PercentChange < -99.0) { m_PercentChange = -99.0; this->Update_Text_PercentChange(); } mEffect->m_SemitonesChange = m_SemitonesChange; mEffect->Preview(); mEffect->m_SemitonesChange = oldSemitonesChange; } // helper fns // NOTE: wxWidgets ref (C:\wxWidgets_2.4.0\docs\htmlhelp) says // wxRadioButton::SetSelection "does not cause a // wxEVT_COMMAND_RadioButton_SELECTED event to get emitted", but it // calls SetFocus, which sure as heck DOES select the radio button. // // So, any wxTextCtrl handler that calls Update_RadioButton_PitchUpDown // needs to call wxTextCtrl::SetFocus afterward, to return the // focus to the wxTextCtrl so the user can keep typing. // // Also, it turns out the wxTextCtrl handlers are sometimes // called before the dialog is displayed, so those SetFocus calls // need to be conditionalized on wxTextCtrl::IsModified. void ChangePitchDialog::Update_RadioButton_PitchUpDown() { if (m_pRadioButton_PitchUp) { m_pRadioButton_PitchUp->SetValue(m_bWantPitchDown == false); m_pRadioButton_PitchDown->SetValue(m_bWantPitchDown == true); } } void ChangePitchDialog::Update_Choice_ToPitch() { if (m_pChoice_ToPitch) m_pChoice_ToPitch->SetSelection(m_ToPitchIndex); } void ChangePitchDialog::Update_Text_SemitonesChange() { if (m_pTextCtrl_SemitonesChange) { wxString str; str.Printf(wxT("%.2f"), m_SemitonesChange); m_pTextCtrl_SemitonesChange->SetValue(str); } } void ChangePitchDialog::Update_Text_ToFrequency() { if (m_pTextCtrl_ToFrequency) { wxString str; if (m_ToFrequency > 0.0) str.Printf(wxT("%.3f"), m_ToFrequency); else str = wxT(""); m_pTextCtrl_ToFrequency->SetValue(str); } } void ChangePitchDialog::Update_Text_PercentChange() { if (m_pTextCtrl_PercentChange) { wxString str; str.Printf(wxT("%.3f"), m_PercentChange); m_pTextCtrl_PercentChange->SetValue(str); FindWindow(wxID_OK)->Enable(m_PercentChange > -100.0); } } void ChangePitchDialog::Update_Slider_PercentChange() { if (m_pSlider_PercentChange) { double unwarped = m_PercentChange; if (unwarped > 0.0) // Un-warp values above zero to actually go up to PERCENTCHANGE_MAX. unwarped = pow(m_PercentChange, (1.0 / PERCENTCHANGE_SLIDER_WARP)); // Add 0.5 to unwarped so trunc -> round. m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5)); } } #endif // USE_SOUNDTOUCH // Indentation settings for Vim and Emacs and unique identifier for Arch, a // version control system. Please do not modify past this point. // // Local Variables: // c-basic-offset: 3 // indent-tabs-mode: nil // End: // // vim: et sts=3 sw=3 // arch-tag: 0b070f91-579c-4b57-bc29-82ceb6775355