2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
AutoDuck.cpp
|
|
|
|
|
|
|
|
Markus Meyer
|
|
|
|
|
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class EffectAutoDuck
|
|
|
|
\brief Implements the Auto Ducking effect
|
|
|
|
|
2017-11-09 18:32:29 +00:00
|
|
|
\class AutoDuckRegion
|
|
|
|
\brief a struct that holds a start and end time.
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
*******************************************************************/
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2015-06-18 14:24:36 +00:00
|
|
|
#include "AutoDuck.h"
|
2019-01-17 23:31:08 +00:00
|
|
|
#include "LoadEffects.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <math.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <float.h>
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <wx/dcclient.h>
|
|
|
|
#include <wx/dcmemory.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/intl.h>
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../AColor.h"
|
|
|
|
#include "../AllThemeResources.h"
|
|
|
|
#include "../Prefs.h"
|
2019-02-06 18:44:52 +00:00
|
|
|
#include "../Shuttle.h"
|
2015-06-18 14:24:36 +00:00
|
|
|
#include "../ShuttleGui.h"
|
2020-05-26 17:31:32 +00:00
|
|
|
#include "../Theme.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../widgets/valnum.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-07-03 04:20:21 +00:00
|
|
|
#include "../WaveTrack.h"
|
2019-05-20 18:27:11 +00:00
|
|
|
#include "../widgets/AudacityMessageBox.h"
|
2015-07-03 04:20:21 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// 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( DuckAmountDb, double, wxT("DuckAmountDb"), -12.0, -24.0, 0.0, 1 );
|
|
|
|
Param( InnerFadeDownLen, double, wxT("InnerFadeDownLen"), 0.0, 0.0, 3.0, 1 );
|
|
|
|
Param( InnerFadeUpLen, double, wxT("InnerFadeUpLen"), 0.0, 0.0, 3.0, 1 );
|
|
|
|
Param( OuterFadeDownLen, double, wxT("OuterFadeDownLen"), 0.5, 0.0, 3.0, 1 );
|
|
|
|
Param( OuterFadeUpLen, double, wxT("OuterFadeUpLen"), 0.5, 0.0, 3.0, 1 );
|
|
|
|
Param( ThresholdDb, double, wxT("ThresholdDb"), -30.0, -100.0, 0.0, 1 );
|
|
|
|
Param( MaximumPause, double, wxT("MaximumPause"), 1.0, 0.0, DBL_MAX, 1 );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Common constants
|
|
|
|
*/
|
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
static const size_t kBufSize = 131072u; // number of samples to process at once
|
|
|
|
static const size_t kRMSWindowSize = 100u; // samples in circular RMS window buffer
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* A auto duck region and an array of auto duck regions
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct AutoDuckRegion
|
|
|
|
{
|
|
|
|
AutoDuckRegion(double t0, double t1)
|
|
|
|
{
|
|
|
|
this->t0 = t0;
|
|
|
|
this->t1 = t1;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
double t0;
|
|
|
|
double t1;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Effect implementation
|
|
|
|
*/
|
|
|
|
|
2019-01-17 22:33:49 +00:00
|
|
|
const ComponentInterfaceSymbol EffectAutoDuck::Symbol
|
|
|
|
{ XO("Auto Duck") };
|
|
|
|
|
2019-01-17 23:31:08 +00:00
|
|
|
namespace{ BuiltinEffectsModule::Registration< EffectAutoDuck > reg; }
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
BEGIN_EVENT_TABLE(EffectAutoDuck, wxEvtHandler)
|
|
|
|
EVT_TEXT(wxID_ANY, EffectAutoDuck::OnValueChanged)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
EffectAutoDuck::EffectAutoDuck()
|
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
mDuckAmountDb = DEF_DuckAmountDb;
|
|
|
|
mInnerFadeDownLen = DEF_InnerFadeDownLen;
|
|
|
|
mInnerFadeUpLen = DEF_InnerFadeUpLen;
|
|
|
|
mOuterFadeDownLen = DEF_OuterFadeDownLen;
|
|
|
|
mOuterFadeUpLen = DEF_OuterFadeUpLen;
|
|
|
|
mThresholdDb = DEF_ThresholdDb;
|
|
|
|
mMaximumPause = DEF_MaximumPause;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-05-15 11:47:51 +00:00
|
|
|
SetLinearEffectFlag(true);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack = NULL;
|
2015-04-17 03:53:42 +00:00
|
|
|
|
|
|
|
mPanel = NULL;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
EffectAutoDuck::~EffectAutoDuck()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
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 EffectAutoDuck::GetSymbol()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2019-01-17 22:33:49 +00:00
|
|
|
return Symbol;
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 18:53:48 +00:00
|
|
|
TranslatableString EffectAutoDuck::GetDescription()
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2019-12-08 18:53:48 +00:00
|
|
|
return XO("Reduces (ducks) the volume of one or more tracks whenever the volume of a specified \"control\" track reaches a particular level");
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 16:18:35 +00:00
|
|
|
ManualPageID EffectAutoDuck::ManualPage()
|
2017-05-20 13:40:09 +00:00
|
|
|
{
|
2021-06-06 16:18:35 +00:00
|
|
|
return L"Auto_Duck";
|
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 EffectAutoDuck::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 EffectAutoDuck::DefineParams( ShuttleParams & S ){
|
|
|
|
S.SHUTTLE_PARAM( mDuckAmountDb, DuckAmountDb);
|
|
|
|
S.SHUTTLE_PARAM( mInnerFadeDownLen, InnerFadeDownLen);
|
|
|
|
S.SHUTTLE_PARAM( mInnerFadeUpLen, InnerFadeUpLen);
|
|
|
|
S.SHUTTLE_PARAM( mOuterFadeDownLen, OuterFadeDownLen);
|
|
|
|
S.SHUTTLE_PARAM( mOuterFadeUpLen, OuterFadeUpLen);
|
|
|
|
S.SHUTTLE_PARAM( mThresholdDb, ThresholdDb);
|
|
|
|
S.SHUTTLE_PARAM( mMaximumPause, MaximumPause);
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectAutoDuck::GetAutomationParameters(CommandParameters & parms)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
parms.Write(KEY_DuckAmountDb, mDuckAmountDb);
|
|
|
|
parms.Write(KEY_InnerFadeDownLen, mInnerFadeDownLen);
|
|
|
|
parms.Write(KEY_InnerFadeUpLen, mInnerFadeUpLen);
|
|
|
|
parms.Write(KEY_OuterFadeDownLen, mOuterFadeDownLen);
|
|
|
|
parms.Write(KEY_OuterFadeUpLen, mOuterFadeUpLen);
|
|
|
|
parms.Write(KEY_ThresholdDb, mThresholdDb);
|
|
|
|
parms.Write(KEY_MaximumPause, mMaximumPause);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectAutoDuck::SetAutomationParameters(CommandParameters & parms)
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
|
|
|
ReadAndVerifyDouble(DuckAmountDb);
|
|
|
|
ReadAndVerifyDouble(InnerFadeDownLen);
|
|
|
|
ReadAndVerifyDouble(InnerFadeUpLen);
|
|
|
|
ReadAndVerifyDouble(OuterFadeDownLen);
|
|
|
|
ReadAndVerifyDouble(OuterFadeUpLen);
|
|
|
|
ReadAndVerifyDouble(ThresholdDb);
|
|
|
|
ReadAndVerifyDouble(MaximumPause);
|
|
|
|
|
|
|
|
mDuckAmountDb = DuckAmountDb;
|
|
|
|
mInnerFadeDownLen = InnerFadeDownLen;
|
|
|
|
mInnerFadeUpLen = InnerFadeUpLen;
|
|
|
|
mOuterFadeDownLen = OuterFadeDownLen;
|
|
|
|
mOuterFadeUpLen = OuterFadeUpLen;
|
|
|
|
mThresholdDb = ThresholdDb;
|
|
|
|
mMaximumPause = MaximumPause;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Effect implementation
|
|
|
|
|
|
|
|
bool EffectAutoDuck::Startup()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
wxString base = wxT("/Effects/AutoDuck/");
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
{
|
|
|
|
gPrefs->Read(base + wxT("DuckAmountDb"), &mDuckAmountDb, DEF_DuckAmountDb);
|
|
|
|
gPrefs->Read(base + wxT("InnerFadeDownLen"), &mInnerFadeDownLen, DEF_InnerFadeDownLen);
|
|
|
|
gPrefs->Read(base + wxT("InnerFadeUpLen"), &mInnerFadeUpLen, DEF_InnerFadeUpLen);
|
|
|
|
gPrefs->Read(base + wxT("OuterFadeDownLen"), &mOuterFadeDownLen, DEF_OuterFadeDownLen);
|
|
|
|
gPrefs->Read(base + wxT("OuterFadeUpLen"), &mOuterFadeUpLen, DEF_OuterFadeUpLen);
|
|
|
|
gPrefs->Read(base + wxT("ThresholdDb"), &mThresholdDb, DEF_ThresholdDb);
|
|
|
|
gPrefs->Read(base + wxT("MaximumPause"), &mMaximumPause, DEF_MaximumPause);
|
|
|
|
|
|
|
|
SaveUserPreset(GetCurrentSettingsGroup());
|
|
|
|
|
|
|
|
// Do not migrate again
|
|
|
|
gPrefs->Write(base + wxT("Migrated"), true);
|
|
|
|
gPrefs->Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
bool EffectAutoDuck::Init()
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack = NULL;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
bool lastWasSelectedWaveTrack = false;
|
2016-09-14 02:23:17 +00:00
|
|
|
const WaveTrack *controlTrackCandidate = NULL;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-12-31 09:33:35 +00:00
|
|
|
for (auto t : inputTracks()->Any())
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2017-04-11 22:16:03 +00:00
|
|
|
if (lastWasSelectedWaveTrack && !t->GetSelected()) {
|
2010-01-23 19:44:49 +00:00
|
|
|
// This could be the control track, so remember it
|
2017-04-11 22:16:03 +00:00
|
|
|
controlTrackCandidate = track_cast<const WaveTrack *>(t);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lastWasSelectedWaveTrack = false;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2017-04-11 22:16:03 +00:00
|
|
|
if (t->GetSelected()) {
|
|
|
|
bool ok = t->TypeSwitch<bool>(
|
|
|
|
[&](const WaveTrack *) {
|
|
|
|
lastWasSelectedWaveTrack = true;
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
[&](const Track *) {
|
|
|
|
Effect::MessageBox(
|
|
|
|
/* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces the volume)
|
|
|
|
* of the audio automatically when there is sound on another track. Not as
|
|
|
|
* in 'Donald-Duck'!*/
|
2019-12-19 19:19:51 +00:00
|
|
|
XO("You selected a track which does not contain audio. AutoDuck can only process audio tracks."),
|
|
|
|
wxICON_ERROR );
|
2017-04-11 22:16:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
if (!ok)
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!controlTrackCandidate)
|
|
|
|
{
|
2017-09-10 14:42:33 +00:00
|
|
|
Effect::MessageBox(
|
2019-12-19 19:19:51 +00:00
|
|
|
/* i18n-hint: Auto duck is the name of an effect that 'ducks' (reduces the volume)
|
|
|
|
* of the audio automatically when there is sound on another track. Not as
|
|
|
|
* in 'Donald-Duck'!*/
|
|
|
|
XO("Auto Duck needs a control track which must be placed below the selected track(s)."),
|
|
|
|
wxICON_ERROR );
|
2010-01-23 19:44:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack = controlTrackCandidate;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectAutoDuck::End()
|
|
|
|
{
|
|
|
|
mControlTrack = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectAutoDuck::Process()
|
|
|
|
{
|
|
|
|
if (GetNumWaveTracks() == 0 || !mControlTrack)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool cancel = false;
|
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start =
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack->TimeToLongSamples(mT0 + mOuterFadeDownLen);
|
2016-08-24 15:24:26 +00:00
|
|
|
auto end =
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack->TimeToLongSamples(mT1 - mOuterFadeUpLen);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (end <= start)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// the minimum number of samples we have to wait until the maximum
|
|
|
|
// pause has been exceeded
|
|
|
|
double maxPause = mMaximumPause;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// We don't fade in until we have time enough to actually fade out again
|
|
|
|
if (maxPause < mOuterFadeDownLen + mOuterFadeUpLen)
|
|
|
|
maxPause = mOuterFadeDownLen + mOuterFadeUpLen;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto minSamplesPause =
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlTrack->TimeToLongSamples(maxPause);
|
|
|
|
|
2015-07-24 20:59:34 +00:00
|
|
|
double threshold = DB_TO_LINEAR(mThresholdDb);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// adjust the threshold so we can compare it to the rmsSum value
|
2015-04-17 03:53:42 +00:00
|
|
|
threshold = threshold * threshold * kRMSWindowSize;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
int rmsPos = 0;
|
|
|
|
float rmsSum = 0;
|
|
|
|
// to make the progress bar appear more natural, we first look for all
|
|
|
|
// duck regions and apply them all at once afterwards
|
2018-02-02 18:24:53 +00:00
|
|
|
std::vector<AutoDuckRegion> regions;
|
2016-04-14 16:35:15 +00:00
|
|
|
bool inDuckRegion = false;
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats rmsWindow{ kRMSWindowSize, true };
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats buf{ kBufSize };
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// initialize the following two variables to prevent compiler warning
|
|
|
|
double duckRegionStart = 0;
|
|
|
|
sampleCount curSamplesPause = 0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
auto pos = start;
|
|
|
|
|
|
|
|
while (pos < end)
|
|
|
|
{
|
|
|
|
const auto len = limitSampleBufferSize( kBufSize, end - pos );
|
|
|
|
|
2021-05-23 21:43:38 +00:00
|
|
|
mControlTrack->GetFloats(buf.get(), pos, len);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
for (auto i = pos; i < pos + len; i++)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
rmsSum -= rmsWindow[rmsPos];
|
|
|
|
// i - pos is bounded by len:
|
|
|
|
auto index = ( i - pos ).as_size_t();
|
|
|
|
rmsWindow[rmsPos] = buf[ index ] * buf[ index ];
|
|
|
|
rmsSum += rmsWindow[rmsPos];
|
|
|
|
rmsPos = (rmsPos + 1) % kRMSWindowSize;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
bool thresholdExceeded = rmsSum > threshold;
|
|
|
|
|
|
|
|
if (thresholdExceeded)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
// everytime the threshold is exceeded, reset our count for
|
|
|
|
// the number of pause samples
|
|
|
|
curSamplesPause = 0;
|
|
|
|
|
|
|
|
if (!inDuckRegion)
|
|
|
|
{
|
|
|
|
// the threshold has been exceeded for the first time, so
|
|
|
|
// let the duck region begin here
|
|
|
|
inDuckRegion = true;
|
|
|
|
duckRegionStart = mControlTrack->LongSamplesToTime(i);
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
if (!thresholdExceeded && inDuckRegion)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
// the threshold has not been exceeded and we are in a duck
|
|
|
|
// region, but only fade in if the maximum pause has been
|
|
|
|
// exceeded
|
|
|
|
curSamplesPause += 1;
|
|
|
|
|
|
|
|
if (curSamplesPause >= minSamplesPause)
|
|
|
|
{
|
|
|
|
// do the actual duck fade and reset all values
|
|
|
|
double duckRegionEnd =
|
|
|
|
mControlTrack->LongSamplesToTime(i - curSamplesPause);
|
|
|
|
|
2018-02-02 18:24:53 +00:00
|
|
|
regions.push_back(AutoDuckRegion(
|
2016-04-14 16:35:15 +00:00
|
|
|
duckRegionStart - mOuterFadeDownLen,
|
|
|
|
duckRegionEnd + mOuterFadeUpLen));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
inDuckRegion = false;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
pos += len;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
if (TotalProgress(
|
2016-08-25 12:53:59 +00:00
|
|
|
(pos - start).as_double() /
|
|
|
|
(end - start).as_double() /
|
|
|
|
(GetNumWaveTracks() + 1)
|
2016-04-14 16:35:15 +00:00
|
|
|
))
|
|
|
|
{
|
|
|
|
cancel = true;
|
|
|
|
break;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// apply last duck fade, if any
|
|
|
|
if (inDuckRegion)
|
|
|
|
{
|
|
|
|
double duckRegionEnd =
|
|
|
|
mControlTrack->LongSamplesToTime(end - curSamplesPause);
|
2018-02-02 18:24:53 +00:00
|
|
|
regions.push_back(AutoDuckRegion(
|
2016-04-14 16:35:15 +00:00
|
|
|
duckRegionStart - mOuterFadeDownLen,
|
|
|
|
duckRegionEnd + mOuterFadeUpLen));
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!cancel)
|
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
CopyInputTracks(); // Set up mOutputTracks.
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2018-02-09 19:12:25 +00:00
|
|
|
int trackNum = 0;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2018-09-11 17:07:32 +00:00
|
|
|
for( auto iterTrack : mOutputTracks->Selected< WaveTrack >() )
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-02-02 18:24:53 +00:00
|
|
|
for (size_t i = 0; i < regions.size(); i++)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
const AutoDuckRegion& region = regions[i];
|
2018-09-11 17:07:32 +00:00
|
|
|
if (ApplyDuckFade(trackNum, iterTrack, region.t0, region.t1))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
cancel = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (cancel)
|
|
|
|
break;
|
|
|
|
|
2018-02-09 19:12:25 +00:00
|
|
|
trackNum++;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
ReplaceProcessedTracks(!cancel);
|
2010-01-23 19:44:49 +00:00
|
|
|
return !cancel;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuck::PopulateOrExchange(ShuttleGui & S)
|
|
|
|
{
|
|
|
|
S.SetBorder(5);
|
|
|
|
S.StartVerticalLay(true);
|
|
|
|
{
|
|
|
|
S.AddSpace(0, 5);
|
|
|
|
|
2017-10-20 16:06:29 +00:00
|
|
|
mPanel = safenew EffectAutoDuckPanel(S.GetParent(), wxID_ANY, this);
|
2015-04-17 03:53:42 +00:00
|
|
|
S.AddWindow(mPanel);
|
|
|
|
|
|
|
|
S.AddSpace(0, 5);
|
|
|
|
|
|
|
|
S.StartMultiColumn(6, wxCENTER);
|
|
|
|
{
|
2017-10-30 16:23:41 +00:00
|
|
|
mDuckAmountDbBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
1, &mDuckAmountDb, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_DuckAmountDb, MAX_DuckAmountDb
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("db"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Duck &amount:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("dB"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mMaximumPauseBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mMaximumPause, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_MaximumPause, MAX_MaximumPause
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("seconds"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Ma&ximum pause:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("seconds"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mOuterFadeDownLenBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mOuterFadeDownLen, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_OuterFadeDownLen, MAX_OuterFadeDownLen
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("seconds"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Outer fade &down length:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("seconds"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mOuterFadeUpLenBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mOuterFadeUpLen, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_OuterFadeUpLen, MAX_OuterFadeUpLen
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("seconds"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Outer fade &up length:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("seconds"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mInnerFadeDownLenBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mInnerFadeDownLen, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_InnerFadeDownLen, MAX_InnerFadeDownLen
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("seconds"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Inner fade d&own length:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("seconds"));
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mInnerFadeUpLenBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mInnerFadeUpLen, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_InnerFadeUpLen, MAX_InnerFadeUpLen
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("seconds"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("Inner &fade up length:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("seconds"));
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
|
|
|
|
S.StartMultiColumn(3, wxCENTER);
|
|
|
|
{
|
2017-10-30 16:23:41 +00:00
|
|
|
mThresholdDbBox = S.Validator<FloatingPointValidator<double>>(
|
|
|
|
2, &mThresholdDb, NumValidatorStyle::NO_TRAILING_ZEROES,
|
|
|
|
MIN_ThresholdDb, MAX_ThresholdDb
|
|
|
|
)
|
2020-02-21 14:55:15 +00:00
|
|
|
.NameSuffix(XO("db"))
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddTextBox(XXO("&Threshold:"), wxT(""), 10);
|
2019-12-22 20:40:29 +00:00
|
|
|
S.AddUnits(XO("dB"));
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
|
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectAutoDuck::TransferDataToWindow()
|
|
|
|
{
|
|
|
|
if (!mUIParent->TransferDataToWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-22 18:05:27 +00:00
|
|
|
mPanel->Refresh(false);
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectAutoDuck::TransferDataFromWindow()
|
|
|
|
{
|
|
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// EffectAutoDuck implementation
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// this currently does an exponential fade
|
2018-02-09 19:12:25 +00:00
|
|
|
bool EffectAutoDuck::ApplyDuckFade(int trackNum, WaveTrack* t,
|
2010-01-23 19:44:49 +00:00
|
|
|
double t0, double t1)
|
|
|
|
{
|
|
|
|
bool cancel = false;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = t->TimeToLongSamples(t0);
|
|
|
|
auto end = t->TimeToLongSamples(t1);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats buf{ kBufSize };
|
2016-08-24 15:24:26 +00:00
|
|
|
auto pos = start;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-24 11:56:33 +00:00
|
|
|
auto fadeDownSamples = t->TimeToLongSamples(
|
2010-01-23 19:44:49 +00:00
|
|
|
mOuterFadeDownLen + mInnerFadeDownLen);
|
|
|
|
if (fadeDownSamples < 1)
|
|
|
|
fadeDownSamples = 1;
|
|
|
|
|
2016-08-24 11:56:33 +00:00
|
|
|
auto fadeUpSamples = t->TimeToLongSamples(
|
2010-01-23 19:44:49 +00:00
|
|
|
mOuterFadeUpLen + mInnerFadeUpLen);
|
|
|
|
if (fadeUpSamples < 1)
|
|
|
|
fadeUpSamples = 1;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-25 12:53:59 +00:00
|
|
|
float fadeDownStep = mDuckAmountDb / fadeDownSamples.as_double();
|
|
|
|
float fadeUpStep = mDuckAmountDb / fadeUpSamples.as_double();
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
while (pos < end)
|
|
|
|
{
|
2016-08-21 22:05:43 +00:00
|
|
|
const auto len = limitSampleBufferSize( kBufSize, end - pos );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2021-05-23 21:43:38 +00:00
|
|
|
t->GetFloats(buf.get(), pos, len);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-08-24 15:24:26 +00:00
|
|
|
for (auto i = pos; i < pos + len; i++)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-25 12:53:59 +00:00
|
|
|
float gainDown = fadeDownStep * (i - start).as_float();
|
|
|
|
float gainUp = fadeUpStep * (end - i).as_float();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
float gain;
|
|
|
|
if (gainDown > gainUp)
|
|
|
|
gain = gainDown;
|
|
|
|
else
|
|
|
|
gain = gainUp;
|
|
|
|
if (gain < mDuckAmountDb)
|
|
|
|
gain = mDuckAmountDb;
|
|
|
|
|
2016-08-31 04:49:22 +00:00
|
|
|
// i - pos is bounded by len:
|
|
|
|
buf[ ( i - pos ).as_size_t() ] *= DB_TO_LINEAR(gain);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
t->Set((samplePtr)buf.get(), floatSample, pos, len);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
pos += len;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
float curTime = t->LongSamplesToTime(pos);
|
|
|
|
float fractionFinished = (curTime - mT0) / (mT1 - mT0);
|
2018-02-09 19:12:25 +00:00
|
|
|
if (TotalProgress( (trackNum + 1 + fractionFinished) /
|
2010-01-23 19:44:49 +00:00
|
|
|
(GetNumWaveTracks() + 1) ))
|
|
|
|
{
|
|
|
|
cancel = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cancel;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuck::OnValueChanged(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mPanel->Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-04-17 03:53:42 +00:00
|
|
|
* EffectAutoDuckPanel implementation
|
2010-01-23 19:44:49 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define CONTROL_POINT_REGION 10 // pixel distance to click on a control point
|
|
|
|
#define CONTROL_POINT_MIN_MOVE 5 // min mouse move until value is changed
|
|
|
|
|
|
|
|
#define TEXT_DISTANCE 15 // pixel distance text <-> center of control point
|
|
|
|
|
|
|
|
#define FADE_DOWN_START 150 // x coordinate
|
|
|
|
#define FADE_UP_START 450 // x coordinate
|
|
|
|
#define DUCK_AMOUNT_START 50 // y coordinate
|
|
|
|
|
|
|
|
#define FADE_SCALE 40 // scale factor for second -> pixel conversion
|
|
|
|
#define DUCK_AMOUNT_SCALE 8 // scale factor for db -> pixel conversion
|
|
|
|
|
|
|
|
static int GetDistance(const wxPoint& first, const wxPoint& second)
|
|
|
|
{
|
|
|
|
int distanceX = abs(first.x - second.x);
|
|
|
|
int distanceY = abs(first.y - second.y);
|
|
|
|
if (distanceX > distanceY)
|
|
|
|
return distanceX;
|
|
|
|
else
|
|
|
|
return distanceY;
|
|
|
|
}
|
|
|
|
|
2016-06-25 18:18:23 +00:00
|
|
|
BEGIN_EVENT_TABLE(EffectAutoDuckPanel, wxPanelWrapper)
|
2010-01-23 19:44:49 +00:00
|
|
|
EVT_PAINT(EffectAutoDuckPanel::OnPaint)
|
|
|
|
EVT_MOUSE_CAPTURE_CHANGED(EffectAutoDuckPanel::OnMouseCaptureChanged)
|
|
|
|
EVT_MOUSE_CAPTURE_LOST(EffectAutoDuckPanel::OnMouseCaptureLost)
|
|
|
|
EVT_LEFT_DOWN(EffectAutoDuckPanel::OnLeftDown)
|
|
|
|
EVT_LEFT_UP(EffectAutoDuckPanel::OnLeftUp)
|
|
|
|
EVT_MOTION(EffectAutoDuckPanel::OnMotion)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
|
2017-10-20 16:06:29 +00:00
|
|
|
EffectAutoDuckPanel::EffectAutoDuckPanel(
|
|
|
|
wxWindow *parent, wxWindowID winid, EffectAutoDuck *effect)
|
|
|
|
: wxPanelWrapper(parent, winid, wxDefaultPosition, wxSize(600, 300))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mParent = parent;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect = effect;
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurrentControlPoint = none;
|
|
|
|
mBackgroundBitmap = NULL;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
ResetControlPoints();
|
|
|
|
}
|
|
|
|
|
|
|
|
EffectAutoDuckPanel::~EffectAutoDuckPanel()
|
|
|
|
{
|
2016-04-25 03:56:15 +00:00
|
|
|
if(HasCapture())
|
|
|
|
ReleaseMouse();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EffectAutoDuckPanel::ResetControlPoints()
|
|
|
|
{
|
|
|
|
mControlPoints[innerFadeDown] = wxPoint(-100,-100);
|
|
|
|
mControlPoints[innerFadeUp] = wxPoint(-100,-100);
|
|
|
|
mControlPoints[outerFadeDown] = wxPoint(-100,-100);
|
|
|
|
mControlPoints[outerFadeUp] = wxPoint(-100,-100);
|
|
|
|
mControlPoints[duckAmount] = wxPoint(-100,-100);
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuckPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
int clientWidth, clientHeight;
|
|
|
|
GetSize(&clientWidth, &clientHeight);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (!mBackgroundBitmap || mBackgroundBitmap->GetWidth() != clientWidth ||
|
|
|
|
mBackgroundBitmap->GetHeight() != clientHeight)
|
|
|
|
{
|
2018-08-03 17:29:49 +00:00
|
|
|
mBackgroundBitmap = std::make_unique<wxBitmap>(clientWidth, clientHeight,24);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxMemoryDC dc;
|
|
|
|
dc.SelectObject(*mBackgroundBitmap);
|
|
|
|
|
|
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
|
|
|
dc.SetPen(*wxBLACK_PEN);
|
|
|
|
dc.DrawRectangle(0, 0, clientWidth, clientHeight);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
dc.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
|
|
|
|
wxFONTWEIGHT_NORMAL));
|
|
|
|
dc.SetTextForeground(*wxBLACK);
|
|
|
|
dc.SetTextBackground(*wxWHITE);
|
|
|
|
|
|
|
|
double duckAmountDb = 0;
|
|
|
|
double innerFadeDownLen = 0;
|
|
|
|
double innerFadeUpLen = 0;
|
|
|
|
double outerFadeDownLen = 0;
|
|
|
|
double outerFadeUpLen = 0;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->mDuckAmountDbBox->GetValue().ToDouble(&duckAmountDb);
|
|
|
|
mEffect->mInnerFadeDownLenBox->GetValue().ToDouble(&innerFadeDownLen);
|
|
|
|
mEffect->mInnerFadeUpLenBox->GetValue().ToDouble(&innerFadeUpLen);
|
|
|
|
mEffect->mOuterFadeDownLenBox->GetValue().ToDouble(&outerFadeDownLen);
|
|
|
|
mEffect->mOuterFadeUpLenBox->GetValue().ToDouble(&outerFadeUpLen);
|
|
|
|
|
|
|
|
if (innerFadeDownLen < MIN_InnerFadeDownLen || innerFadeDownLen > MAX_InnerFadeDownLen ||
|
|
|
|
innerFadeUpLen < MIN_InnerFadeUpLen || innerFadeUpLen > MAX_InnerFadeUpLen ||
|
|
|
|
outerFadeDownLen < MIN_OuterFadeDownLen || outerFadeDownLen > MAX_OuterFadeDownLen ||
|
|
|
|
outerFadeUpLen < MIN_OuterFadeUpLen || outerFadeUpLen > MAX_OuterFadeUpLen ||
|
|
|
|
duckAmountDb < MIN_DuckAmountDb || duckAmountDb > MAX_DuckAmountDb)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// values are out of range, no preview available
|
2017-09-30 16:19:44 +00:00
|
|
|
wxString message = _("Preview not available");
|
2010-01-23 19:44:49 +00:00
|
|
|
int textWidth = 0, textHeight = 0;
|
|
|
|
dc.GetTextExtent(message, &textWidth, &textHeight);
|
|
|
|
dc.DrawText(message, (clientWidth - textWidth) / 2,
|
|
|
|
(clientHeight - textHeight) / 2);
|
|
|
|
|
|
|
|
ResetControlPoints();
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
// draw preview
|
|
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
2018-07-23 17:21:15 +00:00
|
|
|
dc.SetPen(wxPen(theTheme.Colour(clrGraphLines), 3, wxPENSTYLE_SOLID));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
wxPoint points[6];
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
points[0].x = 10;
|
|
|
|
points[0].y = DUCK_AMOUNT_START;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
points[1].x = FADE_DOWN_START - (int)(outerFadeDownLen * FADE_SCALE);
|
|
|
|
points[1].y = DUCK_AMOUNT_START;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
points[2].x = FADE_DOWN_START + (int)(innerFadeDownLen * FADE_SCALE);
|
|
|
|
points[2].y = DUCK_AMOUNT_START -
|
|
|
|
(int)(duckAmountDb * DUCK_AMOUNT_SCALE);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
points[3].x = FADE_UP_START - (int)(innerFadeUpLen * FADE_SCALE);
|
|
|
|
points[3].y = DUCK_AMOUNT_START -
|
|
|
|
(int)(duckAmountDb * DUCK_AMOUNT_SCALE);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
points[4].x = FADE_UP_START + (int)(outerFadeUpLen * FADE_SCALE);
|
|
|
|
points[4].y = DUCK_AMOUNT_START;
|
|
|
|
|
|
|
|
points[5].x = clientWidth - 10;
|
|
|
|
points[5].y = DUCK_AMOUNT_START;
|
|
|
|
|
Fix cross-platform one-pixel differences in line drawing...
... Make all line drawing go through our AColor which wraps the wxDC
line-drawing functions.
Despite what wxWidgets documentation says, the functions still don't
consistently include the first point and omit the last point of a line. I
observed inclusion of both on Mac, while on Windows, omission sometimes of the
first point instead of the last.
Discrepancies could be observed in 2.3.0 between Windows and Mac, using the
Window magnifier or command+alt+ +, zooming in closely on the ends of the
shadow lines below and right of tracks, or at the bottom-right corners of the
bevels drawn around vertical rulers.
So where there is an observable one-pixel difference of drawing between
platforms, there is the question, which was the intent when the drawing code
was written? Should the coordinates be corrected by one or not?
I reviewed each case and used my judgment.
Most of the calls are in drawing rulers. Major tick lines were drawn five
pixels long, and minor, three, on Mac. I keep this behavior, but you might
argue for making them four and two.
On the other hand the drawing of ruler grid lines, which you can see in
the equalization and frequency analysis windows, did need a one-pixel correction
to avoid straying out of bounds.
2018-11-17 14:25:26 +00:00
|
|
|
AColor::Lines(dc, 6, points);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2018-07-23 17:21:15 +00:00
|
|
|
dc.SetPen(wxPen(*wxBLACK, 1, wxPENSTYLE_DOT));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
AColor::Line(dc, FADE_DOWN_START, 10, FADE_DOWN_START, clientHeight - 10);
|
|
|
|
AColor::Line(dc, FADE_UP_START, 10, FADE_UP_START, clientHeight - 10);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
dc.SetPen(AColor::envelopePen);
|
|
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mControlPoints[outerFadeDown] = points[1];
|
|
|
|
mControlPoints[innerFadeDown] = points[2];
|
|
|
|
mControlPoints[innerFadeUp] = points[3];
|
|
|
|
mControlPoints[outerFadeUp] = points[4];
|
|
|
|
mControlPoints[duckAmount] = wxPoint(
|
|
|
|
(points[2].x + points[3].x) / 2, points[2].y);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
|
|
|
|
{
|
|
|
|
EControlPoint cp = (EControlPoint)i;
|
|
|
|
int digits;
|
|
|
|
float value;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (cp == innerFadeDown)
|
|
|
|
{
|
|
|
|
value = innerFadeDownLen;
|
|
|
|
digits = 2;
|
|
|
|
}
|
|
|
|
else if (cp == innerFadeUp)
|
|
|
|
{
|
|
|
|
value = innerFadeUpLen;
|
|
|
|
digits = 2;
|
|
|
|
}
|
|
|
|
else if (cp == outerFadeDown)
|
|
|
|
{
|
|
|
|
value = outerFadeDownLen;
|
|
|
|
digits = 2;
|
|
|
|
} else if (cp == outerFadeUp)
|
|
|
|
{
|
|
|
|
value = outerFadeUpLen;
|
|
|
|
digits = 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value = duckAmountDb;
|
|
|
|
digits = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString valueStr = Internat::ToDisplayString(value, digits);
|
|
|
|
valueStr += wxT(" ");
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (cp == duckAmount)
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: short form of 'decibels'.*/
|
2014-06-03 20:30:19 +00:00
|
|
|
valueStr += _("dB");
|
2010-01-23 19:44:49 +00:00
|
|
|
else
|
2012-03-20 15:36:02 +00:00
|
|
|
/* i18n-hint: short form of 'seconds'.*/
|
2014-06-03 20:30:19 +00:00
|
|
|
valueStr += _("s");
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int textWidth = 0, textHeight = 0;
|
|
|
|
GetTextExtent(valueStr, &textWidth, &textHeight);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int textPosX = mControlPoints[i].x - textWidth / 2;
|
|
|
|
int textPosY = mControlPoints[i].y;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (cp == duckAmount || cp == outerFadeDown || cp == outerFadeUp)
|
|
|
|
textPosY -= TEXT_DISTANCE + textHeight;
|
|
|
|
else
|
|
|
|
textPosY += TEXT_DISTANCE;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
dc.DrawText(valueStr, textPosX, textPosY);
|
|
|
|
|
|
|
|
dc.DrawEllipse(mControlPoints[i].x - 3,
|
|
|
|
mControlPoints[i].y - 3, 6, 6);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy background buffer to paint dc
|
|
|
|
wxPaintDC paintDC(this);
|
|
|
|
paintDC.Blit(0, 0, clientWidth, clientHeight, &dc, 0, 0);
|
|
|
|
|
|
|
|
// clean up: necessary to free resources on Windows
|
|
|
|
dc.SetPen(wxNullPen);
|
|
|
|
dc.SetBrush(wxNullBrush);
|
|
|
|
dc.SetFont(wxNullFont);
|
|
|
|
dc.SelectObject(wxNullBitmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectAutoDuckPanel::OnMouseCaptureChanged(
|
2015-04-17 03:53:42 +00:00
|
|
|
wxMouseCaptureChangedEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
SetCursor(wxNullCursor);
|
|
|
|
mCurrentControlPoint = none;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectAutoDuckPanel::OnMouseCaptureLost(
|
2015-04-17 03:53:42 +00:00
|
|
|
wxMouseCaptureLostEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mCurrentControlPoint = none;
|
|
|
|
|
|
|
|
if (HasCapture())
|
|
|
|
{
|
|
|
|
ReleaseMouse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EffectAutoDuckPanel::EControlPoint
|
2015-04-17 03:53:42 +00:00
|
|
|
EffectAutoDuckPanel::GetNearestControlPoint(const wxPoint & pt)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
int dist[AUTO_DUCK_PANEL_NUM_CONTROL_POINTS];
|
|
|
|
int i;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
|
|
|
|
dist[i] = GetDistance(pt, mControlPoints[i]);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
int curMinimum = 0;
|
|
|
|
for (i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
|
|
|
|
if (dist[i] < dist[curMinimum])
|
|
|
|
curMinimum = i;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (dist[curMinimum] <= CONTROL_POINT_REGION)
|
|
|
|
return (EControlPoint)curMinimum;
|
|
|
|
else
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuckPanel::OnLeftDown(wxMouseEvent & evt)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
EControlPoint nearest = GetNearestControlPoint(evt.GetPosition());
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (nearest != none)
|
|
|
|
{
|
|
|
|
// this control point has been clicked
|
|
|
|
mMouseDownPoint = evt.GetPosition();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurrentControlPoint = nearest;
|
|
|
|
mControlPointMoveActivated = false;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
for (int i = 0; i < AUTO_DUCK_PANEL_NUM_CONTROL_POINTS; i++)
|
|
|
|
mMoveStartControlPoints[i] = mControlPoints[i];
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-07-17 08:38:23 +00:00
|
|
|
if( !HasCapture() )
|
|
|
|
CaptureMouse();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuckPanel::OnLeftUp(wxMouseEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
if (mCurrentControlPoint != none)
|
|
|
|
{
|
|
|
|
mCurrentControlPoint = none;
|
|
|
|
ReleaseMouse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectAutoDuckPanel::OnMotion(wxMouseEvent & evt)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
switch (GetNearestControlPoint(evt.GetPosition()))
|
|
|
|
{
|
|
|
|
case none:
|
|
|
|
SetCursor(wxNullCursor);
|
|
|
|
break;
|
|
|
|
case innerFadeDown:
|
|
|
|
case innerFadeUp:
|
|
|
|
case outerFadeDown:
|
|
|
|
case outerFadeUp:
|
|
|
|
SetCursor(wxCursor(wxCURSOR_SIZEWE));
|
|
|
|
break;
|
|
|
|
case duckAmount:
|
|
|
|
SetCursor(wxCursor(wxCURSOR_SIZENS));
|
|
|
|
break;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCurrentControlPoint != none)
|
|
|
|
{
|
|
|
|
if (!mControlPointMoveActivated)
|
|
|
|
{
|
|
|
|
int dist;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
if (mCurrentControlPoint == duckAmount)
|
|
|
|
dist = abs(evt.GetY() - mMouseDownPoint.y);
|
|
|
|
else
|
|
|
|
dist = abs(evt.GetX() - mMouseDownPoint.x);
|
|
|
|
|
|
|
|
if (dist >= CONTROL_POINT_MIN_MOVE)
|
|
|
|
mControlPointMoveActivated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mControlPointMoveActivated)
|
|
|
|
{
|
|
|
|
float newValue;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
switch (mCurrentControlPoint)
|
|
|
|
{
|
|
|
|
case outerFadeDown:
|
|
|
|
newValue = ((double)(FADE_DOWN_START - evt.GetX())) / FADE_SCALE;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->mOuterFadeDownLen = TrapDouble(newValue, MIN_OuterFadeDownLen, MAX_OuterFadeDownLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case outerFadeUp:
|
|
|
|
newValue = ((double)(evt.GetX() - FADE_UP_START)) / FADE_SCALE;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->mOuterFadeUpLen = TrapDouble(newValue, MIN_OuterFadeUpLen, MAX_OuterFadeUpLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case innerFadeDown:
|
|
|
|
newValue = ((double)(evt.GetX() - FADE_DOWN_START)) / FADE_SCALE;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->mInnerFadeDownLen = TrapDouble(newValue, MIN_InnerFadeDownLen, MAX_InnerFadeDownLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case innerFadeUp:
|
|
|
|
newValue = ((double)(FADE_UP_START - evt.GetX())) / FADE_SCALE;
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->mInnerFadeUpLen = TrapDouble(newValue, MIN_InnerFadeUpLen, MAX_InnerFadeUpLen);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case duckAmount:
|
2015-04-17 03:53:42 +00:00
|
|
|
newValue = ((double)(DUCK_AMOUNT_START - evt.GetY())) / DUCK_AMOUNT_SCALE;
|
|
|
|
mEffect->mDuckAmountDb = TrapDouble(newValue, MIN_DuckAmountDb, MAX_DuckAmountDb);
|
2010-01-23 19:44:49 +00:00
|
|
|
break;
|
|
|
|
case none:
|
|
|
|
wxASSERT(false); // should not happen
|
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
mEffect->TransferDataToWindow();
|
2010-01-23 19:44:49 +00:00
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|