audacia/src/effects/TimeScale.cpp

469 lines
14 KiB
C++
Raw Normal View History

/**********************************************************************
Audacity: A Digital Audio Editor
TimeScale.cpp
Clayton Otey
*******************************************************************//**
2014-06-03 20:30:19 +00:00
\class EffectTimeScale
\brief An EffectTimeScale does high quality sliding time scaling/pitch shifting
*//*******************************************************************/
#if USE_SBSMS
#include "TimeScale.h"
#include "LoadEffects.h"
#include <math.h>
#include <wx/intl.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"
enum
{
ID_RatePercentChangeStart = 10000,
ID_RatePercentChangeEnd,
ID_PitchHalfStepsStart,
ID_PitchHalfStepsEnd,
ID_PitchPercentChangeStart,
ID_PitchPercentChangeEnd
};
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
Param( RatePercentStart, double, wxT("RatePercentChangeStart"), 0.0, -90.0, 500, 1 );
Param( RatePercentEnd, double, wxT("RatePercentChangeEnd"), 0.0, -90.0, 500, 1 );
Param( HalfStepsStart, double, wxT("PitchHalfStepsStart"), 0.0, -12.0, 12.0, 1 );
Param( HalfStepsEnd, double, wxT("PitchHalfStepsEnd"), 0.0, -12.0, 12.0, 1 );
Param( PitchPercentStart, double, wxT("PitchPercentChangeStart"), 0.0, -50.0, 100.0, 1 );
Param( PitchPercentEnd, double, wxT("PitchPercentChangeEnd"), 0.0, -50.0, 100.0, 1 );
//
// EffectTimeScale
//
const ComponentInterfaceSymbol EffectTimeScale::Symbol
{ wxT("Sliding Stretch"), XO("Sliding Stretch") };
namespace{ BuiltinEffectsModule::Registration< EffectTimeScale > reg; }
BEGIN_EVENT_TABLE(EffectTimeScale, wxEvtHandler)
EVT_TEXT(ID_RatePercentChangeStart, EffectTimeScale::OnText_RatePercentChangeStart)
EVT_TEXT(ID_RatePercentChangeEnd, EffectTimeScale::OnText_RatePercentChangeEnd)
EVT_TEXT(ID_PitchHalfStepsStart, EffectTimeScale::OnText_PitchHalfStepsStart)
EVT_TEXT(ID_PitchHalfStepsEnd, EffectTimeScale::OnText_PitchHalfStepsEnd)
EVT_TEXT(ID_PitchPercentChangeStart, EffectTimeScale::OnText_PitchPercentChangeStart)
EVT_TEXT(ID_PitchPercentChangeEnd, EffectTimeScale::OnText_PitchPercentChangeEnd)
EVT_SLIDER(ID_RatePercentChangeStart, EffectTimeScale::OnSlider_RatePercentChangeStart)
EVT_SLIDER(ID_RatePercentChangeEnd, EffectTimeScale::OnSlider_RatePercentChangeEnd)
END_EVENT_TABLE()
EffectTimeScale::EffectTimeScale()
{
m_RatePercentChangeStart = DEF_RatePercentStart;
m_RatePercentChangeEnd = DEF_RatePercentEnd;
m_PitchHalfStepsStart = DEF_HalfStepsStart;
m_PitchHalfStepsEnd = DEF_HalfStepsEnd;
m_PitchPercentChangeStart = DEF_PitchPercentStart;
m_PitchPercentChangeEnd = DEF_PitchPercentEnd;
2016-01-26 18:19:19 +00:00
slideTypeRate = SlideLinearOutputRate;
slideTypePitch = SlideLinearOutputRate;
bPreview = false;
previewSelectedDuration = 0.0;
SetLinearEffectFlag(true);
}
EffectTimeScale::~EffectTimeScale()
{
2014-06-03 20:30:19 +00:00
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectTimeScale::GetSymbol()
{
return Symbol;
}
TranslatableString EffectTimeScale::GetDescription()
{
return XO("Allows continuous changes to the tempo and/or pitch");
}
2014-06-03 20:30:19 +00:00
ManualPageID EffectTimeScale::ManualPage()
{
return L"Sliding_Stretch";
}
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
2014-06-03 20:30:19 +00:00
EffectType EffectTimeScale::GetType()
{
return EffectTypeProcess;
}
// 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 EffectTimeScale::DefineParams( ShuttleParams & S ){
S.SHUTTLE_PARAM( m_RatePercentChangeStart, RatePercentStart );
S.SHUTTLE_PARAM( m_RatePercentChangeEnd, RatePercentEnd );
S.SHUTTLE_PARAM( m_PitchHalfStepsStart, HalfStepsStart );
S.SHUTTLE_PARAM( m_PitchHalfStepsEnd, HalfStepsEnd );
S.SHUTTLE_PARAM( m_PitchPercentChangeStart, PitchPercentStart );
S.SHUTTLE_PARAM( m_PitchPercentChangeEnd, PitchPercentEnd );
return true;
}
2018-02-21 14:24:25 +00:00
bool EffectTimeScale::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_RatePercentStart, m_RatePercentChangeStart);
parms.Write(KEY_RatePercentEnd, m_RatePercentChangeEnd);
parms.Write(KEY_HalfStepsStart, m_PitchHalfStepsStart);
parms.Write(KEY_HalfStepsEnd, m_PitchHalfStepsEnd);
parms.Write(KEY_PitchPercentStart, m_PitchPercentChangeStart);
parms.Write(KEY_PitchPercentEnd, m_PitchPercentChangeEnd);
return true;
}
2018-02-21 14:24:25 +00:00
bool EffectTimeScale::SetAutomationParameters(CommandParameters & parms)
{
ReadAndVerifyDouble(RatePercentStart);
ReadAndVerifyDouble(RatePercentEnd);
ReadAndVerifyDouble(HalfStepsStart);
ReadAndVerifyDouble(HalfStepsEnd);
ReadAndVerifyDouble(PitchPercentStart);
ReadAndVerifyDouble(PitchPercentEnd);
m_RatePercentChangeStart = RatePercentStart;
m_RatePercentChangeEnd = RatePercentEnd;
m_PitchHalfStepsStart = HalfStepsStart;
m_PitchHalfStepsEnd = HalfStepsEnd;
m_PitchPercentChangeStart = PitchPercentStart;
m_PitchPercentChangeEnd = PitchPercentEnd;
return true;
}
// Effect implementation
bool EffectTimeScale::Init()
{
return true;
}
2016-01-26 18:19:19 +00:00
double EffectTimeScale::CalcPreviewInputLength(double previewLength)
{
double inputLength = Effect::GetDuration();
if(inputLength == 0.0) {
return 0.0;
} else {
double rateStart1 = PercentChangeToRatio(m_RatePercentChangeStart);
double rateEnd1 = PercentChangeToRatio(m_RatePercentChangeEnd);
2016-01-26 18:19:19 +00:00
double tOut = previewLength/inputLength;
double t = EffectSBSMS::getInvertedStretchedTime(rateStart1,rateEnd1,slideTypeRate,tOut);
2016-01-26 18:19:19 +00:00
return t * inputLength;
}
}
void EffectTimeScale::Preview(bool dryOnly)
{
previewSelectedDuration = Effect::GetDuration();
auto cleanup = valueRestorer( bPreview, true );
2016-01-26 18:19:19 +00:00
Effect::Preview(dryOnly);
}
bool EffectTimeScale::Process()
{
double pitchStart1 = PercentChangeToRatio(m_PitchPercentChangeStart);
double pitchEnd1 = PercentChangeToRatio(m_PitchPercentChangeEnd);
double rateStart1 = PercentChangeToRatio(m_RatePercentChangeStart);
double rateEnd1 = PercentChangeToRatio(m_RatePercentChangeEnd);
2016-01-26 18:19:19 +00:00
if(bPreview) {
double t = (mT1-mT0) / previewSelectedDuration;
rateEnd1 = EffectSBSMS::getRate(rateStart1,rateEnd1,slideTypeRate,t);
pitchEnd1 = EffectSBSMS::getRate(pitchStart1,pitchEnd1,slideTypePitch,t);
2016-01-26 18:19:19 +00:00
}
EffectSBSMS::setParameters(rateStart1,rateEnd1,pitchStart1,pitchEnd1,slideTypeRate,slideTypePitch,false,false,false);
return EffectSBSMS::Process();
}
void EffectTimeScale::PopulateOrExchange(ShuttleGui & S)
{
S.SetBorder(5);
S.AddSpace(0, 5);
S.StartMultiColumn(2, wxALIGN_CENTER);
{
// Rate Start
S.StartStatic(XO("Initial Tempo Change (%)"));
{
S.StartMultiColumn(1, wxCENTER);
{
m_pTextCtrl_RatePercentChangeStart = S.Id(ID_RatePercentChangeStart)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_RatePercentChangeStart,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_RatePercentStart, MAX_RatePercentStart
)
.AddTextBox( {}, wxT(""), 12);
}
S.EndMultiColumn();
S.StartHorizontalLay(wxEXPAND, 0);
{
m_pSlider_RatePercentChangeStart = S.Id(ID_RatePercentChangeStart)
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, DEF_RatePercentStart, MAX_RatePercentStart, MIN_RatePercentStart);
}
S.EndHorizontalLay();
}
S.EndStatic();
S.StartStatic(XO("Final Tempo Change (%)"));
2014-06-03 20:30:19 +00:00
{
S.StartMultiColumn(1, wxCENTER);
{
m_pTextCtrl_RatePercentChangeEnd = S.Id(ID_RatePercentChangeEnd)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_RatePercentChangeEnd,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_RatePercentEnd, MAX_RatePercentEnd
)
.AddTextBox( {}, wxT(""), 12);
}
S.EndMultiColumn();
S.StartHorizontalLay(wxEXPAND, 0);
{
m_pSlider_RatePercentChangeEnd = S.Id(ID_RatePercentChangeEnd)
.Style(wxSL_HORIZONTAL)
.AddSlider( {}, DEF_RatePercentEnd, MAX_RatePercentEnd, MIN_RatePercentEnd);
}
S.EndHorizontalLay();
}
S.EndStatic();
// Pitch Start
S.StartStatic(XO("Initial Pitch Shift"));
{
S.StartMultiColumn(2, wxCENTER);
{
m_pTextCtrl_PitchHalfStepsStart = S.Id(ID_PitchHalfStepsStart)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_PitchHalfStepsStart,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_HalfStepsStart, MAX_HalfStepsStart
)
.AddTextBox(XXO("(&semitones) [-12 to 12]:"), wxT(""), 12);
2017-10-30 16:23:41 +00:00
m_pTextCtrl_PitchPercentChangeStart = S.Id(ID_PitchPercentChangeStart)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_PitchPercentChangeStart,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_PitchPercentStart, MAX_PitchPercentStart
)
.AddTextBox(XXO("(%) [-50 to 100]:"), wxT(""), 12);
}
S.EndMultiColumn();
}
S.EndStatic();
// Pitch End
S.StartStatic(XO("Final Pitch Shift"));
{
S.StartMultiColumn(2, wxCENTER);
{
m_pTextCtrl_PitchHalfStepsEnd = S.Id(ID_PitchHalfStepsEnd)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_PitchHalfStepsEnd,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_HalfStepsEnd, MAX_HalfStepsEnd
)
.AddTextBox(XXO("(s&emitones) [-12 to 12]:"), wxT(""), 12);
m_pTextCtrl_PitchPercentChangeEnd = S.Id(ID_PitchPercentChangeEnd)
2017-10-30 16:23:41 +00:00
.Validator<FloatingPointValidator<double>>(
3, &m_PitchPercentChangeEnd,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_PitchPercentStart, MAX_PitchPercentStart)
.AddTextBox(XXO("(%) [-50 to 100]:"), wxT(""), 12);
}
S.EndMultiColumn();
}
S.EndStatic();
}
S.EndMultiColumn();
return;
}
bool EffectTimeScale::TransferDataToWindow()
{
if (!mUIParent->TransferDataToWindow())
{
return false;
}
Update_Slider_RatePercentChangeStart();
Update_Slider_RatePercentChangeEnd();
2014-06-03 20:30:19 +00:00
return true;
}
bool EffectTimeScale::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
return true;
}
inline double EffectTimeScale::PercentChangeToRatio(double percentChange)
{
return 1.0 + percentChange / 100.0;
}
inline double EffectTimeScale::HalfStepsToPercentChange(double halfSteps)
{
return 100.0 * (pow(2.0,halfSteps/12.0) - 1.0);
}
inline double EffectTimeScale::PercentChangeToHalfSteps(double percentChange)
{
return 12.0 * log2(PercentChangeToRatio(percentChange));
}
void EffectTimeScale::Update_Text_RatePercentChangeStart()
{
m_pTextCtrl_RatePercentChangeStart->GetValidator()->TransferToWindow();
}
void EffectTimeScale::Update_Text_RatePercentChangeEnd()
{
m_pTextCtrl_RatePercentChangeEnd->GetValidator()->TransferToWindow();
}
void EffectTimeScale::Update_Slider_RatePercentChangeStart()
{
m_pSlider_RatePercentChangeStart->SetValue((int)(m_RatePercentChangeStart + 0.5));
}
void EffectTimeScale::Update_Slider_RatePercentChangeEnd()
{
m_pSlider_RatePercentChangeEnd->SetValue((int)(m_RatePercentChangeEnd + 0.5));
}
void EffectTimeScale::Update_Text_PitchHalfStepsStart()
{
m_pTextCtrl_PitchHalfStepsStart->GetValidator()->TransferToWindow();
}
void EffectTimeScale::Update_Text_PitchHalfStepsEnd()
{
m_pTextCtrl_PitchHalfStepsEnd->GetValidator()->TransferToWindow();
}
void EffectTimeScale::Update_Text_PitchPercentChangeStart()
{
m_pTextCtrl_PitchPercentChangeStart->GetValidator()->TransferToWindow();
}
void EffectTimeScale::Update_Text_PitchPercentChangeEnd()
{
m_pTextCtrl_PitchPercentChangeEnd->GetValidator()->TransferToWindow();
}
void EffectTimeScale::OnText_RatePercentChangeStart(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
Update_Slider_RatePercentChangeStart();
}
void EffectTimeScale::OnText_RatePercentChangeEnd(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
Update_Slider_RatePercentChangeEnd();
}
void EffectTimeScale::OnSlider_RatePercentChangeStart(wxCommandEvent & evt)
{
m_RatePercentChangeStart = (double) evt.GetInt();
Update_Text_RatePercentChangeStart();
}
void EffectTimeScale::OnSlider_RatePercentChangeEnd(wxCommandEvent & evt)
{
m_RatePercentChangeEnd = (double) evt.GetInt();
Update_Text_RatePercentChangeEnd();
}
void EffectTimeScale::OnText_PitchHalfStepsStart(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
m_PitchPercentChangeStart = HalfStepsToPercentChange(m_PitchHalfStepsStart);
Update_Text_PitchPercentChangeStart();
}
void EffectTimeScale::OnText_PitchHalfStepsEnd(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
m_PitchPercentChangeEnd = HalfStepsToPercentChange(m_PitchHalfStepsEnd);
Update_Text_PitchPercentChangeEnd();
}
void EffectTimeScale::OnText_PitchPercentChangeStart(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
m_PitchHalfStepsStart = PercentChangeToHalfSteps(m_PitchPercentChangeStart);
Update_Text_PitchHalfStepsStart();
}
void EffectTimeScale::OnText_PitchPercentChangeEnd(wxCommandEvent & WXUNUSED(evt))
{
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
m_PitchHalfStepsEnd = PercentChangeToHalfSteps(m_PitchPercentChangeEnd);
Update_Text_PitchHalfStepsEnd();
}
#endif