2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
Normalize.cpp
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
Vaughan Johnson (Preview)
|
|
|
|
|
|
|
|
*******************************************************************//**
|
|
|
|
|
|
|
|
\class EffectNormalize
|
2015-05-09 16:36:54 +00:00
|
|
|
\brief An Effect to bring the peak level up to a chosen level.
|
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 "Normalize.h"
|
2019-01-17 23:31:08 +00:00
|
|
|
#include "LoadEffects.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
2018-11-11 22:30:55 +00:00
|
|
|
#include <wx/checkbox.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/intl.h>
|
2018-11-11 21:41:45 +00:00
|
|
|
#include <wx/stattext.h>
|
2015-04-17 03:53:42 +00:00
|
|
|
#include <wx/valgen.h>
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../Prefs.h"
|
2019-06-05 15:54:13 +00:00
|
|
|
#include "../ProjectFileManager.h"
|
2019-02-06 18:44:52 +00:00
|
|
|
#include "../Shuttle.h"
|
2015-04-17 03:53:42 +00:00
|
|
|
#include "../ShuttleGui.h"
|
|
|
|
#include "../WaveTrack.h"
|
|
|
|
#include "../widgets/valnum.h"
|
2019-03-31 20:12:07 +00:00
|
|
|
#include "../widgets/ProgressDialog.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Define keys, defaults, minimums, and maximums for the effect parameters
|
|
|
|
//
|
2018-07-24 15:35:18 +00:00
|
|
|
// Name Type Key Def Min Max Scale
|
2018-07-26 15:01:27 +00:00
|
|
|
Param( PeakLevel, double, wxT("PeakLevel"), -1.0, -145.0, 0.0, 1 );
|
2018-07-24 15:35:18 +00:00
|
|
|
Param( RemoveDC, bool, wxT("RemoveDcOffset"), true, false, true, 1 );
|
|
|
|
Param( ApplyGain, bool, wxT("ApplyGain"), true, false, true, 1 );
|
|
|
|
Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 );
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2019-01-17 22:33:49 +00:00
|
|
|
const ComponentInterfaceSymbol EffectNormalize::Symbol
|
|
|
|
{ XO("Normalize") };
|
|
|
|
|
2019-01-17 23:31:08 +00:00
|
|
|
namespace{ BuiltinEffectsModule::Registration< EffectNormalize > reg; }
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler)
|
|
|
|
EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI)
|
|
|
|
EVT_TEXT(wxID_ANY, EffectNormalize::OnUpdateUI)
|
|
|
|
END_EVENT_TABLE()
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
EffectNormalize::EffectNormalize()
|
|
|
|
{
|
2018-07-26 15:01:27 +00:00
|
|
|
mPeakLevel = DEF_PeakLevel;
|
2015-04-17 03:53:42 +00:00
|
|
|
mDC = DEF_RemoveDC;
|
|
|
|
mGain = DEF_ApplyGain;
|
|
|
|
mStereoInd = DEF_StereoInd;
|
2015-05-15 11:47:51 +00:00
|
|
|
|
|
|
|
SetLinearEffectFlag(false);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
EffectNormalize::~EffectNormalize()
|
|
|
|
{
|
|
|
|
}
|
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 EffectNormalize::GetSymbol()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2019-01-17 22:33:49 +00:00
|
|
|
return Symbol;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 18:53:48 +00:00
|
|
|
TranslatableString EffectNormalize::GetDescription()
|
2014-06-03 20:30:19 +00:00
|
|
|
{
|
2019-12-08 18:53:48 +00:00
|
|
|
return XO("Sets the peak amplitude of one or more tracks");
|
2014-06-03 20:30:19 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2021-06-06 16:18:35 +00:00
|
|
|
ManualPageID EffectNormalize::ManualPage()
|
2017-05-20 13:40:09 +00:00
|
|
|
{
|
2021-06-06 16:18:35 +00:00
|
|
|
return L"Normalize";
|
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 EffectNormalize::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 EffectNormalize::DefineParams( ShuttleParams & S ){
|
2018-07-26 15:01:27 +00:00
|
|
|
S.SHUTTLE_PARAM( mPeakLevel, PeakLevel );
|
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
|
|
|
S.SHUTTLE_PARAM( mGain, ApplyGain );
|
|
|
|
S.SHUTTLE_PARAM( mDC, RemoveDC );
|
|
|
|
S.SHUTTLE_PARAM( mStereoInd, StereoInd );
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectNormalize::GetAutomationParameters(CommandParameters & parms)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-07-26 15:01:27 +00:00
|
|
|
parms.Write(KEY_PeakLevel, mPeakLevel);
|
2015-04-17 03:53:42 +00:00
|
|
|
parms.Write(KEY_ApplyGain, mGain);
|
|
|
|
parms.Write(KEY_RemoveDC, mDC);
|
|
|
|
parms.Write(KEY_StereoInd, mStereoInd);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-21 14:24:25 +00:00
|
|
|
bool EffectNormalize::SetAutomationParameters(CommandParameters & parms)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-07-26 15:01:27 +00:00
|
|
|
ReadAndVerifyDouble(PeakLevel);
|
2015-04-17 03:53:42 +00:00
|
|
|
ReadAndVerifyBool(ApplyGain);
|
|
|
|
ReadAndVerifyBool(RemoveDC);
|
|
|
|
ReadAndVerifyBool(StereoInd);
|
|
|
|
|
2018-07-26 15:01:27 +00:00
|
|
|
mPeakLevel = PeakLevel;
|
2015-04-17 03:53:42 +00:00
|
|
|
mGain = ApplyGain;
|
|
|
|
mDC = RemoveDC;
|
|
|
|
mStereoInd = StereoInd;
|
|
|
|
|
|
|
|
return true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Effect implementation
|
|
|
|
|
|
|
|
bool EffectNormalize::CheckWhetherSkipEffect()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
return ((mGain == false) && (mDC == false));
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
bool EffectNormalize::Startup()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2015-04-17 03:53:42 +00:00
|
|
|
wxString base = wxT("/Effects/Normalize/");
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Migrate settings from 2.1.0 or before
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Already migrated, so bail
|
|
|
|
if (gPrefs->Exists(base + wxT("Migrated")))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Load the old "current" settings
|
|
|
|
if (gPrefs->Exists(base))
|
|
|
|
{
|
|
|
|
int boolProxy = gPrefs->Read(base + wxT("RemoveDcOffset"), 1);
|
|
|
|
mDC = (boolProxy == 1);
|
|
|
|
boolProxy = gPrefs->Read(base + wxT("Normalize"), 1);
|
|
|
|
mGain = (boolProxy == 1);
|
2018-07-26 15:01:27 +00:00
|
|
|
gPrefs->Read(base + wxT("Level"), &mPeakLevel, -1.0);
|
|
|
|
if(mPeakLevel > 0.0) // this should never happen
|
|
|
|
mPeakLevel = -mPeakLevel;
|
2015-04-17 03:53:42 +00:00
|
|
|
boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L);
|
|
|
|
mStereoInd = (boolProxy == 1);
|
|
|
|
|
|
|
|
SaveUserPreset(GetCurrentSettingsGroup());
|
|
|
|
|
|
|
|
// Do not migrate again
|
|
|
|
gPrefs->Write(base + wxT("Migrated"), true);
|
|
|
|
gPrefs->Flush();
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
return true;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectNormalize::Process()
|
|
|
|
{
|
2012-05-03 21:54:18 +00:00
|
|
|
if (mGain == false && mDC == false)
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
|
2012-05-19 20:30:57 +00:00
|
|
|
float ratio;
|
|
|
|
if( mGain )
|
2018-07-24 15:35:18 +00:00
|
|
|
{
|
2018-08-22 19:45:50 +00:00
|
|
|
// same value used for all tracks
|
|
|
|
ratio = DB_TO_LINEAR(TrapDouble(mPeakLevel, MIN_PeakLevel, MAX_PeakLevel));
|
2018-07-24 15:35:18 +00:00
|
|
|
}
|
2018-08-22 19:45:50 +00:00
|
|
|
else {
|
2012-05-19 20:30:57 +00:00
|
|
|
ratio = 1.0;
|
2018-08-22 19:45:50 +00:00
|
|
|
}
|
2012-05-03 21:54:18 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Iterate over each track
|
|
|
|
this->CopyInputTracks(); // Set up mOutputTracks.
|
|
|
|
bool bGoodResult = true;
|
2018-07-24 15:20:52 +00:00
|
|
|
double progress = 0;
|
2019-12-08 03:37:02 +00:00
|
|
|
TranslatableString topMsg;
|
2014-10-25 08:25:22 +00:00
|
|
|
if(mDC && mGain)
|
2019-12-08 03:37:02 +00:00
|
|
|
topMsg = XO("Removing DC offset and Normalizing...\n");
|
2014-10-25 08:25:22 +00:00
|
|
|
else if(mDC && !mGain)
|
2019-12-08 03:37:02 +00:00
|
|
|
topMsg = XO("Removing DC offset...\n");
|
2014-10-25 08:25:22 +00:00
|
|
|
else if(!mDC && mGain)
|
2019-12-08 03:37:02 +00:00
|
|
|
topMsg = XO("Normalizing without removing DC offset...\n");
|
2014-10-25 08:25:22 +00:00
|
|
|
else if(!mDC && !mGain)
|
2019-12-08 03:37:02 +00:00
|
|
|
topMsg = XO("Not doing anything...\n"); // shouldn't get here
|
2012-05-17 00:17:51 +00:00
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
for ( auto track : mOutputTracks->Selected< WaveTrack >()
|
|
|
|
+ ( mStereoInd ? &Track::Any : &Track::IsLeader ) ) {
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get start and end times from track
|
2017-01-08 03:07:23 +00:00
|
|
|
// PRL: No accounting for multiple channels?
|
2010-01-23 19:44:49 +00:00
|
|
|
double trackStart = track->GetStartTime();
|
|
|
|
double trackEnd = track->GetEndTime();
|
|
|
|
|
|
|
|
//Set the current bounds to whichever left marker is
|
|
|
|
//greater and whichever right marker is less:
|
|
|
|
mCurT0 = mT0 < trackStart? trackStart: mT0;
|
|
|
|
mCurT1 = mT1 > trackEnd? trackEnd: mT1;
|
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
auto range = mStereoInd
|
|
|
|
? TrackList::SingletonRange(track)
|
|
|
|
: TrackList::Channels(track);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Process only if the right marker is to the right of the left marker
|
|
|
|
if (mCurT1 > mCurT0) {
|
2017-01-08 03:07:23 +00:00
|
|
|
wxString trackName = track->GetName();
|
2012-05-17 00:17:51 +00:00
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
float extent;
|
2019-03-15 15:22:53 +00:00
|
|
|
// Will compute a maximum
|
|
|
|
extent = std::numeric_limits<float>::lowest();
|
2017-01-08 03:07:23 +00:00
|
|
|
std::vector<float> offsets;
|
|
|
|
|
2019-12-08 03:37:02 +00:00
|
|
|
auto msg = (range.size() == 1)
|
2017-01-07 19:49:20 +00:00
|
|
|
// mono or 'stereo tracks independently'
|
2019-12-08 03:37:02 +00:00
|
|
|
? topMsg +
|
|
|
|
XO("Analyzing: %s").Format( trackName )
|
|
|
|
: topMsg +
|
2017-01-08 03:07:23 +00:00
|
|
|
// TODO: more-than-two-channels-message
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Analyzing first track of stereo pair: %s").Format( trackName );
|
2017-01-08 03:07:23 +00:00
|
|
|
|
|
|
|
// Analysis loop over channels collects offsets and extent
|
|
|
|
for (auto channel : range) {
|
2018-10-30 15:58:15 +00:00
|
|
|
float offset = 0;
|
|
|
|
float extent2 = 0;
|
2017-01-08 03:07:23 +00:00
|
|
|
bGoodResult =
|
|
|
|
AnalyseTrack( channel, msg, progress, offset, extent2 );
|
|
|
|
if ( ! bGoodResult )
|
|
|
|
goto break2;
|
2019-03-15 15:22:53 +00:00
|
|
|
extent = std::max( extent, extent2 );
|
2017-01-08 03:07:23 +00:00
|
|
|
offsets.push_back(offset);
|
|
|
|
// TODO: more-than-two-channels-message
|
|
|
|
msg = topMsg +
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Analyzing second track of stereo pair: %s").Format( trackName );
|
2011-06-09 23:22:10 +00:00
|
|
|
}
|
2018-07-24 15:35:18 +00:00
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
// Compute the multiplier using extent
|
|
|
|
if( (extent > 0) && mGain ) {
|
|
|
|
mMult = ratio / extent;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mMult = 1.0;
|
2018-07-24 15:35:18 +00:00
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
if (range.size() == 1) {
|
|
|
|
if (TrackList::Channels(track).size() == 1)
|
|
|
|
// really mono
|
|
|
|
msg = topMsg +
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Processing: %s").Format( trackName );
|
2012-05-03 21:54:18 +00:00
|
|
|
else
|
2017-01-08 03:07:23 +00:00
|
|
|
//'stereo tracks independently'
|
|
|
|
// TODO: more-than-two-channels-message
|
|
|
|
msg = topMsg +
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Processing stereo channels independently: %s").Format( trackName );
|
2017-01-08 03:07:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
msg = topMsg +
|
|
|
|
// TODO: more-than-two-channels-message
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Processing first track of stereo pair: %s").Format( trackName );
|
2017-01-08 03:07:23 +00:00
|
|
|
|
|
|
|
// Use multiplier in the second, processing loop over channels
|
|
|
|
auto pOffset = offsets.begin();
|
|
|
|
for (auto channel : range) {
|
|
|
|
if (false ==
|
|
|
|
(bGoodResult = ProcessOne(channel, msg, progress, *pOffset++)) )
|
|
|
|
goto break2;
|
|
|
|
// TODO: more-than-two-channels-message
|
|
|
|
msg = topMsg +
|
2019-12-08 03:37:02 +00:00
|
|
|
XO("Processing second track of stereo pair: %s").Format( trackName );
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-08 03:07:23 +00:00
|
|
|
break2:
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
this->ReplaceProcessedTracks(bGoodResult);
|
2010-01-23 19:44:49 +00:00
|
|
|
return bGoodResult;
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectNormalize::PopulateOrExchange(ShuttleGui & S)
|
|
|
|
{
|
|
|
|
mCreating = true;
|
|
|
|
|
|
|
|
S.StartVerticalLay(0);
|
|
|
|
{
|
|
|
|
S.StartMultiColumn(2, wxALIGN_CENTER);
|
|
|
|
{
|
|
|
|
S.StartVerticalLay(false);
|
|
|
|
{
|
2017-10-30 16:23:41 +00:00
|
|
|
mDCCheckBox = S.Validator<wxGenericValidator>(&mDC)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddCheckBox(XXO("&Remove DC offset (center on 0.0 vertically)"),
|
2018-02-02 02:37:34 +00:00
|
|
|
mDC);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2018-07-24 15:35:18 +00:00
|
|
|
S.StartHorizontalLay(wxALIGN_LEFT, false);
|
2015-04-17 03:53:42 +00:00
|
|
|
{
|
2018-01-31 01:03:46 +00:00
|
|
|
mGainCheckBox = S
|
2018-01-31 20:31:22 +00:00
|
|
|
.MinSize()
|
2017-10-30 16:23:41 +00:00
|
|
|
.Validator<wxGenericValidator>(&mGain)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddCheckBox(XXO("&Normalize peak amplitude to "),
|
2018-01-31 01:03:46 +00:00
|
|
|
mGain);
|
2015-04-17 03:53:42 +00:00
|
|
|
|
2017-10-30 16:23:41 +00:00
|
|
|
mLevelTextCtrl = S
|
2017-10-29 14:27:23 +00:00
|
|
|
.Name(XO("Peak amplitude dB"))
|
2017-10-30 16:23:41 +00:00
|
|
|
.Validator<FloatingPointValidator<double>>(
|
|
|
|
2,
|
|
|
|
&mPeakLevel,
|
|
|
|
NumValidatorStyle::ONE_TRAILING_ZERO,
|
|
|
|
MIN_PeakLevel,
|
|
|
|
MAX_PeakLevel
|
|
|
|
)
|
|
|
|
.AddTextBox( {}, wxT(""), 10);
|
2019-12-22 22:01:20 +00:00
|
|
|
mLeveldB = S.AddVariableText(XO("dB"), false,
|
|
|
|
wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
|
2017-09-28 01:20:14 +00:00
|
|
|
mWarning = S.AddVariableText( {}, false,
|
2019-12-22 22:01:20 +00:00
|
|
|
wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndHorizontalLay();
|
2017-10-30 16:23:41 +00:00
|
|
|
|
|
|
|
mStereoIndCheckBox = S
|
|
|
|
.Validator<wxGenericValidator>(&mStereoInd)
|
2020-05-11 15:28:14 +00:00
|
|
|
.AddCheckBox(XXO("N&ormalize stereo channels independently"),
|
2018-02-02 02:37:34 +00:00
|
|
|
mStereoInd);
|
2015-04-17 03:53:42 +00:00
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
}
|
|
|
|
S.EndMultiColumn();
|
|
|
|
}
|
|
|
|
S.EndVerticalLay();
|
|
|
|
mCreating = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectNormalize::TransferDataToWindow()
|
|
|
|
{
|
|
|
|
if (!mUIParent->TransferDataToWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateUI();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EffectNormalize::TransferDataFromWindow()
|
|
|
|
{
|
|
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// EffectNormalize implementation
|
|
|
|
|
2019-12-08 03:37:02 +00:00
|
|
|
bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const TranslatableString &msg,
|
2018-07-24 15:29:51 +00:00
|
|
|
double &progress, float &offset, float &extent)
|
2012-05-03 21:54:18 +00:00
|
|
|
{
|
2018-07-24 15:29:51 +00:00
|
|
|
bool result = true;
|
|
|
|
float min, max;
|
|
|
|
|
2018-07-24 15:35:18 +00:00
|
|
|
if(mGain)
|
|
|
|
{
|
2019-03-15 15:22:53 +00:00
|
|
|
// set mMin, mMax. No progress bar here as it's fast.
|
|
|
|
auto pair = track->GetMinMax(mCurT0, mCurT1); // may throw
|
|
|
|
min = pair.first, max = pair.second;
|
2016-12-25 13:40:15 +00:00
|
|
|
|
2019-03-15 15:22:53 +00:00
|
|
|
if(mDC)
|
|
|
|
{
|
2020-03-21 15:14:13 +00:00
|
|
|
result = AnalyseTrackData(track, msg, progress, offset);
|
2019-03-15 15:22:53 +00:00
|
|
|
min += offset;
|
|
|
|
max += offset;
|
2018-07-24 15:35:18 +00:00
|
|
|
}
|
2012-05-23 23:02:25 +00:00
|
|
|
}
|
2018-07-24 15:35:18 +00:00
|
|
|
else if(mDC)
|
|
|
|
{
|
|
|
|
min = -1.0, max = 1.0; // sensible defaults?
|
2020-03-21 15:14:13 +00:00
|
|
|
result = AnalyseTrackData(track, msg, progress, offset);
|
2017-01-07 19:49:20 +00:00
|
|
|
min += offset;
|
|
|
|
max += offset;
|
2018-07-24 15:35:18 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-08-22 19:45:50 +00:00
|
|
|
wxFAIL_MSG("Analysing Track when nothing to do!");
|
2018-07-24 15:35:18 +00:00
|
|
|
min = -1.0, max = 1.0; // sensible defaults?
|
2017-01-07 19:49:20 +00:00
|
|
|
offset = 0.0;
|
2012-05-23 23:02:25 +00:00
|
|
|
}
|
2019-03-15 15:22:53 +00:00
|
|
|
extent = fmax(fabs(min), fabs(max));
|
2018-08-22 19:45:50 +00:00
|
|
|
|
2018-07-24 15:29:51 +00:00
|
|
|
return result;
|
2012-05-03 21:54:18 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 15:35:18 +00:00
|
|
|
//AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks,
|
|
|
|
//and executes selected AnalyseOperation on it...
|
2019-12-08 03:37:02 +00:00
|
|
|
bool EffectNormalize::AnalyseTrackData(const WaveTrack * track, const TranslatableString &msg,
|
2020-03-21 15:14:13 +00:00
|
|
|
double &progress, float &offset)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
bool rc = true;
|
2012-05-03 21:54:18 +00:00
|
|
|
|
|
|
|
//Transform the marker timepoints to samples
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = track->TimeToLongSamples(mCurT0);
|
|
|
|
auto end = track->TimeToLongSamples(mCurT1);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get the length of the buffer (as double). len is
|
2012-05-03 21:54:18 +00:00
|
|
|
//used simply 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
|
|
|
|
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
|
|
//be shorter than the length of the track being processed.
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats buffer{ track->GetMaxBlockSize() };
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-07-24 15:35:18 +00:00
|
|
|
mSum = 0.0; // dc offset inits
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-03-25 14:42:14 +00:00
|
|
|
sampleCount blockSamples;
|
|
|
|
sampleCount totalSamples = 0;
|
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Go through the track one buffer at a time. s counts which
|
|
|
|
//sample the current buffer starts at.
|
2016-08-24 15:24:26 +00:00
|
|
|
auto s = start;
|
2012-05-03 21:54:18 +00:00
|
|
|
while (s < end) {
|
|
|
|
//Get a block of samples (smaller than the size of the buffer)
|
|
|
|
//Adjust the block size if it is the final block in the track
|
2016-08-21 22:05:43 +00:00
|
|
|
const auto block = limitSampleBufferSize(
|
|
|
|
track->GetBestBlockSize(s),
|
|
|
|
end - s
|
|
|
|
);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Get the samples from the track and put them in the buffer
|
2021-05-23 21:43:38 +00:00
|
|
|
track->GetFloats(buffer.get(), s, block, fillZero, true, &blockSamples);
|
2018-03-25 14:42:14 +00:00
|
|
|
totalSamples += blockSamples;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Process the buffer.
|
2020-03-21 15:14:13 +00:00
|
|
|
AnalyseDataDC(buffer.get(), block);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Increment s one blockfull of samples
|
|
|
|
s += block;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Update the Progress meter
|
2018-07-24 15:20:52 +00:00
|
|
|
if (TotalProgress(progress +
|
|
|
|
((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
|
2012-05-03 21:54:18 +00:00
|
|
|
rc = false; //lda .. break, not return, so that buffer is deleted
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-03-25 14:42:14 +00:00
|
|
|
if( totalSamples > 0 )
|
|
|
|
offset = -mSum / totalSamples.as_double(); // calculate actual offset (amount that needs to be added on)
|
|
|
|
else
|
|
|
|
offset = 0.0;
|
2012-05-03 21:54:18 +00:00
|
|
|
|
2018-07-24 15:20:52 +00:00
|
|
|
progress += 1.0/double(2*GetNumWaveTracks());
|
2012-05-03 21:54:18 +00:00
|
|
|
//Return true because the effect processing succeeded ... unless cancelled
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
|
|
|
|
//and executes ProcessData, on it...
|
2017-01-07 19:49:20 +00:00
|
|
|
// uses mMult and offset to normalize a track.
|
|
|
|
// mMult must be set before this is called
|
|
|
|
bool EffectNormalize::ProcessOne(
|
2019-12-08 03:37:02 +00:00
|
|
|
WaveTrack * track, const TranslatableString &msg, double &progress, float offset)
|
2012-05-03 21:54:18 +00:00
|
|
|
{
|
|
|
|
bool rc = true;
|
|
|
|
|
|
|
|
//Transform the marker timepoints to samples
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = track->TimeToLongSamples(mCurT0);
|
|
|
|
auto end = track->TimeToLongSamples(mCurT1);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Get the length of the buffer (as double). len is
|
|
|
|
//used simply 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();
|
2012-05-03 21:54:18 +00:00
|
|
|
|
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
|
|
//be shorter than the length of the track being processed.
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats buffer{ track->GetMaxBlockSize() };
|
2012-05-03 21:54:18 +00:00
|
|
|
|
|
|
|
//Go through the track one buffer at a time. s counts which
|
|
|
|
//sample the current buffer starts at.
|
2016-08-24 15:24:26 +00:00
|
|
|
auto s = start;
|
2012-05-03 21:54:18 +00:00
|
|
|
while (s < end) {
|
|
|
|
//Get a block of samples (smaller than the size of the buffer)
|
|
|
|
//Adjust the block size if it is the final block in the track
|
2016-08-21 22:05:43 +00:00
|
|
|
const auto block = limitSampleBufferSize(
|
|
|
|
track->GetBestBlockSize(s),
|
|
|
|
end - s
|
|
|
|
);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Get the samples from the track and put them in the buffer
|
2021-05-23 21:43:38 +00:00
|
|
|
track->GetFloats(buffer.get(), s, block);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Process the buffer.
|
2016-04-14 16:35:15 +00:00
|
|
|
ProcessData(buffer.get(), block, offset);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Copy the newly-changed samples back onto the track.
|
2016-04-14 16:35:15 +00:00
|
|
|
track->Set((samplePtr) buffer.get(), floatSample, s, block);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Increment s one blockfull of samples
|
|
|
|
s += block;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2012-05-03 21:54:18 +00:00
|
|
|
//Update the Progress meter
|
2018-07-24 15:20:52 +00:00
|
|
|
if (TotalProgress(progress +
|
|
|
|
((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
|
2012-05-03 21:54:18 +00:00
|
|
|
rc = false; //lda .. break, not return, so that buffer is deleted
|
|
|
|
break;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-24 15:20:52 +00:00
|
|
|
progress += 1.0/double(2*GetNumWaveTracks());
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Return true because the effect processing succeeded ... unless cancelled
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2018-07-24 15:35:18 +00:00
|
|
|
/// @see AnalyseDataLoudnessDC
|
|
|
|
void EffectNormalize::AnalyseDataDC(float *buffer, size_t len)
|
|
|
|
{
|
|
|
|
for(decltype(len) i = 0; i < len; i++)
|
|
|
|
mSum += (double)buffer[i];
|
|
|
|
}
|
|
|
|
|
2017-01-07 19:49:20 +00:00
|
|
|
void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-24 15:24:26 +00:00
|
|
|
for(decltype(len) i = 0; i < len; i++) {
|
2017-01-07 19:49:20 +00:00
|
|
|
float adjFrame = (buffer[i] + offset) * mMult;
|
2010-01-23 19:44:49 +00:00
|
|
|
buffer[i] = adjFrame;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
UpdateUI();
|
|
|
|
}
|
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
void EffectNormalize::UpdateUI()
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2018-07-26 18:05:31 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
if (!mUIParent->TransferDataFromWindow())
|
2011-06-11 23:08:06 +00:00
|
|
|
{
|
2018-10-15 16:59:02 +00:00
|
|
|
mWarning->SetLabel(_("(Maximum 0dB)"));
|
2015-04-17 03:53:42 +00:00
|
|
|
EnableApply(false);
|
|
|
|
return;
|
2011-06-11 23:08:06 +00:00
|
|
|
}
|
2015-04-17 03:53:42 +00:00
|
|
|
mWarning->SetLabel(wxT(""));
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Disallow level stuff if not normalizing
|
|
|
|
mLevelTextCtrl->Enable(mGain);
|
|
|
|
mLeveldB->Enable(mGain);
|
|
|
|
mStereoIndCheckBox->Enable(mGain);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2015-04-17 03:53:42 +00:00
|
|
|
// Disallow OK/Preview if doing nothing
|
|
|
|
EnableApply(mGain || mDC);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|