mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-07 15:49:42 +02:00
Merge pull request #286 from mmmaisel/master
EBU R128 loudness normalization and normalize progress bar bugfix
This commit is contained in:
commit
e3e2eb99af
@ -1,32 +1,42 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
EffectScienFilter.h
|
||||||
|
|
||||||
|
Norm C
|
||||||
|
Max Maisel
|
||||||
|
|
||||||
|
***********************************************************************/
|
||||||
|
|
||||||
#include "Biquad.h"
|
#include "Biquad.h"
|
||||||
|
|
||||||
#define square(a) ((a)*(a))
|
#define square(a) ((a)*(a))
|
||||||
|
|
||||||
void Biquad_Process (BiquadStruct* pBQ, int iNumSamples)
|
Biquad::Biquad()
|
||||||
|
{
|
||||||
|
pfIn = 0;
|
||||||
|
pfOut = 0;
|
||||||
|
fNumerCoeffs[B0] = 1;
|
||||||
|
fNumerCoeffs[B1] = 0;
|
||||||
|
fNumerCoeffs[B2] = 0;
|
||||||
|
fDenomCoeffs[A1] = 0;
|
||||||
|
fDenomCoeffs[A2] = 0;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Biquad::Reset()
|
||||||
|
{
|
||||||
|
fPrevIn = 0;
|
||||||
|
fPrevPrevIn = 0;
|
||||||
|
fPrevOut = 0;
|
||||||
|
fPrevPrevOut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Biquad::Process(int iNumSamples)
|
||||||
{
|
{
|
||||||
float* pfIn = pBQ->pfIn;
|
|
||||||
float* pfOut = pBQ->pfOut;
|
|
||||||
float fPrevIn = pBQ->fPrevIn;
|
|
||||||
float fPrevPrevIn = pBQ->fPrevPrevIn;
|
|
||||||
float fPrevOut = pBQ->fPrevOut;
|
|
||||||
float fPrevPrevOut = pBQ->fPrevPrevOut;
|
|
||||||
for (int i = 0; i < iNumSamples; i++)
|
for (int i = 0; i < iNumSamples; i++)
|
||||||
{
|
*pfOut++ = ProcessOne(*pfIn++);
|
||||||
float fIn = *pfIn++;
|
|
||||||
*pfOut = fIn * pBQ->fNumerCoeffs [0] +
|
|
||||||
fPrevIn * pBQ->fNumerCoeffs [1] +
|
|
||||||
fPrevPrevIn * pBQ->fNumerCoeffs [2] -
|
|
||||||
fPrevOut * pBQ->fDenomCoeffs [0] -
|
|
||||||
fPrevPrevOut * pBQ->fDenomCoeffs [1];
|
|
||||||
fPrevPrevIn = fPrevIn;
|
|
||||||
fPrevIn = fIn;
|
|
||||||
fPrevPrevOut = fPrevOut;
|
|
||||||
fPrevOut = *pfOut++;
|
|
||||||
}
|
|
||||||
pBQ->fPrevIn = fPrevIn;
|
|
||||||
pBQ->fPrevPrevIn = fPrevPrevIn;
|
|
||||||
pBQ->fPrevOut = fPrevOut;
|
|
||||||
pBQ->fPrevPrevOut = fPrevPrevOut;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI)
|
void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI)
|
||||||
|
@ -1,37 +1,55 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
EffectScienFilter.h
|
||||||
|
|
||||||
|
Norm C
|
||||||
|
Max Maisel
|
||||||
|
|
||||||
|
***********************************************************************/
|
||||||
|
|
||||||
#ifndef __BIQUAD_H__
|
#ifndef __BIQUAD_H__
|
||||||
#define __BIQUAD_H__
|
#define __BIQUAD_H__
|
||||||
|
|
||||||
#if 0
|
struct Biquad
|
||||||
//initialisations not supported in MSVC 2013.
|
{
|
||||||
//Gives error C2905
|
Biquad();
|
||||||
// Do not make conditional on compiler.
|
void Reset();
|
||||||
typedef struct {
|
void Process(int iNumSamples);
|
||||||
float* pfIn {};
|
|
||||||
float* pfOut {};
|
enum
|
||||||
float fNumerCoeffs [3] { 1.0f, 0.0f, 0.0f }; // B0 B1 B2
|
{
|
||||||
float fDenomCoeffs [2] { 0.0f, 0.0f }; // A1 A2
|
/// Numerator coefficient indices
|
||||||
float fPrevIn {};
|
B0=0, B1, B2,
|
||||||
float fPrevPrevIn {};
|
/// Denominator coefficient indices
|
||||||
float fPrevOut {};
|
A1=0, A2
|
||||||
float fPrevPrevOut {};
|
};
|
||||||
} BiquadStruct;
|
|
||||||
#else
|
inline float ProcessOne(float fIn)
|
||||||
// WARNING: This structure may need initialisation.
|
{
|
||||||
typedef struct {
|
float fOut = fIn * fNumerCoeffs[B0] +
|
||||||
|
fPrevIn * fNumerCoeffs[B1] +
|
||||||
|
fPrevPrevIn * fNumerCoeffs[B2] -
|
||||||
|
fPrevOut * fDenomCoeffs[A1] -
|
||||||
|
fPrevPrevOut * fDenomCoeffs[A2];
|
||||||
|
fPrevPrevIn = fPrevIn;
|
||||||
|
fPrevIn = fIn;
|
||||||
|
fPrevPrevOut = fPrevOut;
|
||||||
|
fPrevOut = fOut;
|
||||||
|
return fOut;
|
||||||
|
}
|
||||||
|
|
||||||
float* pfIn;
|
float* pfIn;
|
||||||
float* pfOut;
|
float* pfOut;
|
||||||
float fNumerCoeffs [3]; // B0 B1 B2
|
float fNumerCoeffs[3]; // B0 B1 B2
|
||||||
float fDenomCoeffs [2]; // A1 A2
|
float fDenomCoeffs[2]; // A1 A2, A0 == 1.0
|
||||||
float fPrevIn;
|
float fPrevIn;
|
||||||
float fPrevPrevIn;
|
float fPrevPrevIn;
|
||||||
float fPrevOut;
|
float fPrevOut;
|
||||||
float fPrevPrevOut;
|
float fPrevPrevOut;
|
||||||
} BiquadStruct;
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Biquad_Process (BiquadStruct* pBQ, int iNumSamples);
|
|
||||||
void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI);
|
void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI);
|
||||||
bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY);
|
bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY);
|
||||||
float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2);
|
float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2);
|
||||||
|
@ -1974,10 +1974,10 @@ void Effect::IncludeNotSelectedPreviewTracks(bool includeNotSelected)
|
|||||||
mPreviewWithNotSelected = includeNotSelected;
|
mPreviewWithNotSelected = includeNotSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Effect::TotalProgress(double frac)
|
bool Effect::TotalProgress(double frac, const wxString &msg)
|
||||||
{
|
{
|
||||||
auto updateResult = (mProgress ?
|
auto updateResult = (mProgress ?
|
||||||
mProgress->Update(frac) :
|
mProgress->Update(frac, msg) :
|
||||||
ProgressResult::Success);
|
ProgressResult::Success);
|
||||||
return (updateResult != ProgressResult::Success);
|
return (updateResult != ProgressResult::Success);
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,7 @@ protected:
|
|||||||
// is okay, but don't try to undo).
|
// is okay, but don't try to undo).
|
||||||
|
|
||||||
// Pass a fraction between 0.0 and 1.0
|
// Pass a fraction between 0.0 and 1.0
|
||||||
bool TotalProgress(double frac);
|
bool TotalProgress(double frac, const wxString & = wxEmptyString);
|
||||||
|
|
||||||
// Pass a fraction between 0.0 and 1.0, for the current track
|
// Pass a fraction between 0.0 and 1.0, for the current track
|
||||||
// (when doing one track at a time)
|
// (when doing one track at a time)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
Dominic Mazzoni
|
Dominic Mazzoni
|
||||||
Vaughan Johnson (Preview)
|
Vaughan Johnson (Preview)
|
||||||
|
Max Maisel (Loudness)
|
||||||
|
|
||||||
*******************************************************************//**
|
*******************************************************************//**
|
||||||
|
|
||||||
@ -32,10 +33,11 @@
|
|||||||
// Define keys, defaults, minimums, and maximums for the effect parameters
|
// Define keys, defaults, minimums, and maximums for the effect parameters
|
||||||
//
|
//
|
||||||
// Name Type Key Def Min Max Scale
|
// Name Type Key Def Min Max Scale
|
||||||
Param( Level, double, wxT("Level"), -1.0, -145.0, 0.0, 1 );
|
Param( Level, double, wxT("Level"), -23.0, -145.0, 0.0, 1 );
|
||||||
Param( RemoveDC, bool, wxT("RemoveDcOffset"), true, false, true, 1 );
|
Param( RemoveDC, bool, wxT("RemoveDcOffset"), true, false, true, 1 );
|
||||||
Param( ApplyGain, bool, wxT("ApplyGain"), true, false, true, 1 );
|
Param( ApplyGain, bool, wxT("ApplyGain"), true, false, true, 1 );
|
||||||
Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 );
|
Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 );
|
||||||
|
Param( UseLoudness, bool, wxT("Use Loudness"), true, false, true, 1 );
|
||||||
|
|
||||||
BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler)
|
BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler)
|
||||||
EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI)
|
EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI)
|
||||||
@ -48,6 +50,7 @@ EffectNormalize::EffectNormalize()
|
|||||||
mDC = DEF_RemoveDC;
|
mDC = DEF_RemoveDC;
|
||||||
mGain = DEF_ApplyGain;
|
mGain = DEF_ApplyGain;
|
||||||
mStereoInd = DEF_StereoInd;
|
mStereoInd = DEF_StereoInd;
|
||||||
|
mUseLoudness = DEF_UseLoudness;
|
||||||
|
|
||||||
SetLinearEffectFlag(false);
|
SetLinearEffectFlag(false);
|
||||||
}
|
}
|
||||||
@ -65,7 +68,7 @@ IdentInterfaceSymbol EffectNormalize::GetSymbol()
|
|||||||
|
|
||||||
wxString EffectNormalize::GetDescription()
|
wxString EffectNormalize::GetDescription()
|
||||||
{
|
{
|
||||||
return _("Sets the peak amplitude of one or more tracks");
|
return _("Sets the peak amplitude or loudness of one or more tracks");
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString EffectNormalize::ManualPage()
|
wxString EffectNormalize::ManualPage()
|
||||||
@ -86,6 +89,7 @@ bool EffectNormalize::DefineParams( ShuttleParams & S ){
|
|||||||
S.SHUTTLE_PARAM( mGain, ApplyGain );
|
S.SHUTTLE_PARAM( mGain, ApplyGain );
|
||||||
S.SHUTTLE_PARAM( mDC, RemoveDC );
|
S.SHUTTLE_PARAM( mDC, RemoveDC );
|
||||||
S.SHUTTLE_PARAM( mStereoInd, StereoInd );
|
S.SHUTTLE_PARAM( mStereoInd, StereoInd );
|
||||||
|
S.SHUTTLE_PARAM( mUseLoudness, UseLoudness );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +99,7 @@ bool EffectNormalize::GetAutomationParameters(CommandParameters & parms)
|
|||||||
parms.Write(KEY_ApplyGain, mGain);
|
parms.Write(KEY_ApplyGain, mGain);
|
||||||
parms.Write(KEY_RemoveDC, mDC);
|
parms.Write(KEY_RemoveDC, mDC);
|
||||||
parms.Write(KEY_StereoInd, mStereoInd);
|
parms.Write(KEY_StereoInd, mStereoInd);
|
||||||
|
parms.Write(KEY_UseLoudness, mUseLoudness);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -105,11 +110,13 @@ bool EffectNormalize::SetAutomationParameters(CommandParameters & parms)
|
|||||||
ReadAndVerifyBool(ApplyGain);
|
ReadAndVerifyBool(ApplyGain);
|
||||||
ReadAndVerifyBool(RemoveDC);
|
ReadAndVerifyBool(RemoveDC);
|
||||||
ReadAndVerifyBool(StereoInd);
|
ReadAndVerifyBool(StereoInd);
|
||||||
|
ReadAndVerifyBool(UseLoudness);
|
||||||
|
|
||||||
mLevel = Level;
|
mLevel = Level;
|
||||||
mGain = ApplyGain;
|
mGain = ApplyGain;
|
||||||
mDC = RemoveDC;
|
mDC = RemoveDC;
|
||||||
mStereoInd = StereoInd;
|
mStereoInd = StereoInd;
|
||||||
|
mUseLoudness = UseLoudness;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -145,6 +152,7 @@ bool EffectNormalize::Startup()
|
|||||||
mLevel = -mLevel;
|
mLevel = -mLevel;
|
||||||
boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L);
|
boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L);
|
||||||
mStereoInd = (boolProxy == 1);
|
mStereoInd = (boolProxy == 1);
|
||||||
|
mUseLoudness = false;
|
||||||
|
|
||||||
SaveUserPreset(GetCurrentSettingsGroup());
|
SaveUserPreset(GetCurrentSettingsGroup());
|
||||||
|
|
||||||
@ -163,8 +171,15 @@ bool EffectNormalize::Process()
|
|||||||
|
|
||||||
float ratio;
|
float ratio;
|
||||||
if( mGain )
|
if( mGain )
|
||||||
|
{
|
||||||
|
if(mUseLoudness)
|
||||||
|
// LU use 10*log10(...) instead of 20*log10(...)
|
||||||
|
// so multiply level by 2 and use standard DB_TO_LINEAR macro.
|
||||||
|
ratio = DB_TO_LINEAR(TrapDouble(mLevel*2, MIN_Level, MAX_Level));
|
||||||
|
else
|
||||||
// same value used for all tracks
|
// same value used for all tracks
|
||||||
ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level));
|
ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ratio = 1.0;
|
ratio = 1.0;
|
||||||
|
|
||||||
@ -175,7 +190,7 @@ bool EffectNormalize::Process()
|
|||||||
WaveTrack *track = (WaveTrack *) iter.First();
|
WaveTrack *track = (WaveTrack *) iter.First();
|
||||||
WaveTrack *prevTrack;
|
WaveTrack *prevTrack;
|
||||||
prevTrack = track;
|
prevTrack = track;
|
||||||
int curTrackNum = 0;
|
double progress = 0;
|
||||||
wxString topMsg;
|
wxString topMsg;
|
||||||
if(mDC && mGain)
|
if(mDC && mGain)
|
||||||
topMsg = _("Removing DC offset and Normalizing...\n");
|
topMsg = _("Removing DC offset and Normalizing...\n");
|
||||||
@ -207,13 +222,12 @@ bool EffectNormalize::Process()
|
|||||||
else
|
else
|
||||||
msg =
|
msg =
|
||||||
topMsg + wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName );
|
topMsg + wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName );
|
||||||
float offset, min, max;
|
float offset, extent;
|
||||||
bGoodResult = AnalyseTrack(track, msg, curTrackNum, offset, min, max);
|
bGoodResult = AnalyseTrack(track, msg, progress, offset, extent);
|
||||||
if (!bGoodResult )
|
if (!bGoodResult )
|
||||||
break;
|
break;
|
||||||
if(!track->GetLinked() || mStereoInd) {
|
if(!track->GetLinked() || mStereoInd) {
|
||||||
// mono or 'stereo tracks independently'
|
// mono or 'stereo tracks independently'
|
||||||
float extent = wxMax(fabs(max), fabs(min));
|
|
||||||
if( (extent > 0) && mGain )
|
if( (extent > 0) && mGain )
|
||||||
mMult = ratio / extent;
|
mMult = ratio / extent;
|
||||||
else
|
else
|
||||||
@ -224,7 +238,7 @@ bool EffectNormalize::Process()
|
|||||||
msg =
|
msg =
|
||||||
topMsg + wxString::Format( _("Processing stereo channels independently: %s"), trackName );
|
topMsg + wxString::Format( _("Processing stereo channels independently: %s"), trackName );
|
||||||
|
|
||||||
if (!ProcessOne(track, msg, curTrackNum, offset))
|
if (!ProcessOne(track, msg, progress, offset))
|
||||||
{
|
{
|
||||||
bGoodResult = false;
|
bGoodResult = false;
|
||||||
break;
|
break;
|
||||||
@ -235,16 +249,23 @@ bool EffectNormalize::Process()
|
|||||||
// we have a linked stereo track
|
// we have a linked stereo track
|
||||||
// so we need to find it's min, max and offset
|
// so we need to find it's min, max and offset
|
||||||
// as they are needed to calc the multiplier for both tracks
|
// as they are needed to calc the multiplier for both tracks
|
||||||
|
|
||||||
track = (WaveTrack *) iter.Next(); // get the next one
|
track = (WaveTrack *) iter.Next(); // get the next one
|
||||||
msg =
|
msg =
|
||||||
topMsg + wxString::Format( _("Analyzing second track of stereo pair: %s"), trackName );
|
topMsg + wxString::Format( _("Analyzing second track of stereo pair: %s"), trackName );
|
||||||
float offset2, min2, max2;
|
|
||||||
bGoodResult = AnalyseTrack(track, msg, curTrackNum + 1, offset2, min2, max2);
|
float offset2, extent2;
|
||||||
|
bGoodResult = AnalyseTrack(track, msg, progress, offset2, extent2);
|
||||||
if ( !bGoodResult )
|
if ( !bGoodResult )
|
||||||
break;
|
break;
|
||||||
float extent = wxMax(fabs(min), fabs(max));
|
|
||||||
extent = wxMax(extent, fabs(min2));
|
if (mUseLoudness)
|
||||||
extent = wxMax(extent, fabs(max2));
|
// Loudness: use mean of both tracks.
|
||||||
|
extent = (extent + extent2) / 2;
|
||||||
|
else
|
||||||
|
// Peak: use maximum of both tracks.
|
||||||
|
extent = fmax(extent, extent2);
|
||||||
|
|
||||||
if( (extent > 0) && mGain )
|
if( (extent > 0) && mGain )
|
||||||
mMult = ratio / extent; // we need to use this for both linked tracks
|
mMult = ratio / extent; // we need to use this for both linked tracks
|
||||||
else
|
else
|
||||||
@ -252,16 +273,15 @@ bool EffectNormalize::Process()
|
|||||||
track = (WaveTrack *) iter.Prev(); // go back to the first linked one
|
track = (WaveTrack *) iter.Prev(); // go back to the first linked one
|
||||||
msg =
|
msg =
|
||||||
topMsg + wxString::Format( _("Processing first track of stereo pair: %s"), trackName );
|
topMsg + wxString::Format( _("Processing first track of stereo pair: %s"), trackName );
|
||||||
if (!ProcessOne(track, msg, curTrackNum, offset))
|
if (!ProcessOne(track, msg, progress, offset))
|
||||||
{
|
{
|
||||||
bGoodResult = false;
|
bGoodResult = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
track = (WaveTrack *) iter.Next(); // go to the second linked one
|
track = (WaveTrack *) iter.Next(); // go to the second linked one
|
||||||
curTrackNum++; // keeps progress bar correct
|
|
||||||
msg =
|
msg =
|
||||||
topMsg + wxString::Format( _("Processing second track of stereo pair: %s"), trackName );
|
topMsg + wxString::Format( _("Processing second track of stereo pair: %s"), trackName );
|
||||||
if (!ProcessOne(track, msg, curTrackNum, offset2))
|
if (!ProcessOne(track, msg, progress, offset2))
|
||||||
{
|
{
|
||||||
bGoodResult = false;
|
bGoodResult = false;
|
||||||
break;
|
break;
|
||||||
@ -272,7 +292,6 @@ bool EffectNormalize::Process()
|
|||||||
//Iterate to the next track
|
//Iterate to the next track
|
||||||
prevTrack = track;
|
prevTrack = track;
|
||||||
track = (WaveTrack *) iter.Next();
|
track = (WaveTrack *) iter.Next();
|
||||||
curTrackNum++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->ReplaceProcessedTracks(bGoodResult);
|
this->ReplaceProcessedTracks(bGoodResult);
|
||||||
@ -293,7 +312,7 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S)
|
|||||||
mDC ? wxT("true") : wxT("false"));
|
mDC ? wxT("true") : wxT("false"));
|
||||||
mDCCheckBox->SetValidator(wxGenericValidator(&mDC));
|
mDCCheckBox->SetValidator(wxGenericValidator(&mDC));
|
||||||
|
|
||||||
S.StartHorizontalLay(wxALIGN_CENTER, false);
|
S.StartHorizontalLay(wxALIGN_LEFT, false);
|
||||||
{
|
{
|
||||||
mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"),
|
mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"),
|
||||||
mGain ? wxT("true") : wxT("false"));
|
mGain ? wxT("true") : wxT("false"));
|
||||||
@ -311,6 +330,9 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S)
|
|||||||
}
|
}
|
||||||
S.EndHorizontalLay();
|
S.EndHorizontalLay();
|
||||||
|
|
||||||
|
mUseLoudnessCheckBox = S.AddCheckBox(_("Use integrative loudness instead of maximum amplitude"),
|
||||||
|
mUseLoudness ? wxT("true") : wxT("false"));
|
||||||
|
mUseLoudnessCheckBox->SetValidator(wxGenericValidator(&mUseLoudness));
|
||||||
|
|
||||||
mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"),
|
mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"),
|
||||||
mStereoInd ? wxT("true") : wxT("false"));
|
mStereoInd ? wxT("true") : wxT("false"));
|
||||||
@ -350,11 +372,33 @@ bool EffectNormalize::TransferDataFromWindow()
|
|||||||
// EffectNormalize implementation
|
// EffectNormalize implementation
|
||||||
|
|
||||||
bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg,
|
bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg,
|
||||||
int curTrackNum,
|
double &progress, float &offset, float &extent)
|
||||||
float &offset, float &min, float &max)
|
|
||||||
{
|
{
|
||||||
if(mGain) {
|
bool result = true;
|
||||||
|
float min, max;
|
||||||
|
|
||||||
|
if(mGain)
|
||||||
|
{
|
||||||
|
if(mUseLoudness)
|
||||||
|
{
|
||||||
|
CalcEBUR128HPF(track->GetRate());
|
||||||
|
CalcEBUR128HSF(track->GetRate());
|
||||||
|
if(mDC)
|
||||||
|
{
|
||||||
|
result = AnalyseTrackData(track, msg, progress, ANALYSE_LOUDNESS_DC, offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = AnalyseTrackData(track, msg, progress, ANALYSE_LOUDNESS, offset);
|
||||||
|
offset = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extent = sqrt(mSqSum / mCount.as_double());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Since we need complete summary data, we need to block until the OD tasks are done for this track
|
// Since we need complete summary data, we need to block until the OD tasks are done for this track
|
||||||
|
// This is needed for track->GetMinMax
|
||||||
// TODO: should we restrict the flags to just the relevant block files (for selections)
|
// TODO: should we restrict the flags to just the relevant block files (for selections)
|
||||||
while (track->GetODFlags()) {
|
while (track->GetODFlags()) {
|
||||||
// update the gui
|
// update the gui
|
||||||
@ -368,34 +412,40 @@ bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg,
|
|||||||
auto pair = track->GetMinMax(mCurT0, mCurT1); // may throw
|
auto pair = track->GetMinMax(mCurT0, mCurT1); // may throw
|
||||||
min = pair.first, max = pair.second;
|
min = pair.first, max = pair.second;
|
||||||
|
|
||||||
} else {
|
if(mDC)
|
||||||
|
{
|
||||||
min = -1.0, max = 1.0; // sensible defaults?
|
min = -1.0, max = 1.0; // sensible defaults?
|
||||||
}
|
result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset);
|
||||||
|
|
||||||
if(mDC) {
|
|
||||||
auto rc = AnalyseDC(track, msg, curTrackNum, offset);
|
|
||||||
min += offset;
|
min += offset;
|
||||||
max += offset;
|
max += offset;
|
||||||
return rc;
|
|
||||||
} else {
|
|
||||||
offset = 0.0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(mDC)
|
||||||
|
{
|
||||||
|
min = -1.0, max = 1.0; // sensible defaults?
|
||||||
|
result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset);
|
||||||
|
min += offset;
|
||||||
|
max += offset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
min = -1.0, max = 1.0; // sensible defaults?
|
||||||
|
offset = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mUseLoudness)
|
||||||
|
extent = fmax(fabs(min), fabs(max));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//AnalyseDC() takes a track, transforms it to bunch of buffer-blocks,
|
//AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks,
|
||||||
//and executes AnalyzeData on it...
|
//and executes selected AnalyseOperation on it...
|
||||||
bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
|
bool EffectNormalize::AnalyseTrackData(const WaveTrack * track, const wxString &msg,
|
||||||
int curTrackNum,
|
double &progress, AnalyseOperation op, float &offset)
|
||||||
float &offset)
|
|
||||||
{
|
{
|
||||||
bool rc = true;
|
bool rc = true;
|
||||||
|
|
||||||
offset = 0.0; // we might just return
|
|
||||||
|
|
||||||
if(!mDC) // don't do analysis if not doing dc removal
|
|
||||||
return(rc);
|
|
||||||
|
|
||||||
//Transform the marker timepoints to samples
|
//Transform the marker timepoints to samples
|
||||||
auto start = track->TimeToLongSamples(mCurT0);
|
auto start = track->TimeToLongSamples(mCurT0);
|
||||||
auto end = track->TimeToLongSamples(mCurT1);
|
auto end = track->TimeToLongSamples(mCurT1);
|
||||||
@ -410,6 +460,7 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
|
|||||||
Floats buffer{ track->GetMaxBlockSize() };
|
Floats buffer{ track->GetMaxBlockSize() };
|
||||||
|
|
||||||
mSum = 0.0; // dc offset inits
|
mSum = 0.0; // dc offset inits
|
||||||
|
mSqSum = 0.0; // rms init
|
||||||
mCount = 0;
|
mCount = 0;
|
||||||
|
|
||||||
sampleCount blockSamples;
|
sampleCount blockSamples;
|
||||||
@ -431,14 +482,19 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
|
|||||||
totalSamples += blockSamples;
|
totalSamples += blockSamples;
|
||||||
|
|
||||||
//Process the buffer.
|
//Process the buffer.
|
||||||
AnalyzeData(buffer.get(), block);
|
if(op == ANALYSE_DC)
|
||||||
|
AnalyseDataDC(buffer.get(), block);
|
||||||
|
else if(op == ANALYSE_LOUDNESS)
|
||||||
|
AnalyseDataLoudness(buffer.get(), block);
|
||||||
|
else if(op == ANALYSE_LOUDNESS_DC)
|
||||||
|
AnalyseDataLoudnessDC(buffer.get(), block);
|
||||||
|
|
||||||
//Increment s one blockfull of samples
|
//Increment s one blockfull of samples
|
||||||
s += block;
|
s += block;
|
||||||
|
|
||||||
//Update the Progress meter
|
//Update the Progress meter
|
||||||
if (TrackProgress(curTrackNum,
|
if (TotalProgress(progress +
|
||||||
((s - start).as_double() / len)/2.0, msg)) {
|
((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
|
||||||
rc = false; //lda .. break, not return, so that buffer is deleted
|
rc = false; //lda .. break, not return, so that buffer is deleted
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -448,6 +504,7 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
|
|||||||
else
|
else
|
||||||
offset = 0.0;
|
offset = 0.0;
|
||||||
|
|
||||||
|
progress += 1.0/double(2*GetNumWaveTracks());
|
||||||
//Return true because the effect processing succeeded ... unless cancelled
|
//Return true because the effect processing succeeded ... unless cancelled
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -457,7 +514,7 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
|
|||||||
// uses mMult and offset to normalize a track.
|
// uses mMult and offset to normalize a track.
|
||||||
// mMult must be set before this is called
|
// mMult must be set before this is called
|
||||||
bool EffectNormalize::ProcessOne(
|
bool EffectNormalize::ProcessOne(
|
||||||
WaveTrack * track, const wxString &msg, int curTrackNum, float offset)
|
WaveTrack * track, const wxString &msg, double &progress, float offset)
|
||||||
{
|
{
|
||||||
bool rc = true;
|
bool rc = true;
|
||||||
|
|
||||||
@ -498,24 +555,58 @@ bool EffectNormalize::ProcessOne(
|
|||||||
s += block;
|
s += block;
|
||||||
|
|
||||||
//Update the Progress meter
|
//Update the Progress meter
|
||||||
if (TrackProgress(curTrackNum,
|
if (TotalProgress(progress +
|
||||||
0.5+((s - start).as_double() / len)/2.0, msg)) {
|
((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
|
||||||
rc = false; //lda .. break, not return, so that buffer is deleted
|
rc = false; //lda .. break, not return, so that buffer is deleted
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress += 1.0/double(2*GetNumWaveTracks());
|
||||||
|
|
||||||
//Return true because the effect processing succeeded ... unless cancelled
|
//Return true because the effect processing succeeded ... unless cancelled
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectNormalize::AnalyzeData(float *buffer, size_t len)
|
/// @see AnalyseDataLoudnessDC
|
||||||
|
void EffectNormalize::AnalyseDataDC(float *buffer, size_t len)
|
||||||
{
|
{
|
||||||
for(decltype(len) i = 0; i < len; i++)
|
for(decltype(len) i = 0; i < len; i++)
|
||||||
mSum += (double)buffer[i];
|
mSum += (double)buffer[i];
|
||||||
mCount += len;
|
mCount += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @see AnalyseDataLoudnessDC
|
||||||
|
void EffectNormalize::AnalyseDataLoudness(float *buffer, size_t len)
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
for(decltype(len) i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
value = mR128HSF.ProcessOne(buffer[i]);
|
||||||
|
value = mR128HPF.ProcessOne(value);
|
||||||
|
mSqSum += ((double)value) * ((double)value);
|
||||||
|
}
|
||||||
|
mCount += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates sample sum (for DC) and EBU R128 weighted square sum
|
||||||
|
/// (for loudness). This function has variants which only calculate
|
||||||
|
/// sum or square sum for performance improvements if only one of those
|
||||||
|
/// values is required.
|
||||||
|
/// @see AnalyseDataLoudness
|
||||||
|
/// @see AnalyseDataDC
|
||||||
|
void EffectNormalize::AnalyseDataLoudnessDC(float *buffer, size_t len)
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
for(decltype(len) i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
mSum += (double)buffer[i];
|
||||||
|
value = mR128HSF.ProcessOne(buffer[i]);
|
||||||
|
value = mR128HPF.ProcessOne(value);
|
||||||
|
mSqSum += ((double)value) * ((double)value);
|
||||||
|
}
|
||||||
|
mCount += len;
|
||||||
|
}
|
||||||
|
|
||||||
void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
|
void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
|
||||||
{
|
{
|
||||||
for(decltype(len) i = 0; i < len; i++) {
|
for(decltype(len) i = 0; i < len; i++) {
|
||||||
@ -524,6 +615,46 @@ void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// after Juha, https://hydrogenaud.io/index.php/topic,76394.0.html
|
||||||
|
void EffectNormalize::CalcEBUR128HPF(float fs)
|
||||||
|
{
|
||||||
|
double f0 = 38.13547087602444;
|
||||||
|
double Q = 0.5003270373238773;
|
||||||
|
double K = tan(M_PI * f0 / fs);
|
||||||
|
|
||||||
|
mR128HPF.Reset();
|
||||||
|
|
||||||
|
mR128HPF.fNumerCoeffs[Biquad::B0] = 1.0;
|
||||||
|
mR128HPF.fNumerCoeffs[Biquad::B1] = -2.0;
|
||||||
|
mR128HPF.fNumerCoeffs[Biquad::B2] = 1.0;
|
||||||
|
|
||||||
|
mR128HPF.fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K);
|
||||||
|
mR128HPF.fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K);
|
||||||
|
}
|
||||||
|
|
||||||
|
// after Juha, https://hydrogenaud.io/index.php/topic,76394.0.html
|
||||||
|
void EffectNormalize::CalcEBUR128HSF(float fs)
|
||||||
|
{
|
||||||
|
double db = 3.999843853973347;
|
||||||
|
double f0 = 1681.974450955533;
|
||||||
|
double Q = 0.7071752369554196;
|
||||||
|
double K = tan(M_PI * f0 / fs);
|
||||||
|
|
||||||
|
double Vh = pow(10.0, db / 20.0);
|
||||||
|
double Vb = pow(Vh, 0.4996667741545416);
|
||||||
|
|
||||||
|
double a0 = 1.0 + K / Q + K * K;
|
||||||
|
|
||||||
|
mR128HSF.Reset();
|
||||||
|
|
||||||
|
mR128HSF.fNumerCoeffs[Biquad::B0] = (Vh + Vb * K / Q + K * K) / a0;
|
||||||
|
mR128HSF.fNumerCoeffs[Biquad::B1] = 2.0 * (K * K - Vh) / a0;
|
||||||
|
mR128HSF.fNumerCoeffs[Biquad::B2] = (Vh - Vb * K / Q + K * K) / a0;
|
||||||
|
|
||||||
|
mR128HSF.fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / a0;
|
||||||
|
mR128HSF.fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
||||||
{
|
{
|
||||||
UpdateUI();
|
UpdateUI();
|
||||||
@ -539,10 +670,16 @@ void EffectNormalize::UpdateUI()
|
|||||||
}
|
}
|
||||||
mWarning->SetLabel(wxT(""));
|
mWarning->SetLabel(wxT(""));
|
||||||
|
|
||||||
|
if (mUseLoudness)
|
||||||
|
mLeveldB->SetLabel(_("LUFS"));
|
||||||
|
else
|
||||||
|
mLeveldB->SetLabel(_("dB"));
|
||||||
|
|
||||||
// Disallow level stuff if not normalizing
|
// Disallow level stuff if not normalizing
|
||||||
mLevelTextCtrl->Enable(mGain);
|
mLevelTextCtrl->Enable(mGain);
|
||||||
mLeveldB->Enable(mGain);
|
mLeveldB->Enable(mGain);
|
||||||
mStereoIndCheckBox->Enable(mGain);
|
mStereoIndCheckBox->Enable(mGain);
|
||||||
|
mUseLoudnessCheckBox->Enable(mGain);
|
||||||
|
|
||||||
// Disallow OK/Preview if doing nothing
|
// Disallow OK/Preview if doing nothing
|
||||||
EnableApply(mGain || mDC);
|
EnableApply(mGain || mDC);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
Dominic Mazzoni
|
Dominic Mazzoni
|
||||||
Vaughan Johnson (Preview)
|
Vaughan Johnson (Preview)
|
||||||
|
Max Maisel (Loudness)
|
||||||
|
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
@ -19,6 +20,7 @@
|
|||||||
#include <wx/textctrl.h>
|
#include <wx/textctrl.h>
|
||||||
|
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
#include "Biquad.h"
|
||||||
|
|
||||||
class ShuttleGui;
|
class ShuttleGui;
|
||||||
|
|
||||||
@ -58,16 +60,25 @@ public:
|
|||||||
private:
|
private:
|
||||||
// EffectNormalize implementation
|
// EffectNormalize implementation
|
||||||
|
|
||||||
|
enum AnalyseOperation
|
||||||
|
{
|
||||||
|
ANALYSE_DC, ANALYSE_LOUDNESS, ANALYSE_LOUDNESS_DC
|
||||||
|
};
|
||||||
|
|
||||||
bool ProcessOne(
|
bool ProcessOne(
|
||||||
WaveTrack * t, const wxString &msg, int curTrackNum, float offset);
|
WaveTrack * t, const wxString &msg, double& progress, float offset);
|
||||||
bool AnalyseTrack(const WaveTrack * track, const wxString &msg,
|
bool AnalyseTrack(const WaveTrack * track, const wxString &msg,
|
||||||
int curTrackNum,
|
double &progress, float &offset, float &extent);
|
||||||
float &offset, float &min, float &max);
|
bool AnalyseTrackData(const WaveTrack * track, const wxString &msg, double &progress,
|
||||||
void AnalyzeData(float *buffer, size_t len);
|
AnalyseOperation op, float &offset);
|
||||||
bool AnalyseDC(const WaveTrack * track, const wxString &msg, int curTrackNum,
|
void AnalyseDataDC(float *buffer, size_t len);
|
||||||
float &offset);
|
void AnalyseDataLoudness(float *buffer, size_t len);
|
||||||
|
void AnalyseDataLoudnessDC(float *buffer, size_t len);
|
||||||
void ProcessData(float *buffer, size_t len, float offset);
|
void ProcessData(float *buffer, size_t len, float offset);
|
||||||
|
|
||||||
|
void CalcEBUR128HPF(float fs);
|
||||||
|
void CalcEBUR128HSF(float fs);
|
||||||
|
|
||||||
void OnUpdateUI(wxCommandEvent & evt);
|
void OnUpdateUI(wxCommandEvent & evt);
|
||||||
void UpdateUI();
|
void UpdateUI();
|
||||||
|
|
||||||
@ -76,11 +87,13 @@ private:
|
|||||||
bool mGain;
|
bool mGain;
|
||||||
bool mDC;
|
bool mDC;
|
||||||
bool mStereoInd;
|
bool mStereoInd;
|
||||||
|
bool mUseLoudness;
|
||||||
|
|
||||||
double mCurT0;
|
double mCurT0;
|
||||||
double mCurT1;
|
double mCurT1;
|
||||||
float mMult;
|
float mMult;
|
||||||
double mSum;
|
double mSum;
|
||||||
|
double mSqSum;
|
||||||
sampleCount mCount;
|
sampleCount mCount;
|
||||||
|
|
||||||
wxCheckBox *mGainCheckBox;
|
wxCheckBox *mGainCheckBox;
|
||||||
@ -88,9 +101,12 @@ private:
|
|||||||
wxTextCtrl *mLevelTextCtrl;
|
wxTextCtrl *mLevelTextCtrl;
|
||||||
wxStaticText *mLeveldB;
|
wxStaticText *mLeveldB;
|
||||||
wxStaticText *mWarning;
|
wxStaticText *mWarning;
|
||||||
|
wxCheckBox *mUseLoudnessCheckBox;
|
||||||
wxCheckBox *mStereoIndCheckBox;
|
wxCheckBox *mStereoIndCheckBox;
|
||||||
|
|
||||||
bool mCreating;
|
bool mCreating;
|
||||||
|
Biquad mR128HSF;
|
||||||
|
Biquad mR128HPF;
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
};
|
};
|
||||||
|
@ -218,12 +218,7 @@ unsigned EffectScienFilter::GetAudioOutCount()
|
|||||||
bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
|
bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap))
|
||||||
{
|
{
|
||||||
for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
|
for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++)
|
||||||
{
|
mpBiquad[iPair].Reset();
|
||||||
mpBiquad[iPair].fPrevIn = 0;
|
|
||||||
mpBiquad[iPair].fPrevPrevIn = 0;
|
|
||||||
mpBiquad[iPair].fPrevOut = 0;
|
|
||||||
mpBiquad[iPair].fPrevPrevOut = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -235,7 +230,7 @@ size_t EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, size_t
|
|||||||
{
|
{
|
||||||
mpBiquad[iPair].pfIn = ibuf;
|
mpBiquad[iPair].pfIn = ibuf;
|
||||||
mpBiquad[iPair].pfOut = outBlock[0];
|
mpBiquad[iPair].pfOut = outBlock[0];
|
||||||
Biquad_Process(&mpBiquad[iPair], blockLen);
|
mpBiquad[iPair].Process(blockLen);
|
||||||
ibuf = outBlock[0];
|
ibuf = outBlock[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ private:
|
|||||||
int mFilterSubtype; // lowpass, highpass
|
int mFilterSubtype; // lowpass, highpass
|
||||||
int mOrder;
|
int mOrder;
|
||||||
int mOrderIndex;
|
int mOrderIndex;
|
||||||
ArrayOf<BiquadStruct> mpBiquad;
|
ArrayOf<Biquad> mpBiquad;
|
||||||
|
|
||||||
double mdBMax;
|
double mdBMax;
|
||||||
double mdBMin;
|
double mdBMin;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user