audacia/src/effects/ChangeTempo.cpp
Paul Licameli 4d09705a73 Change XO to XXO in many more places, with no effects at all...
... because the two macros have the same expansion, and are both checked for
in the --keyword arguments passed to msgfmt by locale/update_po_files.sh.

This commit makes ONLY such changes, and comments in Internat.h.  It is big
but quite harmless.

The intention is to introduce a type distinction in a later release, by defining
XXO differently.  XXO is used where & characters in strings (for hotkeys of menu
items or control prompts) are permitted, XO where not.
2020-05-22 13:07:50 -04:00

496 lines
13 KiB
C++

/**********************************************************************
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.
*//*******************************************************************/
#include "../Audacity.h" // for USE_SOUNDTOUCH
#if USE_SOUNDTOUCH
#include "ChangeTempo.h"
#if USE_SBSMS
#include "../../../lib-src/header-substitutes/sbsms.h"
#include <wx/valgen.h>
#endif
#include <math.h>
#include <wx/intl.h>
#include <wx/checkbox.h>
#include <wx/slider.h>
#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()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectChangeTempo::GetSymbol()
{
return Symbol;
}
TranslatableString EffectChangeTempo::GetDescription()
{
return XO("Changes the tempo of a selection without changing its pitch");
}
wxString EffectChangeTempo::ManualPage()
{
return wxT("Change_Tempo");
}
// EffectDefinitionInterface implementation
EffectType EffectChangeTempo::GetType()
{
return EffectTypeProcess;
}
bool EffectChangeTempo::SupportsAutomation()
{
return true;
}
// EffectClientInterface implementation
bool EffectChangeTempo::DefineParams( ShuttleParams & S ){
S.SHUTTLE_PARAM( m_PercentChange, Percentage );
S.SHUTTLE_PARAM( mUseSBSMS, UseSBSMS );
return true;
}
bool EffectChangeTempo::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_Percentage, m_PercentChange);
parms.Write(KEY_UseSBSMS, mUseSBSMS);
return true;
}
bool EffectChangeTempo::SetAutomationParameters(CommandParameters & parms)
{
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);
mSoundTouch.reset();
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
{
mSoundTouch = std::make_unique<soundtouch::SoundTouch>();
mSoundTouch->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 ) };
success = EffectSoundTouch::ProcessWithTimeWarper(warper);
}
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)
.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 a quantity "from" one value "to" another */
.Name(XO("Beats per minute, from"))
.Validator<FloatingPointValidator<double>>(
3, &m_FromBPM,
NumValidatorStyle::THREE_TRAILING_ZEROES
| NumValidatorStyle::ZERO_AS_BLANK)
/* i18n-hint: changing a quantity "from" one value "to" another */
.AddTextBox(XXO("&from"), wxT(""), 12);
m_pTextCtrl_ToBPM = S.Id(ID_ToBPM)
/* i18n-hint: changing a quantity "from" one value "to" another */
.Name(XO("Beats per minute, to"))
.Validator<FloatingPointValidator<double>>(
3, &m_ToBPM,
NumValidatorStyle::THREE_TRAILING_ZEROES
| NumValidatorStyle::ZERO_AS_BLANK)
/* i18n-hint: changing a quantity "from" one value "to" another */
.AddTextBox(XXO("&to"), 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.
.Validator<FloatingPointValidator<double>>(
precision, &m_FromLength,
NumValidatorStyle::TWO_TRAILING_ZEROES)
/* i18n-hint: changing a quantity "from" one value "to" another */
.AddTextBox(XXO("from"), wxT(""), 12);
m_pTextCtrl_ToLength = S.Id(ID_ToLength)
.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) ),
RoundValue( precision,
(m_FromLength * 100.0) / (100.0 + MIN_Percentage) )
)
/* i18n-hint: changing a quantity "from" one value "to" another */
.AddTextBox(XXO("t&o"), wxT(""), 12);
}
S.EndHorizontalLay();
}
S.EndStatic();
#if USE_SBSMS
S.StartMultiColumn(2);
{
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();
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();
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