/********************************************************************** Audacity: A Digital Audio Editor ChangeTempo.cpp Vaughan Johnson, Dominic Mazzoni *******************************************************************//** \class EffectChangeTempo \brief An EffectSoundTouch provides speeding up or slowing down tempo without changing pitch. *//****************************************************************//** \class ChangeTempoDialog \brief Dialog used with EffectChangeTempo *//*******************************************************************/ #include "../Audacity.h" // for USE_SOUNDTOUCH #if USE_SOUNDTOUCH #include "ChangeTempo.h" #include "../ShuttleGui.h" #include "TimeWarper.h" #include #include #include #include #include #include // // EffectChangeTempo // EffectChangeTempo::EffectChangeTempo() { m_PercentChange = 0.0; m_FromBPM = 0.0; // indicates not yet set m_ToBPM = 0.0; // indicates not yet set m_FromLength = 0.0; m_ToLength = 0.0; } double EffectChangeTempo::CalcPreviewInputLength(double previewLength) { return previewLength * (100.0 + m_PercentChange) / 100.0; } wxString EffectChangeTempo::GetEffectDescription() { // Note: This is useful only after change amount has been set. return wxString::Format(_("Applied effect: %s %.1f%%"), this->GetEffectName().c_str(), m_PercentChange); } bool EffectChangeTempo::Init() { // The selection might have changed since the last time EffectChangeTempo // was invoked, so recalculate the Length parameters. m_FromLength = mT1 - mT0; m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange); mSoundTouch = NULL; return true; } bool EffectChangeTempo::PromptUser() { ChangeTempoDialog dlog(this, mParent); dlog.m_PercentChange = m_PercentChange; dlog.m_FromBPM = m_FromBPM; dlog.m_ToBPM = m_ToBPM; dlog.m_FromLength = m_FromLength; dlog.m_ToLength = m_ToLength; // 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_PercentChange = dlog.m_PercentChange; m_FromBPM = dlog.m_FromBPM; m_ToBPM = dlog.m_ToBPM; m_ToLength = dlog.m_ToLength; return true; } bool EffectChangeTempo::TransferParameters( Shuttle & shuttle ) { shuttle.TransferDouble(wxT("Percentage"),m_PercentChange,0.0); return true; } bool EffectChangeTempo::Process() { mSoundTouch = new SoundTouch(); mSoundTouch->setTempoChange(m_PercentChange); double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0); SetTimeWarper(new RegionTimeWarper(mT0, mT1, new LinearTimeWarper(mT0, mT0, mT1, mT1Dashed ))); bool success = this->EffectSoundTouch::Process(); if( success ) mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.); return success; } //---------------------------------------------------------------------------- // ChangeTempoDialog //---------------------------------------------------------------------------- #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. #define ID_TEXT_PERCENTCHANGE 10001 #define ID_SLIDER_PERCENTCHANGE 10002 #define ID_TEXT_FROMBPM 10003 #define ID_TEXT_TOBPM 10004 #define ID_TEXT_FROMLENGTH 10005 #define ID_TEXT_TOLENGTH 10006 // event table for ChangeTempoDialog BEGIN_EVENT_TABLE(ChangeTempoDialog, EffectDialog) EVT_TEXT(ID_TEXT_PERCENTCHANGE, ChangeTempoDialog::OnText_PercentChange) EVT_SLIDER(ID_SLIDER_PERCENTCHANGE, ChangeTempoDialog::OnSlider_PercentChange) EVT_TEXT(ID_TEXT_FROMBPM, ChangeTempoDialog::OnText_FromBPM) EVT_TEXT(ID_TEXT_TOBPM, ChangeTempoDialog::OnText_ToBPM) EVT_TEXT(ID_TEXT_TOLENGTH, ChangeTempoDialog::OnText_ToLength) EVT_BUTTON(ID_EFFECT_PREVIEW, ChangeTempoDialog::OnPreview) END_EVENT_TABLE() ChangeTempoDialog::ChangeTempoDialog(EffectChangeTempo *effect, wxWindow *parent) : EffectDialog(parent, _("Change Tempo"), 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_pTextCtrl_PercentChange = NULL; m_pSlider_PercentChange = NULL; m_pTextCtrl_FromBPM = NULL; m_pTextCtrl_ToBPM = NULL; m_pTextCtrl_FromLength = NULL; m_pTextCtrl_ToLength = NULL; // effect parameters m_PercentChange = 0.0; m_FromBPM = 0.0; // indicates not yet set m_ToBPM = 0.0; // indicates not yet set m_FromLength = 0.0; m_ToLength = 0.0; Init(); } void ChangeTempoDialog::PopulateOrExchange(ShuttleGui & S) { wxTextValidator nullvld(wxFILTER_INCLUDE_CHAR_LIST); wxTextValidator numvld(wxFILTER_NUMERIC); S.AddSpace(0, 5); S.AddTitle(_("Change Tempo without Changing Pitch")); S.SetBorder(5); // S.StartMultiColumn(2, wxCENTER); { m_pTextCtrl_PercentChange = S.Id(ID_TEXT_PERCENTCHANGE) .AddTextBox(_("Percent Change:"), wxT(""), 12); m_pTextCtrl_PercentChange->SetValidator(numvld); } 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(); // S.StartMultiColumn(5, wxCENTER); { // S.AddUnits(_("Beats per minute:")); m_pTextCtrl_FromBPM = S.Id(ID_TEXT_FROMBPM) .AddTextBox(_("from"), wxT(""), 12); m_pTextCtrl_FromBPM->SetName(_("From beats per minute")); m_pTextCtrl_FromBPM->SetValidator(numvld); m_pTextCtrl_ToBPM = S.Id(ID_TEXT_TOBPM) .AddTextBox(_("to"), wxT(""), 12); m_pTextCtrl_ToBPM->SetName(_("To beats per minute")); m_pTextCtrl_ToBPM->SetValidator(numvld); // S.AddUnits(_("Length (seconds):")); m_pTextCtrl_FromLength = S.Id(ID_TEXT_FROMLENGTH) .AddTextBox(_("from"), wxT(""), 12); m_pTextCtrl_FromLength->SetName(_("From length in seconds")); m_pTextCtrl_FromLength->SetValidator(nullvld); m_pTextCtrl_ToLength = S.Id(ID_TEXT_TOLENGTH) .AddTextBox(_("to"), wxT(""), 12); m_pTextCtrl_ToLength->SetName(_("To length in seconds")); m_pTextCtrl_ToLength->SetValidator(numvld); } S.EndMultiColumn(); return; } bool ChangeTempoDialog::TransferDataToWindow() { m_bLoopDetect = true; // percent change controls this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); // from/to BPM controls wxString str; if (m_pTextCtrl_FromBPM) { if (m_FromBPM != 0.0) str.Printf(wxT("%.3f"), m_FromBPM); else str = wxT(""); m_pTextCtrl_FromBPM->SetValue(str); } if (m_pTextCtrl_ToBPM) { if (m_ToBPM != 0.0) str.Printf(wxT("%.3f"), m_ToBPM); else str = wxT(""); m_pTextCtrl_ToBPM->SetValue(str); } // from/to Length controls if (m_pTextCtrl_FromLength) { str.Printf(wxT("%.2f"), m_FromLength); m_pTextCtrl_FromLength->SetValue(str); m_pTextCtrl_FromLength->Enable(false); // Disable because the value comes from the user selection. } if (m_pTextCtrl_ToLength) { str.Printf(wxT("%.2f"), m_ToLength); m_pTextCtrl_ToLength->SetValue(str); } m_bLoopDetect = false; return true; } bool ChangeTempoDialog::TransferDataFromWindow() { wxString str; // percent change controls if (m_pTextCtrl_PercentChange) { str = m_pTextCtrl_PercentChange->GetValue(); double newValue = 0; str.ToDouble(&newValue); m_PercentChange = newValue; } // Ignore Slider_PercentChange because TextCtrl_PercentChange // always tracks it & is more precise (decimal points). // from/to BPM controls double newValue; if (m_pTextCtrl_FromBPM) { str = m_pTextCtrl_FromBPM->GetValue(); str.ToDouble(&newValue); m_FromBPM = newValue; } if (m_pTextCtrl_ToBPM) { str = m_pTextCtrl_ToBPM->GetValue(); str.ToDouble(&newValue); m_ToBPM = newValue; } // from/to Length controls // Don't do m_pTextCtrl_ToLength. It's disabled. if (m_pTextCtrl_ToLength) { str = m_pTextCtrl_ToLength->GetValue(); str.ToDouble(&newValue); m_ToLength = newValue; } return true; } // handler implementations for ChangeTempoDialog void ChangeTempoDialog::OnText_PercentChange(wxCommandEvent & WXUNUSED(event)) { if (m_bLoopDetect) return; if (m_pTextCtrl_PercentChange) { wxString str = m_pTextCtrl_PercentChange->GetValue(); double newValue = 0; str.ToDouble(&newValue); m_PercentChange = newValue; m_bLoopDetect = true; this->Update_Slider_PercentChange(); this->Update_Text_ToBPM(); this->Update_Text_ToLength(); m_bLoopDetect = false; FindWindow(wxID_OK)->Enable(m_PercentChange > -100.0); } } void ChangeTempoDialog::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(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); m_bLoopDetect = true; this->Update_Text_PercentChange(); this->Update_Text_ToBPM(); this->Update_Text_ToLength(); m_bLoopDetect = false; } } void ChangeTempoDialog::OnText_FromBPM(wxCommandEvent & WXUNUSED(event)) { if (m_bLoopDetect) return; if (m_pTextCtrl_FromBPM) { wxString str = m_pTextCtrl_FromBPM->GetValue(); double newValue; str.ToDouble(&newValue); m_FromBPM = newValue; m_bLoopDetect = true; this->Update_Text_ToBPM(); m_bLoopDetect = false; } } void ChangeTempoDialog::OnText_ToBPM(wxCommandEvent & WXUNUSED(event)) { if (m_bLoopDetect) return; if (m_pTextCtrl_ToBPM) { wxString str = m_pTextCtrl_ToBPM->GetValue(); double newValue; str.ToDouble(&newValue); m_ToBPM = newValue; m_bLoopDetect = true; // If FromBPM has already been set, then there's a new percent change. if (m_FromBPM != 0.0) { m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0; this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); this->Update_Text_ToLength(); } m_bLoopDetect = false; } } void ChangeTempoDialog::OnText_ToLength(wxCommandEvent & WXUNUSED(event)) { if (m_bLoopDetect) return; if (m_pTextCtrl_ToLength) { wxString str = m_pTextCtrl_ToLength->GetValue(); double newValue = 0; str.ToDouble(&newValue); m_ToLength = newValue; m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0; m_bLoopDetect = true; this->Update_Text_PercentChange(); this->Update_Slider_PercentChange(); this->Update_Text_ToBPM(); m_bLoopDetect = false; } } void ChangeTempoDialog::OnPreview(wxCommandEvent & WXUNUSED(event)) { TransferDataFromWindow(); // Save & restore parameters around Preview, because we didn't do OK. double oldPercentChange = mEffect->m_PercentChange; if( m_PercentChange < -99.0) { m_PercentChange = -99.0; this->Update_Text_PercentChange(); } mEffect->m_PercentChange = m_PercentChange; mEffect->Preview(); mEffect->m_PercentChange = oldPercentChange; } // helper fns void ChangeTempoDialog::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 ChangeTempoDialog::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)); } } void ChangeTempoDialog::Update_Text_ToBPM() // Use m_FromBPM & m_PercentChange to set new m_ToBPM & control. { // Update ToBPM iff FromBPM has been set. if (m_FromBPM == 0.0) return; m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0)); if (m_pTextCtrl_ToBPM) { wxString str; str.Printf(wxT("%.3f"), m_ToBPM); m_pTextCtrl_ToBPM->SetValue(str); } } void ChangeTempoDialog::Update_Text_ToLength() // Use m_FromLength & m_PercentChange to set new m_ToLength & control. { m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange); if (m_pTextCtrl_ToLength) { wxString str; str.Printf(wxT("%.2f"), m_ToLength); m_pTextCtrl_ToLength->SetValue(str); } } #endif // USE_SOUNDTOUCH