audacia/src/effects/ChangeTempo.cpp

495 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.
*//*******************************************************************/
#if USE_SOUNDTOUCH
#include "ChangeTempo.h"
#if USE_SBSMS
#include <wx/valgen.h>
#endif
#include <math.h>
#include <wx/intl.h>
2018-11-11 22:30:55 +00:00
#include <wx/checkbox.h>
2018-11-11 21:18:23 +00:00
#include <wx/slider.h>
2019-02-06 18:44:52 +00:00
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../widgets/valnum.h"
#include "TimeWarper.h"
#include "LoadEffects.h"
// Soundtouch defines these as well, which are also in generated configmac.h
// and configunix.h, so get rid of them before including,
// to avoid compiler warnings, and be sure to do this
// after all other #includes, to avoid any mischief that might result
// from doing the un-definitions before seeing any wx headers.
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#undef PACKAGE_BUGREPORT
#undef PACKAGE
#undef VERSION
#include "SoundTouch.h"
enum
{
ID_PercentChange = 10000,
ID_FromBPM,
ID_ToBPM,
ID_FromLength,
ID_ToLength
};
// Soundtouch is not reasonable below -99% or above 3000%.
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
Param( Percentage, double, wxT("Percentage"), 0.0, -95.0, 3000.0, 1 );
Param( UseSBSMS, bool, wxT("SBSMS"), false, false, true, 1 );
// We warp the slider to go up to 400%, but user can enter higher values.
static const double kSliderMax = 100.0; // warped above zero to actually go up to 400%
static const double kSliderWarp = 1.30105; // warp power takes max from 100 to 400.
//
// EffectChangeTempo
//
const ComponentInterfaceSymbol EffectChangeTempo::Symbol
{ XO("Change Tempo") };
namespace{ BuiltinEffectsModule::Registration< EffectChangeTempo > reg; }
BEGIN_EVENT_TABLE(EffectChangeTempo, wxEvtHandler)
EVT_TEXT(ID_PercentChange, EffectChangeTempo::OnText_PercentChange)
EVT_SLIDER(ID_PercentChange, EffectChangeTempo::OnSlider_PercentChange)
EVT_TEXT(ID_FromBPM, EffectChangeTempo::OnText_FromBPM)
EVT_TEXT(ID_ToBPM, EffectChangeTempo::OnText_ToBPM)
EVT_TEXT(ID_ToLength, EffectChangeTempo::OnText_ToLength)
END_EVENT_TABLE()
EffectChangeTempo::EffectChangeTempo()
{
m_PercentChange = DEF_Percentage;
m_FromBPM = 0.0; // indicates not yet set
m_ToBPM = 0.0; // indicates not yet set
m_FromLength = 0.0;
m_ToLength = 0.0;
m_bLoopDetect = false;
#if USE_SBSMS
mUseSBSMS = DEF_UseSBSMS;
#else
mUseSBSMS = false;
#endif
SetLinearEffectFlag(true);
}
EffectChangeTempo::~EffectChangeTempo()
2013-02-15 00:24:43 +00:00
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectChangeTempo::GetSymbol()
{
return Symbol;
2014-06-03 20:30:19 +00:00
}
TranslatableString EffectChangeTempo::GetDescription()
{
return XO("Changes the tempo of a selection without changing its pitch");
}
ManualPageID EffectChangeTempo::ManualPage()
{
return L"Change_Tempo";
}
Automation: AudacityCommand This is a squash of 50 commits. This merges the capabilities of BatchCommands and Effects using a new AudacityCommand class. AudacityCommand provides one function to specify the parameters, and then we leverage that one function in automation, whether by chains, mod-script-pipe or (future) Nyquist. - Now have AudacityCommand which is using the same mechanism as Effect - Has configurable parameters - Has data-entry GUI (built using shuttle GUI) - Registers with PluginManager. - Menu commands now provided in chains, and to python batch. - Tested with Zoom Toggle. - ShuttleParams now can set, get, set defaults, validate and specify the parameters. - Bugfix: Don't overwrite values with defaults first time out. - Add DefineParams function for all built-in effects. - Extend CommandContext to carry output channels for results. We abuse EffectsManager. It handles both Effects and AudacityCommands now. In time an Effect should become a special case of AudacityCommand and we'll split and rename the EffectManager class. - Don't use 'default' as a parameter name. - Massive renaming for CommandDefinitionInterface - EffectIdentInterface becomes EffectDefinitionInterface - EffectAutomationParameters becomes CommandAutomationParameters - PluginType is now a bit field. This way we can search for related types at the same time. - Most old batch commands made into AudacityCommands. The ones that weren't are for a reason. They are used by mod-script-pipe to carry commands and responses across from a non-GUI thread to the GUI thread. - Major tidy up of ScreenshotCommand - Reworking of SelectCommand - GetPreferenceCommand and SetPreferenceCommand - GetTrackInfo and SetTrackInfo - GetInfoCommand - Help, Open, Save, Import and Export commands. - Removed obsolete commands ExecMenu, GetProjectInfo and SetProjectInfo which are now better handled by other commands. - JSONify "GetInfo: Commands" output, i.e. commas in the right places. - General work on better Doxygen. - Lyrics -> LyricsPanel - Meter -> MeterPanel - Updated Linux makefile. - Scripting commands added into Extra menu. - Distinct names for previously duplicated find-clipping parameters. - Fixed longstanding error with erroneous status field number which previously caused an ASSERT in debug. - Sensible formatting of numbers in Chains, 0.1 not 0.1000000000137
2018-01-14 18:51:41 +00:00
// EffectDefinitionInterface implementation
EffectType EffectChangeTempo::GetType()
{
return EffectTypeProcess;
}
bool EffectChangeTempo::SupportsAutomation()
{
return true;
}
// EffectClientInterface implementation
Automation: AudacityCommand This is a squash of 50 commits. This merges the capabilities of BatchCommands and Effects using a new AudacityCommand class. AudacityCommand provides one function to specify the parameters, and then we leverage that one function in automation, whether by chains, mod-script-pipe or (future) Nyquist. - Now have AudacityCommand which is using the same mechanism as Effect - Has configurable parameters - Has data-entry GUI (built using shuttle GUI) - Registers with PluginManager. - Menu commands now provided in chains, and to python batch. - Tested with Zoom Toggle. - ShuttleParams now can set, get, set defaults, validate and specify the parameters. - Bugfix: Don't overwrite values with defaults first time out. - Add DefineParams function for all built-in effects. - Extend CommandContext to carry output channels for results. We abuse EffectsManager. It handles both Effects and AudacityCommands now. In time an Effect should become a special case of AudacityCommand and we'll split and rename the EffectManager class. - Don't use 'default' as a parameter name. - Massive renaming for CommandDefinitionInterface - EffectIdentInterface becomes EffectDefinitionInterface - EffectAutomationParameters becomes CommandAutomationParameters - PluginType is now a bit field. This way we can search for related types at the same time. - Most old batch commands made into AudacityCommands. The ones that weren't are for a reason. They are used by mod-script-pipe to carry commands and responses across from a non-GUI thread to the GUI thread. - Major tidy up of ScreenshotCommand - Reworking of SelectCommand - GetPreferenceCommand and SetPreferenceCommand - GetTrackInfo and SetTrackInfo - GetInfoCommand - Help, Open, Save, Import and Export commands. - Removed obsolete commands ExecMenu, GetProjectInfo and SetProjectInfo which are now better handled by other commands. - JSONify "GetInfo: Commands" output, i.e. commas in the right places. - General work on better Doxygen. - Lyrics -> LyricsPanel - Meter -> MeterPanel - Updated Linux makefile. - Scripting commands added into Extra menu. - Distinct names for previously duplicated find-clipping parameters. - Fixed longstanding error with erroneous status field number which previously caused an ASSERT in debug. - Sensible formatting of numbers in Chains, 0.1 not 0.1000000000137
2018-01-14 18:51:41 +00:00
bool EffectChangeTempo::DefineParams( ShuttleParams & S ){
S.SHUTTLE_PARAM( m_PercentChange, Percentage );
S.SHUTTLE_PARAM( mUseSBSMS, UseSBSMS );
return true;
}
2018-02-21 14:24:25 +00:00
bool EffectChangeTempo::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_Percentage, m_PercentChange);
parms.Write(KEY_UseSBSMS, mUseSBSMS);
return true;
}
2018-02-21 14:24:25 +00:00
bool EffectChangeTempo::SetAutomationParameters(CommandParameters & parms)
2014-06-03 20:30:19 +00:00
{
ReadAndVerifyDouble(Percentage);
m_PercentChange = Percentage;
#if USE_SBSMS
ReadAndVerifyBool(UseSBSMS);
mUseSBSMS = UseSBSMS;
#else
mUseSBSMS = false;
#endif
return true;
}
// Effect implementation
double EffectChangeTempo::CalcPreviewInputLength(double previewLength)
{
return previewLength * (100.0 + m_PercentChange) / 100.0;
}
bool EffectChangeTempo::CheckWhetherSkipEffect()
{
return (m_PercentChange == 0.0);
}
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);
return true;
}
bool EffectChangeTempo::Process()
{
bool success = false;
#if USE_SBSMS
if (mUseSBSMS)
{
double tempoRatio = 1.0 + m_PercentChange / 100.0;
EffectSBSMS proxy;
proxy.mProxyEffectName = XO("High Quality Tempo Change");
proxy.setParameters(tempoRatio, 1.0);
success = Delegate(proxy, *mUIParent, nullptr);
}
else
#endif
{
auto initer = [&](soundtouch::SoundTouch *soundtouch)
{
soundtouch->setTempoChange(m_PercentChange);
};
double mT1Dashed = mT0 + (mT1 - mT0)/(m_PercentChange/100.0 + 1.0);
RegionTimeWarper warper{ mT0, mT1,
std::make_unique<LinearTimeWarper>(mT0, mT0, mT1, mT1Dashed ) };
2020-09-18 04:22:00 +00:00
success = EffectSoundTouch::ProcessWithTimeWarper(initer, warper, false);
}
if(success)
mT1 = mT0 + (mT1 - mT0)/(m_PercentChange/100 + 1.);
return success;
}
void EffectChangeTempo::PopulateOrExchange(ShuttleGui & S)
{
enum { precision = 2 };
S.StartVerticalLay(0);
{
S.AddSpace(0, 5);
S.AddTitle(XO("Change Tempo without Changing Pitch"));
S.SetBorder(5);
//
S.StartMultiColumn(2, wxCENTER);
{
m_pTextCtrl_PercentChange = S.Id(ID_PercentChange)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_PercentChange, NumValidatorStyle::THREE_TRAILING_ZEROES,
MIN_Percentage, MAX_Percentage
)
.AddTextBox(XXO("Percent C&hange:"), wxT(""), 12);
}
S.EndMultiColumn();
//
S.StartHorizontalLay(wxEXPAND);
{
m_pSlider_PercentChange = S.Id(ID_PercentChange)
.Name(XO("Percent Change"))
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, 0, (int)kSliderMax, (int)MIN_Percentage);
}
S.EndHorizontalLay();
S.StartStatic(XO("Beats per minute"));
{
S.StartHorizontalLay(wxALIGN_CENTER);
{
m_pTextCtrl_FromBPM = S.Id(ID_FromBPM)
/* i18n-hint: changing tempo "from" one value "to" another */
.Name(XO("Beats per minute, from"))
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_FromBPM,
NumValidatorStyle::THREE_TRAILING_ZEROES
| NumValidatorStyle::ZERO_AS_BLANK)
/* i18n-hint: changing tempo "from" one value "to" another */
.AddTextBox(XXC("&from", "change tempo"), wxT(""), 12);
m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
/* i18n-hint: changing tempo "from" one value "to" another */
.Name(XO("Beats per minute, to"))
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_ToBPM,
NumValidatorStyle::THREE_TRAILING_ZEROES
| NumValidatorStyle::ZERO_AS_BLANK)
/* i18n-hint: changing tempo "from" one value "to" another */
.AddTextBox(XXC("&to", "change tempo"), wxT(""), 12);
}
S.EndHorizontalLay();
}
S.EndStatic();
//
S.StartStatic(XO("Length (seconds)"));
{
S.StartHorizontalLay(wxALIGN_CENTER);
{
m_pTextCtrl_FromLength = S.Id(ID_FromLength)
.Disable() // Disable because the value comes from the
// user selection.
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
precision, &m_FromLength,
NumValidatorStyle::TWO_TRAILING_ZEROES)
/* i18n-hint: changing tempo "from" one value "to" another */
.AddTextBox(XXC("from", "change tempo"), wxT(""), 12);
m_pTextCtrl_ToLength = S.Id(ID_ToLength)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
2, &m_ToLength, NumValidatorStyle::TWO_TRAILING_ZEROES,
// min and max need same precision as what we're validating (bug 963)
RoundValue( precision,
(m_FromLength * 100.0) / (100.0 + MAX_Percentage) ),
2017-10-30 16:23:41 +00:00
RoundValue( precision,
(m_FromLength * 100.0) / (100.0 + MIN_Percentage) )
2017-10-30 16:23:41 +00:00
)
/* i18n-hint: changing tempo "from" one value "to" another */
.AddTextBox(XXC("t&o", "change tempo"), wxT(""), 12);
}
S.EndHorizontalLay();
}
S.EndStatic();
#if USE_SBSMS
S.StartMultiColumn(2);
{
2017-10-30 16:23:41 +00:00
mUseSBSMSCheckBox = S.Validator<wxGenericValidator>(&mUseSBSMS)
.AddCheckBox(XXO("&Use high quality stretching (slow)"),
mUseSBSMS);
}
S.EndMultiColumn();
#endif
}
S.EndVerticalLay();
return;
}
bool EffectChangeTempo::TransferDataToWindow()
{
// Reset from length because it can be changed by Preview
m_FromLength = mT1 - mT0;
m_bLoopDetect = true;
if (!mUIParent->TransferDataToWindow())
{
return false;
}
// percent change controls
Update_Slider_PercentChange();
Update_Text_ToBPM();
Update_Text_ToLength();
m_bLoopDetect = false;
// Set the accessibility name here because we need m_pTextCtrl_FromLength to have had its value set
m_pTextCtrl_ToLength->SetName(
wxString::Format( _("Length in seconds from %s, to"),
m_pTextCtrl_FromLength->GetValue() ) );
return true;
}
bool EffectChangeTempo::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
return true;
}
// handler implementations for EffectChangeTempo
void EffectChangeTempo::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
{
if (m_bLoopDetect)
return;
m_pTextCtrl_PercentChange->GetValidator()->TransferFromWindow();
m_bLoopDetect = true;
Update_Slider_PercentChange();
Update_Text_ToBPM();
Update_Text_ToLength();
m_bLoopDetect = false;
}
void EffectChangeTempo::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
{
if (m_bLoopDetect)
return;
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, kSliderWarp);
m_bLoopDetect = true;
Update_Text_PercentChange();
Update_Text_ToBPM();
Update_Text_ToLength();
m_bLoopDetect = false;
}
void EffectChangeTempo::OnText_FromBPM(wxCommandEvent & WXUNUSED(evt))
{
if (m_bLoopDetect)
return;
m_pTextCtrl_FromBPM->GetValidator()->TransferFromWindow();
m_bLoopDetect = true;
Update_Text_ToBPM();
m_bLoopDetect = false;
}
void EffectChangeTempo::OnText_ToBPM(wxCommandEvent & WXUNUSED(evt))
{
if (m_bLoopDetect)
return;
m_pTextCtrl_ToBPM->GetValidator()->TransferFromWindow();
m_bLoopDetect = true;
// If FromBPM has already been set, then there's a NEW percent change.
if (m_FromBPM != 0.0 && m_ToBPM != 0.0)
{
m_PercentChange = ((m_ToBPM * 100.0) / m_FromBPM) - 100.0;
Update_Text_PercentChange();
Update_Slider_PercentChange();
2014-06-03 20:30:19 +00:00
Update_Text_ToLength();
}
m_bLoopDetect = false;
}
void EffectChangeTempo::OnText_ToLength(wxCommandEvent & WXUNUSED(evt))
{
if (m_bLoopDetect)
return;
m_pTextCtrl_ToLength->GetValidator()->TransferFromWindow();
if (m_ToLength != 0.0)
{
m_PercentChange = ((m_FromLength * 100.0) / m_ToLength) - 100.0;
}
m_bLoopDetect = true;
Update_Text_PercentChange();
Update_Slider_PercentChange();
2014-06-03 20:30:19 +00:00
Update_Text_ToBPM();
m_bLoopDetect = false;
}
// helper fns
void EffectChangeTempo::Update_Text_PercentChange()
{
m_pTextCtrl_PercentChange->GetValidator()->TransferToWindow();
}
void EffectChangeTempo::Update_Slider_PercentChange()
{
double unwarped = m_PercentChange;
if (unwarped > 0.0)
// Un-warp values above zero to actually go up to kSliderMax.
unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));
// Add 0.5 to unwarped so trunc -> round.
m_pSlider_PercentChange->SetValue((int)(unwarped + 0.5));
}
void EffectChangeTempo::Update_Text_ToBPM()
// Use m_FromBPM & m_PercentChange to set NEW m_ToBPM & control.
{
m_ToBPM = (((m_FromBPM * (100.0 + m_PercentChange)) / 100.0));
m_pTextCtrl_ToBPM->GetValidator()->TransferToWindow();
}
void EffectChangeTempo::Update_Text_ToLength()
// Use m_FromLength & m_PercentChange to set NEW m_ToLength & control.
{
m_ToLength = (m_FromLength * 100.0) / (100.0 + m_PercentChange);
m_pTextCtrl_ToLength->GetValidator()->TransferToWindow();
}
#endif // USE_SOUNDTOUCH