2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
ChangeSpeed.cpp
|
|
|
|
|
|
|
|
Vaughan Johnson, Dominic Mazzoni
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class EffectChangeSpeed
|
2010-08-26 22:05:52 +00:00
|
|
|
\brief An Effect that affects both pitch & speed.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2015-07-03 04:20:21 +00:00
|
|
|
#include "ChangeSpeed.h"
|
2019-01-17 23:31:08 +00:00
|
|
|
#include "LoadEffects.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2010-08-26 22:05:52 +00:00
|
|
|
#include <math.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-11-14 16:07:06 +00:00
|
|
|
#include <wx/choice.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/intl.h>
|
2018-11-11 21:18:23 +00:00
|
|
|
#include <wx/slider.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
#include "../LabelTrack.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../Prefs.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../Resample.h"
|
2019-02-06 18:44:52 +00:00
|
|
|
#include "../Shuttle.h"
|
2010-08-26 22:05:52 +00:00
|
|
|
#include "../ShuttleGui.h"
|
2019-03-30 19:53:54 +00:00
|
|
|
#include "../widgets/NumericTextCtrl.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../widgets/valnum.h"
|
2010-08-26 22:05:52 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "TimeWarper.h"
|
2020-09-18 14:00:50 +00:00
|
|
|
#include "../WaveClip.h"
|
2015-07-03 04:20:21 +00:00
|
|
|
#include "../WaveTrack.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
ID_PercentChange = 10000,
|
|
|
|
ID_Multiplier,
|
|
|
|
ID_FromVinyl,
|
|
|
|
ID_ToVinyl,
|
|
|
|
ID_ToLength
|
|
|
|
};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-10-31 22:13:48 +00:00
|
|
|
// the standard vinyl rpm choices
|
2010-08-26 22:05:52 +00:00
|
|
|
// If the percent change is not one of these ratios, the choice control gets "n/a".
|
2015-04-17 03:53:42 +00:00
|
|
|
enum kVinyl
|
|
|
|
{
|
2010-08-26 22:05:52 +00:00
|
|
|
kVinyl_33AndAThird = 0,
|
|
|
|
kVinyl_45,
|
|
|
|
kVinyl_78,
|
2019-12-18 03:52:42 +00:00
|
|
|
kVinyl_NA
|
2015-04-17 03:53:42 +00:00
|
|
|
};
|
|
|
|
|
2019-12-18 03:52:42 +00:00
|
|
|
static const TranslatableStrings kVinylStrings{
|
2018-03-29 20:26:31 +00:00
|
|
|
XO("33\u2153"),
|
|
|
|
XO("45"),
|
|
|
|
XO("78"),
|
2015-04-17 03:53:42 +00:00
|
|
|
/* i18n-hint: n/a is an English abbreviation meaning "not applicable". */
|
2015-04-19 03:49:05 +00:00
|
|
|
XO("n/a"),
|
2010-08-26 22:05:52 +00:00
|
|
|
};
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Soundtouch is not reasonable below -99% or above 3000%.
|
|
|
|
|
|
|
|
// Define keys, defaults, minimums, and maximums for the effect parameters
|
|
|
|
//
|
2015-04-19 03:49:05 +00:00
|
|
|
// Name Type Key Def Min Max Scale
|
2017-10-03 22:07:04 +00:00
|
|
|
Param( Percentage, double, wxT("Percentage"), 0.0, -99.0, 4900.0, 1 );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
// 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.
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// EffectChangeSpeed
|
|
|
|
//
|
|
|
|
|
2019-01-17 22:33:49 +00:00
|
|
|
const ComponentInterfaceSymbol EffectChangeSpeed::Symbol
|
|
|
|
{ XO("Change Speed") };
|
|
|
|
|
2019-01-17 23:31:08 +00:00
|
|
|
namespace{ BuiltinEffectsModule::Registration< EffectChangeSpeed > reg; }
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
BEGIN_EVENT_TABLE(EffectChangeSpeed, wxEvtHandler)
|
|
|
|
EVT_TEXT(ID_PercentChange, EffectChangeSpeed::OnText_PercentChange)
|
|
|
|
EVT_TEXT(ID_Multiplier, EffectChangeSpeed::OnText_Multiplier)
|
|
|
|
EVT_SLIDER(ID_PercentChange, EffectChangeSpeed::OnSlider_PercentChange)
|
|
|
|
EVT_CHOICE(ID_FromVinyl, EffectChangeSpeed::OnChoice_Vinyl)
|
|
|
|
EVT_CHOICE(ID_ToVinyl, EffectChangeSpeed::OnChoice_Vinyl)
|
|
|
|
EVT_TEXT(ID_ToLength, EffectChangeSpeed::OnTimeCtrl_ToLength)
|
|
|
|
EVT_COMMAND(ID_ToLength, EVT_TIMETEXTCTRL_UPDATED, EffectChangeSpeed::OnTimeCtrlUpdate)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
EffectChangeSpeed::EffectChangeSpeed()
|
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
// effect parameters
|
|
|
|
m_PercentChange = DEF_Percentage;
|
2014-11-22 19:12:57 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mFromVinyl = kVinyl_33AndAThird;
|
|
|
|
mToVinyl = kVinyl_33AndAThird;
|
|
|
|
mFromLength = 0.0;
|
|
|
|
mToLength = 0.0;
|
2017-10-06 15:05:15 +00:00
|
|
|
mFormat = NumericConverter::DefaultSelectionFormat();
|
2015-04-17 03:53:42 +00:00
|
|
|
mbLoopDetect = false;
|
2015-05-15 11:47:51 +00:00
|
|
|
|
|
|
|
SetLinearEffectFlag(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
EffectChangeSpeed::~EffectChangeSpeed()
|
|
|
|
{
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-11-02 15:31:44 +00:00
|
|
|
// ComponentInterface implementation
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-11-02 15:31:44 +00:00
|
|
|
ComponentInterfaceSymbol EffectChangeSpeed::GetSymbol()
|
2013-03-08 05:01:07 +00:00
|
|
|
{
|
2019-01-17 22:33:49 +00:00
|
|
|
return Symbol;
|
2013-03-08 05:01:07 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 18:53:48 +00:00
|
|
|
TranslatableString EffectChangeSpeed::GetDescription()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2019-12-08 18:53:48 +00:00
|
|
|
return XO("Changes the speed of a track, also changing its pitch");
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 16:18:35 +00:00
|
|
|
ManualPageID EffectChangeSpeed::ManualPage()
|
2017-05-20 13:40:09 +00:00
|
|
|
{
|
2021-06-06 16:18:35 +00:00
|
|
|
return L"Change_Speed";
|
2017-05-20 13:40:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
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
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
EffectType EffectChangeSpeed::GetType()
|
|
|
|
{
|
|
|
|
return EffectTypeProcess;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// 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 EffectChangeSpeed::DefineParams( ShuttleParams & S ){
|
|
|
|
S.SHUTTLE_PARAM( m_PercentChange, Percentage );
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectChangeSpeed::GetAutomationParameters(CommandParameters & parms)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
parms.Write(KEY_Percentage, m_PercentChange);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2010-08-26 22:05:52 +00:00
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectChangeSpeed::SetAutomationParameters(CommandParameters & parms)
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
ReadAndVerifyDouble(Percentage);
|
|
|
|
|
|
|
|
m_PercentChange = Percentage;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-27 15:52:42 +00:00
|
|
|
bool EffectChangeSpeed::LoadFactoryDefaults()
|
|
|
|
{
|
|
|
|
mFromVinyl = kVinyl_33AndAThird;
|
2017-10-06 15:05:15 +00:00
|
|
|
mFormat = NumericConverter::DefaultSelectionFormat();
|
2015-04-27 15:52:42 +00:00
|
|
|
|
|
|
|
return Effect::LoadFactoryDefaults();
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Effect implementation
|
|
|
|
|
|
|
|
bool EffectChangeSpeed::CheckWhetherSkipEffect()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
return (m_PercentChange == 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
double EffectChangeSpeed::CalcPreviewInputLength(double previewLength)
|
|
|
|
{
|
|
|
|
return previewLength * (100.0 + m_PercentChange) / 100.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangeSpeed::Startup()
|
|
|
|
{
|
|
|
|
wxString base = wxT("/Effects/ChangeSpeed/");
|
|
|
|
|
|
|
|
// Migrate settings from 2.1.0 or before
|
|
|
|
|
|
|
|
// Already migrated, so bail
|
|
|
|
if (gPrefs->Exists(base + wxT("Migrated")))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the old "current" settings
|
|
|
|
if (gPrefs->Exists(base))
|
|
|
|
{
|
|
|
|
// Retrieve last used control values
|
|
|
|
gPrefs->Read(base + wxT("PercentChange"), &m_PercentChange, 0);
|
|
|
|
|
2017-10-06 15:05:15 +00:00
|
|
|
wxString format;
|
|
|
|
gPrefs->Read(base + wxT("TimeFormat"), &format, wxString{});
|
|
|
|
mFormat = NumericConverter::LookupFormat( NumericConverter::TIME, format );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
gPrefs->Read(base + wxT("VinylChoice"), &mFromVinyl, 0);
|
|
|
|
if (mFromVinyl == kVinyl_NA)
|
|
|
|
{
|
|
|
|
mFromVinyl = kVinyl_33AndAThird;
|
|
|
|
}
|
|
|
|
|
2017-10-06 15:05:15 +00:00
|
|
|
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"), mFormat.Internal());
|
2015-04-17 03:53:42 +00:00
|
|
|
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl);
|
|
|
|
|
|
|
|
SaveUserPreset(GetCurrentSettingsGroup());
|
|
|
|
|
|
|
|
// Do not migrate again
|
|
|
|
gPrefs->Write(base + wxT("Migrated"), true);
|
|
|
|
gPrefs->Flush();
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
bool EffectChangeSpeed::Init()
|
2015-04-07 11:20:08 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
// The selection might have changed since the last time EffectChangeSpeed
|
|
|
|
// was invoked, so recalculate the Length parameters.
|
|
|
|
mFromLength = mT1 - mT0;
|
2015-04-07 11:20:08 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
bool EffectChangeSpeed::Process()
|
|
|
|
{
|
|
|
|
// Similar to EffectSoundTouch::Process()
|
|
|
|
|
2011-04-18 06:30:07 +00:00
|
|
|
// Iterate over each track.
|
2016-12-31 09:54:52 +00:00
|
|
|
// All needed because this effect needs to introduce
|
2011-04-18 06:30:07 +00:00
|
|
|
// silence in the sync-lock group tracks to keep sync
|
2016-12-31 09:54:52 +00:00
|
|
|
CopyInputTracks(true); // Set up mOutputTracks.
|
2010-01-23 19:44:49 +00:00
|
|
|
bool bGoodResult = true;
|
|
|
|
|
|
|
|
mCurTrackNum = 0;
|
2010-08-26 22:05:52 +00:00
|
|
|
mMaxNewLength = 0.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2013-03-08 05:01:07 +00:00
|
|
|
mFactor = 100.0 / (100.0 + m_PercentChange);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 21:55:19 +00:00
|
|
|
mOutputTracks->Any().VisitWhile( bGoodResult,
|
|
|
|
[&](LabelTrack *lt) {
|
|
|
|
if (lt->GetSelected() || lt->IsSyncLockSelected())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-04-11 21:55:19 +00:00
|
|
|
if (!ProcessLabelTrack(lt))
|
2010-02-16 20:50:38 +00:00
|
|
|
bGoodResult = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 21:55:19 +00:00
|
|
|
},
|
|
|
|
[&](WaveTrack *pOutWaveTrack, const Track::Fallthrough &fallthrough) {
|
|
|
|
if (!pOutWaveTrack->GetSelected())
|
|
|
|
return fallthrough();
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get start and end times from track
|
|
|
|
mCurT0 = pOutWaveTrack->GetStartTime();
|
|
|
|
mCurT1 = pOutWaveTrack->GetEndTime();
|
|
|
|
|
|
|
|
//Set the current bounds to whichever left marker is
|
|
|
|
//greater and whichever right marker is less:
|
|
|
|
mCurT0 = wxMax(mT0, mCurT0);
|
|
|
|
mCurT1 = wxMin(mT1, mCurT1);
|
|
|
|
|
|
|
|
// Process only if the right marker is to the right of the left marker
|
|
|
|
if (mCurT1 > mCurT0) {
|
|
|
|
//Transform the marker timepoints to samples
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = pOutWaveTrack->TimeToLongSamples(mCurT0);
|
|
|
|
auto end = pOutWaveTrack->TimeToLongSamples(mCurT1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//ProcessOne() (implemented below) processes a single track
|
2010-02-16 20:50:38 +00:00
|
|
|
if (!ProcessOne(pOutWaveTrack, start, end))
|
2010-01-23 19:44:49 +00:00
|
|
|
bGoodResult = false;
|
|
|
|
}
|
|
|
|
mCurTrackNum++;
|
2017-04-11 21:55:19 +00:00
|
|
|
},
|
|
|
|
[&](Track *t) {
|
|
|
|
if (t->IsSyncLockSelected())
|
|
|
|
t->SyncLockAdjust(mT1, mT0 + (mT1 - mT0) * mFactor);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 21:55:19 +00:00
|
|
|
);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (bGoodResult)
|
|
|
|
ReplaceProcessedTracks(bGoodResult);
|
|
|
|
|
2015-01-22 08:50:00 +00:00
|
|
|
// Update selection.
|
|
|
|
mT1 = mT0 + (((mT1 - mT0) * 100.0) / (100.0 + m_PercentChange));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return bGoodResult;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::PopulateOrExchange(ShuttleGui & S)
|
|
|
|
{
|
2017-10-06 15:05:15 +00:00
|
|
|
{
|
|
|
|
wxString formatId;
|
|
|
|
GetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"),
|
|
|
|
formatId, mFormat.Internal());
|
|
|
|
mFormat = NumericConverter::LookupFormat(
|
|
|
|
NumericConverter::TIME, formatId );
|
|
|
|
}
|
2015-04-27 15:52:42 +00:00
|
|
|
GetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl, mFromVinyl);
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
S.SetBorder(5);
|
|
|
|
|
|
|
|
S.StartVerticalLay(0);
|
|
|
|
{
|
|
|
|
S.AddSpace(0, 5);
|
2019-12-22 23:14:40 +00:00
|
|
|
S.AddTitle(XO("Change Speed, affecting both Tempo and Pitch"));
|
2015-04-17 03:53:42 +00:00
|
|
|
S.AddSpace(0, 10);
|
|
|
|
|
|
|
|
// Speed multiplier and percent change controls.
|
|
|
|
S.StartMultiColumn(4, wxCENTER);
|
|
|
|
{
|
2017-10-30 16:23:41 +00:00
|
|
|
mpTextCtrl_Multiplier = S.Id(ID_Multiplier)
|
|
|
|
.Validator<FloatingPointValidator<double>>(
|
|
|
|
3, &mMultiplier,
|
|
|
|
NumValidatorStyle::THREE_TRAILING_ZEROES,
|
|
|
|
MIN_Percentage / 100.0, ((MAX_Percentage / 100.0) + 1)
|
|
|
|
)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("&Speed Multiplier:"), wxT(""), 12);
|
2017-10-30 16:23:41 +00:00
|
|
|
|
|
|
|
mpTextCtrl_PercentChange = S.Id(ID_PercentChange)
|
|
|
|
.Validator<FloatingPointValidator<double>>(
|
|
|
|
3, &m_PercentChange,
|
|
|
|
NumValidatorStyle::THREE_TRAILING_ZEROES,
|
|
|
|
MIN_Percentage, MAX_Percentage
|
|
|
|
)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Percent C&hange:"), wxT(""), 12);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
|
|
|
|
// Percent change slider.
|
|
|
|
S.StartHorizontalLay(wxEXPAND);
|
|
|
|
{
|
2017-10-29 14:27:23 +00:00
|
|
|
mpSlider_PercentChange = S.Id(ID_PercentChange)
|
|
|
|
.Name(XO("Percent Change"))
|
2017-10-31 18:52:01 +00:00
|
|
|
.Style(wxSL_HORIZONTAL)
|
2017-10-29 14:27:23 +00:00
|
|
|
.AddSlider( {}, 0, (int)kSliderMax, (int)MIN_Percentage);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndHorizontalLay();
|
|
|
|
|
|
|
|
// Vinyl rpm controls.
|
|
|
|
S.StartMultiColumn(5, wxCENTER);
|
|
|
|
{
|
2020-04-05 12:40:15 +00:00
|
|
|
/* i18n-hint: "rpm" is an English abbreviation meaning "revolutions per minute".
|
|
|
|
"vinyl" refers to old-fashioned phonograph records */
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("Standard Vinyl rpm:"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2019-02-22 20:15:19 +00:00
|
|
|
mpChoice_FromVinyl = S.Id(ID_FromVinyl)
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another
|
|
|
|
"rpm" means "revolutions per minute" as on a vinyl record turntable
|
|
|
|
*/
|
2017-10-29 14:27:23 +00:00
|
|
|
.Name(XO("From rpm"))
|
2018-01-31 20:31:22 +00:00
|
|
|
.MinSize( { 100, -1 } )
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another */
|
|
|
|
.AddChoice(XXC("&from", "change speed"), kVinylStrings);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-29 14:27:23 +00:00
|
|
|
mpChoice_ToVinyl = S.Id(ID_ToVinyl)
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another
|
|
|
|
"rpm" means "revolutions per minute" as on a vinyl record turntable
|
|
|
|
*/
|
2017-10-29 14:27:23 +00:00
|
|
|
.Name(XO("To rpm"))
|
2018-01-31 20:31:22 +00:00
|
|
|
.MinSize( { 100, -1 } )
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another */
|
|
|
|
.AddChoice(XXC("&to", "change speed"), kVinylStrings);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
|
|
|
|
// From/To time controls.
|
2019-12-22 19:58:36 +00:00
|
|
|
S.StartStatic(XO("Selection Length"), 0);
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2015-04-22 18:04:37 +00:00
|
|
|
S.StartMultiColumn(2, wxALIGN_LEFT);
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2020-05-11 15:28:14 +00:00
|
|
|
S.AddPrompt(XXO("C&urrent Length:"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2016-02-14 07:54:25 +00:00
|
|
|
mpFromLengthCtrl = safenew
|
2017-10-27 23:14:48 +00:00
|
|
|
NumericTextCtrl(S.GetParent(), wxID_ANY,
|
|
|
|
NumericConverter::TIME,
|
2015-04-17 03:53:42 +00:00
|
|
|
mFormat,
|
|
|
|
mFromLength,
|
2017-10-27 23:14:48 +00:00
|
|
|
mProjectRate,
|
|
|
|
NumericTextCtrl::Options{}
|
|
|
|
.ReadOnly(true)
|
|
|
|
.MenuEnabled(false));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-29 14:27:23 +00:00
|
|
|
S.ToolTip(XO("Current length of selection."))
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another */
|
|
|
|
.Name(XC("from", "change speed"))
|
2017-10-31 23:44:00 +00:00
|
|
|
.Position(wxALIGN_LEFT)
|
|
|
|
.AddWindow(mpFromLengthCtrl);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2020-05-11 15:28:14 +00:00
|
|
|
S.AddPrompt(XXO("&New Length:"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2016-02-14 07:54:25 +00:00
|
|
|
mpToLengthCtrl = safenew
|
2017-10-27 23:14:48 +00:00
|
|
|
NumericTextCtrl(S.GetParent(), ID_ToLength,
|
|
|
|
NumericConverter::TIME,
|
2015-04-17 03:53:42 +00:00
|
|
|
mFormat,
|
|
|
|
mToLength,
|
|
|
|
mProjectRate);
|
|
|
|
|
2020-05-28 20:23:40 +00:00
|
|
|
/* i18n-hint: changing speed of audio "from" one value "to" another */
|
|
|
|
S.Name(XC("to", "change speed"))
|
2017-10-31 23:44:00 +00:00
|
|
|
.Position(wxALIGN_LEFT)
|
|
|
|
.AddWindow(mpToLengthCtrl);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
}
|
|
|
|
S.EndStatic();
|
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangeSpeed::TransferDataToWindow()
|
|
|
|
{
|
|
|
|
mbLoopDetect = true;
|
|
|
|
|
|
|
|
if (!mUIParent->TransferDataToWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mFromVinyl == kVinyl_NA)
|
|
|
|
{
|
|
|
|
mFromVinyl = kVinyl_33AndAThird;
|
|
|
|
}
|
|
|
|
|
|
|
|
Update_Text_PercentChange();
|
|
|
|
Update_Text_Multiplier();
|
|
|
|
Update_Slider_PercentChange();
|
|
|
|
Update_TimeCtrl_ToLength();
|
|
|
|
|
|
|
|
// Set from/to Vinyl controls - mFromVinyl must be set first.
|
|
|
|
mpChoice_FromVinyl->SetSelection(mFromVinyl);
|
|
|
|
// Then update to get correct mToVinyl.
|
|
|
|
Update_Vinyl();
|
|
|
|
// Then update ToVinyl control.
|
|
|
|
mpChoice_ToVinyl->SetSelection(mToVinyl);
|
|
|
|
|
|
|
|
// Set From Length control.
|
|
|
|
// Set the format first so we can get sample accuracy.
|
|
|
|
mpFromLengthCtrl->SetFormatName(mFormat);
|
|
|
|
mpFromLengthCtrl->SetValue(mFromLength);
|
|
|
|
|
|
|
|
mbLoopDetect = false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectChangeSpeed::TransferDataFromWindow()
|
|
|
|
{
|
2016-01-21 04:03:32 +00:00
|
|
|
// mUIParent->TransferDataFromWindow() loses some precision, so save and restore it.
|
|
|
|
double exactPercent = m_PercentChange;
|
2015-04-17 03:53:42 +00:00
|
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2016-01-21 04:03:32 +00:00
|
|
|
m_PercentChange = exactPercent;
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-06 15:05:15 +00:00
|
|
|
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("TimeFormat"), mFormat.Internal());
|
2015-04-17 03:53:42 +00:00
|
|
|
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// EffectChangeSpeed implementation
|
|
|
|
|
|
|
|
// Labels are time-scaled linearly inside the affected region, and labels after
|
|
|
|
// the region are shifted along according to how the region size changed.
|
2016-09-15 13:54:38 +00:00
|
|
|
bool EffectChangeSpeed::ProcessLabelTrack(LabelTrack *lt)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2016-12-16 17:37:15 +00:00
|
|
|
RegionTimeWarper warper { mT0, mT1,
|
|
|
|
std::make_unique<LinearTimeWarper>(mT0, mT0,
|
|
|
|
mT1, mT0 + (mT1-mT0)*mFactor) };
|
|
|
|
lt->WarpLabels(warper);
|
2015-04-17 03:53:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
|
|
|
|
// and calls libsamplerate code on these blocks.
|
|
|
|
bool EffectChangeSpeed::ProcessOne(WaveTrack * track,
|
2010-02-16 20:50:38 +00:00
|
|
|
sampleCount start, sampleCount end)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (track == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// initialization, per examples of Mixer::Mixer and
|
|
|
|
// EffectSoundTouch::ProcessOne
|
|
|
|
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
|
|
|
|
auto outputTrack = track->EmptyCopy();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Get the length of the selection (as double). len is
|
|
|
|
//used simple to calculate a progress meter, so it is easier
|
2014-06-03 20:30:19 +00:00
|
|
|
//to make it a double now than it is to do it later
|
2016-08-25 12:53:59 +00:00
|
|
|
auto len = (end - start).as_double();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
// Initiate processing buffers, most likely shorter than
|
2010-01-23 19:44:49 +00:00
|
|
|
// the length of the selection being processed.
|
2016-08-24 15:24:26 +00:00
|
|
|
auto inBufferSize = track->GetMaxBlockSize();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats inBuffer{ inBufferSize };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-08-31 04:49:22 +00:00
|
|
|
// mFactor is at most 100-fold so this shouldn't overflow size_t
|
2016-09-06 13:19:27 +00:00
|
|
|
auto outBufferSize = size_t( mFactor * inBufferSize + 10 );
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats outBuffer{ outBufferSize };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Set up the resampling stuff for this track.
|
2013-08-04 01:58:54 +00:00
|
|
|
Resample resample(true, mFactor, mFactor); // constant rate resampling
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Go through the track one buffer at a time. samplePos counts which
|
|
|
|
//sample the current buffer starts at.
|
2011-11-18 03:47:43 +00:00
|
|
|
bool bResult = true;
|
2016-08-24 15:24:26 +00:00
|
|
|
auto samplePos = start;
|
2010-01-23 19:44:49 +00:00
|
|
|
while (samplePos < end) {
|
|
|
|
//Get a blockSize of samples (smaller than the size of the buffer)
|
2016-08-27 02:02:58 +00:00
|
|
|
auto blockSize = limitSampleBufferSize(
|
|
|
|
track->GetBestBlockSize(samplePos),
|
|
|
|
end - samplePos
|
|
|
|
);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Get the samples from the track and put them in the buffer
|
2021-05-23 21:43:38 +00:00
|
|
|
track->GetFloats(inBuffer.get(), samplePos, blockSize);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-09-05 17:44:23 +00:00
|
|
|
const auto results = resample.Process(mFactor,
|
2016-04-14 16:35:15 +00:00
|
|
|
inBuffer.get(),
|
2010-01-23 19:44:49 +00:00
|
|
|
blockSize,
|
|
|
|
((samplePos + blockSize) >= end),
|
2016-04-14 16:35:15 +00:00
|
|
|
outBuffer.get(),
|
2010-01-23 19:44:49 +00:00
|
|
|
outBufferSize);
|
2016-09-05 17:44:23 +00:00
|
|
|
const auto outgen = results.second;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
if (outgen > 0)
|
2016-04-14 16:35:15 +00:00
|
|
|
outputTrack->Append((samplePtr)outBuffer.get(), floatSample,
|
2010-01-23 19:44:49 +00:00
|
|
|
outgen);
|
|
|
|
|
|
|
|
// Increment samplePos
|
2016-09-05 17:44:23 +00:00
|
|
|
samplePos += results.first;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Update the Progress meter
|
2016-08-25 12:53:59 +00:00
|
|
|
if (TrackProgress(mCurTrackNum, (samplePos - start).as_double() / len)) {
|
2011-11-18 03:47:43 +00:00
|
|
|
bResult = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush the output WaveTrack (since it's buffered, too)
|
|
|
|
outputTrack->Flush();
|
|
|
|
|
|
|
|
// Take the output track and insert it in place of the original
|
|
|
|
// sample data
|
2014-06-03 20:30:19 +00:00
|
|
|
double newLength = outputTrack->GetEndTime();
|
|
|
|
if (bResult)
|
2011-11-16 05:59:48 +00:00
|
|
|
{
|
2021-05-15 10:35:39 +00:00
|
|
|
// Silenced samples will be inserted in gaps between clips, so capture where these
|
2020-09-18 14:00:50 +00:00
|
|
|
// gaps are for later deletion
|
|
|
|
std::vector<std::pair<double, double>> gaps;
|
2021-05-15 10:35:39 +00:00
|
|
|
double last = mCurT0;
|
2020-09-18 14:00:50 +00:00
|
|
|
auto clips = track->SortedClipArray();
|
|
|
|
auto front = clips.front();
|
|
|
|
auto back = clips.back();
|
|
|
|
for (auto &clip : clips) {
|
|
|
|
auto st = clip->GetStartTime();
|
|
|
|
auto et = clip->GetEndTime();
|
|
|
|
|
|
|
|
if (st >= mCurT0 || et < mCurT1) {
|
|
|
|
if (mCurT0 < st && clip == front) {
|
|
|
|
gaps.push_back(std::make_pair(mCurT0, st));
|
|
|
|
}
|
2021-05-15 10:35:39 +00:00
|
|
|
else if (last < st && mCurT0 <= last ) {
|
2020-09-18 14:00:50 +00:00
|
|
|
gaps.push_back(std::make_pair(last, st));
|
|
|
|
}
|
2021-05-15 10:35:39 +00:00
|
|
|
|
|
|
|
if (et < mCurT1 && clip == back) {
|
|
|
|
gaps.push_back(std::make_pair(et, mCurT1));
|
|
|
|
}
|
2020-09-18 14:00:50 +00:00
|
|
|
}
|
|
|
|
last = et;
|
|
|
|
}
|
|
|
|
|
2016-12-16 17:37:15 +00:00
|
|
|
LinearTimeWarper warper { mCurT0, mCurT0, mCurT1, mCurT0 + newLength };
|
2020-09-18 14:00:50 +00:00
|
|
|
|
|
|
|
// Take the output track and insert it in place of the original sample data
|
|
|
|
track->ClearAndPaste(mCurT0, mCurT1, outputTrack.get(), true, true, &warper);
|
|
|
|
|
|
|
|
// Finally, recreate the gaps
|
|
|
|
for (auto gap : gaps) {
|
|
|
|
auto st = track->LongSamplesToTime(track->TimeToLongSamples(gap.first));
|
|
|
|
auto et = track->LongSamplesToTime(track->TimeToLongSamples(gap.second));
|
2021-01-27 05:25:48 +00:00
|
|
|
if (st >= mCurT0 && et <= mCurT1 && st != et)
|
|
|
|
{
|
|
|
|
track->SplitDelete(warper.Warp(st), warper.Warp(et));
|
|
|
|
}
|
2020-09-18 14:00:50 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
if (newLength > mMaxNewLength)
|
|
|
|
mMaxNewLength = newLength;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2011-11-18 03:47:43 +00:00
|
|
|
return bResult;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// handler implementations for EffectChangeSpeed
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnText_PercentChange(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-08-26 22:05:52 +00:00
|
|
|
if (mbLoopDetect)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mpTextCtrl_PercentChange->GetValidator()->TransferFromWindow();
|
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = true;
|
2015-04-17 03:53:42 +00:00
|
|
|
Update_Text_Multiplier();
|
|
|
|
Update_Slider_PercentChange();
|
|
|
|
Update_Vinyl();
|
|
|
|
Update_TimeCtrl_ToLength();
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnText_Multiplier(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-08-26 22:05:52 +00:00
|
|
|
if (mbLoopDetect)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mpTextCtrl_Multiplier->GetValidator()->TransferFromWindow();
|
|
|
|
m_PercentChange = 100 * (mMultiplier - 1);
|
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = true;
|
2015-04-17 03:53:42 +00:00
|
|
|
Update_Text_PercentChange();
|
|
|
|
Update_Slider_PercentChange();
|
|
|
|
Update_Vinyl();
|
|
|
|
Update_TimeCtrl_ToLength();
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnSlider_PercentChange(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-08-26 22:05:52 +00:00
|
|
|
if (mbLoopDetect)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2014-11-22 19:12:57 +00:00
|
|
|
m_PercentChange = (double)(mpSlider_PercentChange->GetValue());
|
|
|
|
// Warp positive values to actually go up faster & further than negatives.
|
|
|
|
if (m_PercentChange > 0.0)
|
2015-04-17 03:53:42 +00:00
|
|
|
m_PercentChange = pow(m_PercentChange, kSliderWarp);
|
|
|
|
UpdateUI();
|
2014-11-22 19:12:57 +00:00
|
|
|
|
|
|
|
mbLoopDetect = true;
|
2015-04-17 03:53:42 +00:00
|
|
|
Update_Text_PercentChange();
|
|
|
|
Update_Text_Multiplier();
|
|
|
|
Update_Vinyl();
|
|
|
|
Update_TimeCtrl_ToLength();
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = false;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnChoice_Vinyl(wxCommandEvent & WXUNUSED(evt))
|
2014-11-22 19:12:57 +00:00
|
|
|
{
|
|
|
|
// Treat mpChoice_FromVinyl and mpChoice_ToVinyl as one control since we need
|
|
|
|
// both to calculate Percent Change.
|
|
|
|
mFromVinyl = mpChoice_FromVinyl->GetSelection();
|
|
|
|
mToVinyl = mpChoice_ToVinyl->GetSelection();
|
|
|
|
// Use this as the 'preferred' choice.
|
|
|
|
if (mFromVinyl != kVinyl_NA) {
|
2015-04-17 03:53:42 +00:00
|
|
|
SetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl);
|
2014-11-22 19:12:57 +00:00
|
|
|
}
|
|
|
|
|
2016-02-13 15:43:16 +00:00
|
|
|
// If mFromVinyl & mToVinyl are set, then there's a NEW percent change.
|
2014-11-22 19:12:57 +00:00
|
|
|
if ((mFromVinyl != kVinyl_NA) && (mToVinyl != kVinyl_NA))
|
|
|
|
{
|
|
|
|
double fromRPM;
|
|
|
|
double toRPM;
|
|
|
|
switch (mFromVinyl) {
|
|
|
|
default:
|
|
|
|
case kVinyl_33AndAThird: fromRPM = 33.0 + (1.0 / 3.0); break;
|
|
|
|
case kVinyl_45: fromRPM = 45.0; break;
|
|
|
|
case kVinyl_78: fromRPM = 78; break;
|
|
|
|
}
|
|
|
|
switch (mToVinyl) {
|
|
|
|
default:
|
|
|
|
case kVinyl_33AndAThird: toRPM = 33.0 + (1.0 / 3.0); break;
|
|
|
|
case kVinyl_45: toRPM = 45.0; break;
|
|
|
|
case kVinyl_78: toRPM = 78; break;
|
|
|
|
}
|
|
|
|
m_PercentChange = ((toRPM * 100.0) / fromRPM) - 100.0;
|
2015-04-17 03:53:42 +00:00
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-08-26 22:05:52 +00:00
|
|
|
mbLoopDetect = true;
|
2015-04-17 03:53:42 +00:00
|
|
|
Update_Text_PercentChange();
|
|
|
|
Update_Text_Multiplier();
|
|
|
|
Update_Slider_PercentChange();
|
|
|
|
Update_TimeCtrl_ToLength();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-11-22 19:12:57 +00:00
|
|
|
mbLoopDetect = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnTimeCtrl_ToLength(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2010-08-26 22:05:52 +00:00
|
|
|
if (mbLoopDetect)
|
2010-01-23 19:44:49 +00:00
|
|
|
return;
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mToLength = mpToLengthCtrl->GetValue();
|
2017-01-10 18:16:41 +00:00
|
|
|
// Division by (double) 0.0 is not an error and we want to show "infinite" in
|
|
|
|
// text controls, so take care that we handle infinite values when they occur.
|
2015-04-17 03:53:42 +00:00
|
|
|
m_PercentChange = ((mFromLength * 100.0) / mToLength) - 100.0;
|
|
|
|
UpdateUI();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mbLoopDetect = true;
|
2014-11-22 19:12:57 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
Update_Text_PercentChange();
|
|
|
|
Update_Text_Multiplier();
|
|
|
|
Update_Slider_PercentChange();
|
|
|
|
Update_Vinyl();
|
2014-11-22 19:12:57 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
mbLoopDetect = false;
|
2014-11-22 19:12:57 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::OnTimeCtrlUpdate(wxCommandEvent & evt)
|
2014-11-22 19:12:57 +00:00
|
|
|
{
|
2017-10-06 15:05:15 +00:00
|
|
|
mFormat = NumericConverter::LookupFormat(
|
|
|
|
NumericConverter::TIME, evt.GetString() );
|
2014-11-22 19:12:57 +00:00
|
|
|
|
|
|
|
mpFromLengthCtrl->SetFormatName(mFormat);
|
|
|
|
// Update From/To Length controls (precision has changed).
|
|
|
|
mpToLengthCtrl->SetValue(mToLength);
|
|
|
|
mpFromLengthCtrl->SetValue(mFromLength);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2014-11-22 19:12:57 +00:00
|
|
|
// helper functions
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::Update_Text_PercentChange()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Update Text Percent control from percent change.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
mpTextCtrl_PercentChange->GetValidator()->TransferToWindow();
|
2014-11-22 19:12:57 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::Update_Text_Multiplier()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Update Multiplier control from percent change.
|
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
mMultiplier = 1 + (m_PercentChange) / 100.0;
|
|
|
|
mpTextCtrl_Multiplier->GetValidator()->TransferToWindow();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::Update_Slider_PercentChange()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Update Slider Percent control from percent change.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-07-22 02:13:15 +00:00
|
|
|
auto unwarped = std::min<double>(m_PercentChange, MAX_Percentage);
|
2014-11-22 19:12:57 +00:00
|
|
|
if (unwarped > 0.0)
|
2015-04-17 03:53:42 +00:00
|
|
|
// Un-warp values above zero to actually go up to kSliderMax.
|
|
|
|
unwarped = pow(m_PercentChange, (1.0 / kSliderWarp));
|
2014-11-22 19:12:57 +00:00
|
|
|
|
2017-01-10 18:16:41 +00:00
|
|
|
// Caution: m_PercentChange could be infinite.
|
|
|
|
int unwarpedi = (int)(unwarped + 0.5);
|
2017-07-22 02:13:15 +00:00
|
|
|
unwarpedi = std::min<int>(unwarpedi, (int)kSliderMax);
|
2017-01-10 18:16:41 +00:00
|
|
|
|
|
|
|
mpSlider_PercentChange->SetValue(unwarpedi);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::Update_Vinyl()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Update Vinyl controls from percent change.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-22 19:12:57 +00:00
|
|
|
// Match Vinyl rpm when within 0.01% of a standard ratio.
|
|
|
|
// Ratios calculated as: ((toRPM / fromRPM) - 1) * 100 * 100
|
2017-01-10 18:16:41 +00:00
|
|
|
|
|
|
|
// Caution: m_PercentChange could be infinite
|
|
|
|
int ratio = (int)((m_PercentChange * 100) + 0.5);
|
2014-11-22 19:12:57 +00:00
|
|
|
|
|
|
|
switch (ratio)
|
2010-08-26 22:05:52 +00:00
|
|
|
{
|
2014-11-22 19:12:57 +00:00
|
|
|
case 0: // toRPM is the same as fromRPM
|
|
|
|
if (mFromVinyl != kVinyl_NA) {
|
|
|
|
mpChoice_ToVinyl->SetSelection(mpChoice_FromVinyl->GetSelection());
|
|
|
|
} else {
|
|
|
|
// Use the last saved option.
|
2015-04-17 03:53:42 +00:00
|
|
|
GetPrivateConfig(GetCurrentSettingsGroup(), wxT("VinylChoice"), mFromVinyl, 0);
|
2014-11-22 19:12:57 +00:00
|
|
|
mpChoice_FromVinyl->SetSelection(mFromVinyl);
|
|
|
|
mpChoice_ToVinyl->SetSelection(mFromVinyl);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3500:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_33AndAThird);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_45);
|
|
|
|
break;
|
|
|
|
case 13400:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_33AndAThird);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_78);
|
|
|
|
break;
|
|
|
|
case -2593:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_45);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_33AndAThird);
|
|
|
|
break;
|
|
|
|
case 7333:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_45);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_78);
|
|
|
|
break;
|
|
|
|
case -5727:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_78);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_33AndAThird);
|
|
|
|
break;
|
|
|
|
case -4231:
|
|
|
|
mpChoice_FromVinyl->SetSelection(kVinyl_78);
|
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_45);
|
|
|
|
break;
|
|
|
|
default:
|
2010-08-26 22:05:52 +00:00
|
|
|
mpChoice_ToVinyl->SetSelection(kVinyl_NA);
|
|
|
|
}
|
2014-11-22 19:12:57 +00:00
|
|
|
// and update variables.
|
|
|
|
mFromVinyl = mpChoice_FromVinyl->GetSelection();
|
|
|
|
mToVinyl = mpChoice_ToVinyl->GetSelection();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::Update_TimeCtrl_ToLength()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Update ToLength control from percent change.
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2014-11-22 19:12:57 +00:00
|
|
|
mToLength = (mFromLength * 100.0) / (100.0 + m_PercentChange);
|
|
|
|
|
|
|
|
// Set the format first so we can get sample accuracy.
|
|
|
|
mpToLengthCtrl->SetFormatName(mFormat);
|
|
|
|
// Negative times do not make sense.
|
|
|
|
// 359999 = 99h:59m:59s which is a little less disturbing than overflow characters
|
|
|
|
// though it may still look a bit strange with some formats.
|
|
|
|
mToLength = TrapDouble(mToLength, 0.0, 359999.0);
|
|
|
|
mpToLengthCtrl->SetValue(mToLength);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectChangeSpeed::UpdateUI()
|
2014-11-22 19:12:57 +00:00
|
|
|
// Disable OK and Preview if not in sensible range.
|
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
EnableApply(m_PercentChange >= MIN_Percentage && m_PercentChange <= MAX_Percentage);
|
2014-11-22 19:12:57 +00:00
|
|
|
}
|