audacia/src/effects/ChangeTempo.cpp

497 lines
13 KiB
C++
Raw Normal View History

/**********************************************************************
Audacity: A Digital Audio Editor
ChangeTempo.cpp
2014-06-03 20:30:19 +00:00
Vaughan Johnson,
Dominic Mazzoni
*******************************************************************//**
\class EffectChangeTempo
2014-06-03 20:30:19 +00:00
\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 <math.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/valtext.h>
//
// 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;
}
2013-02-15 00:24:43 +00:00
double EffectChangeTempo::CalcPreviewInputLength(double previewLength)
{
return previewLength * (100.0 + m_PercentChange) / 100.0;
}
2014-06-03 20:30:19 +00:00
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);
2014-06-03 20:30:19 +00:00
}
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 )
2014-06-03 20:30:19 +00:00
{
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();
2014-06-03 20:30:19 +00:00
//
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();
}
2014-06-03 20:30:19 +00:00
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();
2014-06-03 20:30:19 +00:00
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));
}
}
2014-06-03 20:30:19 +00:00
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);
}
}
2014-06-03 20:30:19 +00:00
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