diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index 4a79567a1..bab5c6915 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -1,32 +1,42 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EffectScienFilter.h + +Norm C +Max Maisel + +***********************************************************************/ + #include "Biquad.h" #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++) - { - 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; + *pfOut++ = ProcessOne(*pfIn++); } void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index 0f6ebf235..f25d8b9da 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -1,37 +1,55 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EffectScienFilter.h + +Norm C +Max Maisel + +***********************************************************************/ + #ifndef __BIQUAD_H__ #define __BIQUAD_H__ -#if 0 -//initialisations not supported in MSVC 2013. -//Gives error C2905 -// Do not make conditional on compiler. -typedef struct { - float* pfIn {}; - float* pfOut {}; - float fNumerCoeffs [3] { 1.0f, 0.0f, 0.0f }; // B0 B1 B2 - float fDenomCoeffs [2] { 0.0f, 0.0f }; // A1 A2 - float fPrevIn {}; - float fPrevPrevIn {}; - float fPrevOut {}; - float fPrevPrevOut {}; -} BiquadStruct; -#else -// WARNING: This structure may need initialisation. -typedef struct { +struct Biquad +{ + Biquad(); + void Reset(); + void Process(int iNumSamples); + + enum + { + /// Numerator coefficient indices + B0=0, B1, B2, + /// Denominator coefficient indices + A1=0, A2 + }; + + inline float ProcessOne(float fIn) + { + 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* pfOut; - float fNumerCoeffs [3]; // B0 B1 B2 - float fDenomCoeffs [2]; // A1 A2 + float fNumerCoeffs[3]; // B0 B1 B2 + float fDenomCoeffs[2]; // A1 A2, A0 == 1.0 float fPrevIn; float fPrevPrevIn; float fPrevOut; 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); bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY); float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2); diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index d5c60d413..d0af0ac52 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -1974,10 +1974,10 @@ void Effect::IncludeNotSelectedPreviewTracks(bool includeNotSelected) mPreviewWithNotSelected = includeNotSelected; } -bool Effect::TotalProgress(double frac) +bool Effect::TotalProgress(double frac, const wxString &msg) { auto updateResult = (mProgress ? - mProgress->Update(frac) : + mProgress->Update(frac, msg) : ProgressResult::Success); return (updateResult != ProgressResult::Success); } diff --git a/src/effects/Effect.h b/src/effects/Effect.h index ab3d61154..de3ddbd02 100644 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -330,7 +330,7 @@ protected: // is okay, but don't try to undo). // 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 // (when doing one track at a time) diff --git a/src/effects/Normalize.cpp b/src/effects/Normalize.cpp index 1558964ce..24af23323 100644 --- a/src/effects/Normalize.cpp +++ b/src/effects/Normalize.cpp @@ -6,6 +6,7 @@ Dominic Mazzoni Vaughan Johnson (Preview) + Max Maisel (Loudness) *******************************************************************//** @@ -31,11 +32,12 @@ // Define keys, defaults, minimums, and maximums for the effect parameters // -// Name Type Key Def Min Max Scale -Param( Level, double, wxT("Level"), -1.0, -145.0, 0.0, 1 ); -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 ); +// Name Type Key Def Min Max Scale +Param( Level, double, wxT("Level"), -23.0, -145.0, 0.0, 1 ); +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 ); +Param( UseLoudness, bool, wxT("Use Loudness"), true, false, true, 1 ); BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler) EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI) @@ -48,6 +50,7 @@ EffectNormalize::EffectNormalize() mDC = DEF_RemoveDC; mGain = DEF_ApplyGain; mStereoInd = DEF_StereoInd; + mUseLoudness = DEF_UseLoudness; SetLinearEffectFlag(false); } @@ -65,7 +68,7 @@ IdentInterfaceSymbol EffectNormalize::GetSymbol() 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() @@ -86,6 +89,7 @@ bool EffectNormalize::DefineParams( ShuttleParams & S ){ S.SHUTTLE_PARAM( mGain, ApplyGain ); S.SHUTTLE_PARAM( mDC, RemoveDC ); S.SHUTTLE_PARAM( mStereoInd, StereoInd ); + S.SHUTTLE_PARAM( mUseLoudness, UseLoudness ); return true; } @@ -95,6 +99,7 @@ bool EffectNormalize::GetAutomationParameters(CommandParameters & parms) parms.Write(KEY_ApplyGain, mGain); parms.Write(KEY_RemoveDC, mDC); parms.Write(KEY_StereoInd, mStereoInd); + parms.Write(KEY_UseLoudness, mUseLoudness); return true; } @@ -105,11 +110,13 @@ bool EffectNormalize::SetAutomationParameters(CommandParameters & parms) ReadAndVerifyBool(ApplyGain); ReadAndVerifyBool(RemoveDC); ReadAndVerifyBool(StereoInd); + ReadAndVerifyBool(UseLoudness); mLevel = Level; mGain = ApplyGain; mDC = RemoveDC; mStereoInd = StereoInd; + mUseLoudness = UseLoudness; return true; } @@ -145,6 +152,7 @@ bool EffectNormalize::Startup() mLevel = -mLevel; boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L); mStereoInd = (boolProxy == 1); + mUseLoudness = false; SaveUserPreset(GetCurrentSettingsGroup()); @@ -163,8 +171,15 @@ bool EffectNormalize::Process() float ratio; if( mGain ) - // same value used for all tracks - ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level)); + { + 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 + ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level)); + } else ratio = 1.0; @@ -175,7 +190,7 @@ bool EffectNormalize::Process() WaveTrack *track = (WaveTrack *) iter.First(); WaveTrack *prevTrack; prevTrack = track; - int curTrackNum = 0; + double progress = 0; wxString topMsg; if(mDC && mGain) topMsg = _("Removing DC offset and Normalizing...\n"); @@ -207,13 +222,12 @@ bool EffectNormalize::Process() else msg = topMsg + wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName ); - float offset, min, max; - bGoodResult = AnalyseTrack(track, msg, curTrackNum, offset, min, max); + float offset, extent; + bGoodResult = AnalyseTrack(track, msg, progress, offset, extent); if (!bGoodResult ) break; if(!track->GetLinked() || mStereoInd) { // mono or 'stereo tracks independently' - float extent = wxMax(fabs(max), fabs(min)); if( (extent > 0) && mGain ) mMult = ratio / extent; else @@ -224,7 +238,7 @@ bool EffectNormalize::Process() msg = topMsg + wxString::Format( _("Processing stereo channels independently: %s"), trackName ); - if (!ProcessOne(track, msg, curTrackNum, offset)) + if (!ProcessOne(track, msg, progress, offset)) { bGoodResult = false; break; @@ -235,16 +249,23 @@ bool EffectNormalize::Process() // we have a linked stereo track // so we need to find it's min, max and offset // as they are needed to calc the multiplier for both tracks + track = (WaveTrack *) iter.Next(); // get the next one msg = 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 ) break; - float extent = wxMax(fabs(min), fabs(max)); - extent = wxMax(extent, fabs(min2)); - extent = wxMax(extent, fabs(max2)); + + if (mUseLoudness) + // 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 ) mMult = ratio / extent; // we need to use this for both linked tracks else @@ -252,16 +273,15 @@ bool EffectNormalize::Process() track = (WaveTrack *) iter.Prev(); // go back to the first linked one msg = 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; break; } track = (WaveTrack *) iter.Next(); // go to the second linked one - curTrackNum++; // keeps progress bar correct msg = 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; break; @@ -272,7 +292,6 @@ bool EffectNormalize::Process() //Iterate to the next track prevTrack = track; track = (WaveTrack *) iter.Next(); - curTrackNum++; } this->ReplaceProcessedTracks(bGoodResult); @@ -293,7 +312,7 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S) mDC ? wxT("true") : wxT("false")); mDCCheckBox->SetValidator(wxGenericValidator(&mDC)); - S.StartHorizontalLay(wxALIGN_CENTER, false); + S.StartHorizontalLay(wxALIGN_LEFT, false); { mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"), mGain ? wxT("true") : wxT("false")); @@ -311,7 +330,10 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S) } 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"), mStereoInd ? wxT("true") : wxT("false")); mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd)); @@ -350,52 +372,80 @@ bool EffectNormalize::TransferDataFromWindow() // EffectNormalize implementation bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg, - int curTrackNum, - float &offset, float &min, float &max) + double &progress, float &offset, float &extent) { - if(mGain) { - // Since we need complete summary data, we need to block until the OD tasks are done for this track - // TODO: should we restrict the flags to just the relevant block files (for selections) - while (track->GetODFlags()) { - // update the gui - if (ProgressResult::Cancelled == mProgress->Update( - 0, _("Waiting for waveform to finish computing...")) ) - return false; - wxMilliSleep(100); + 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 + // This is needed for track->GetMinMax + // TODO: should we restrict the flags to just the relevant block files (for selections) + while (track->GetODFlags()) { + // update the gui + if (ProgressResult::Cancelled == mProgress->Update( + 0, _("Waiting for waveform to finish computing...")) ) + return false; + wxMilliSleep(100); + } - // 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; + // 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; - } else { - min = -1.0, max = 1.0; // sensible defaults? + if(mDC) + { + min = -1.0, max = 1.0; // sensible defaults? + result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset); + min += offset; + max += offset; + } + } } - - if(mDC) { - auto rc = AnalyseDC(track, msg, curTrackNum, offset); + else if(mDC) + { + min = -1.0, max = 1.0; // sensible defaults? + result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset); min += offset; max += offset; - return rc; - } else { - offset = 0.0; - return true; } + 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, -//and executes AnalyzeData on it... -bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg, - int curTrackNum, - float &offset) +//AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks, +//and executes selected AnalyseOperation on it... +bool EffectNormalize::AnalyseTrackData(const WaveTrack * track, const wxString &msg, + double &progress, AnalyseOperation op, float &offset) { 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 auto start = track->TimeToLongSamples(mCurT0); auto end = track->TimeToLongSamples(mCurT1); @@ -409,7 +459,8 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg, //be shorter than the length of the track being processed. Floats buffer{ track->GetMaxBlockSize() }; - mSum = 0.0; // dc offset inits + mSum = 0.0; // dc offset inits + mSqSum = 0.0; // rms init mCount = 0; sampleCount blockSamples; @@ -431,14 +482,19 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg, totalSamples += blockSamples; //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 s += block; //Update the Progress meter - if (TrackProgress(curTrackNum, - ((s - start).as_double() / len)/2.0, msg)) { + if (TotalProgress(progress + + ((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) { rc = false; //lda .. break, not return, so that buffer is deleted break; } @@ -448,6 +504,7 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg, else offset = 0.0; + progress += 1.0/double(2*GetNumWaveTracks()); //Return true because the effect processing succeeded ... unless cancelled return rc; } @@ -457,7 +514,7 @@ bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg, // uses mMult and offset to normalize a track. // mMult must be set before this is called bool EffectNormalize::ProcessOne( - WaveTrack * track, const wxString &msg, int curTrackNum, float offset) + WaveTrack * track, const wxString &msg, double &progress, float offset) { bool rc = true; @@ -498,24 +555,58 @@ bool EffectNormalize::ProcessOne( s += block; //Update the Progress meter - if (TrackProgress(curTrackNum, - 0.5+((s - start).as_double() / len)/2.0, msg)) { + if (TotalProgress(progress + + ((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) { rc = false; //lda .. break, not return, so that buffer is deleted break; } } + progress += 1.0/double(2*GetNumWaveTracks()); //Return true because the effect processing succeeded ... unless cancelled 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++) mSum += (double)buffer[i]; 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) { 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)) { UpdateUI(); @@ -539,10 +670,16 @@ void EffectNormalize::UpdateUI() } mWarning->SetLabel(wxT("")); + if (mUseLoudness) + mLeveldB->SetLabel(_("LUFS")); + else + mLeveldB->SetLabel(_("dB")); + // Disallow level stuff if not normalizing mLevelTextCtrl->Enable(mGain); mLeveldB->Enable(mGain); mStereoIndCheckBox->Enable(mGain); + mUseLoudnessCheckBox->Enable(mGain); // Disallow OK/Preview if doing nothing EnableApply(mGain || mDC); diff --git a/src/effects/Normalize.h b/src/effects/Normalize.h index d0bd8b36e..eb0a4afe9 100644 --- a/src/effects/Normalize.h +++ b/src/effects/Normalize.h @@ -6,6 +6,7 @@ Dominic Mazzoni Vaughan Johnson (Preview) + Max Maisel (Loudness) **********************************************************************/ @@ -19,6 +20,7 @@ #include #include "Effect.h" +#include "Biquad.h" class ShuttleGui; @@ -58,16 +60,25 @@ public: private: // EffectNormalize implementation + enum AnalyseOperation + { + ANALYSE_DC, ANALYSE_LOUDNESS, ANALYSE_LOUDNESS_DC + }; + 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, - int curTrackNum, - float &offset, float &min, float &max); - void AnalyzeData(float *buffer, size_t len); - bool AnalyseDC(const WaveTrack * track, const wxString &msg, int curTrackNum, - float &offset); + double &progress, float &offset, float &extent); + bool AnalyseTrackData(const WaveTrack * track, const wxString &msg, double &progress, + AnalyseOperation op, float &offset); + void AnalyseDataDC(float *buffer, size_t len); + void AnalyseDataLoudness(float *buffer, size_t len); + void AnalyseDataLoudnessDC(float *buffer, size_t len); void ProcessData(float *buffer, size_t len, float offset); + void CalcEBUR128HPF(float fs); + void CalcEBUR128HSF(float fs); + void OnUpdateUI(wxCommandEvent & evt); void UpdateUI(); @@ -76,11 +87,13 @@ private: bool mGain; bool mDC; bool mStereoInd; + bool mUseLoudness; double mCurT0; double mCurT1; float mMult; double mSum; + double mSqSum; sampleCount mCount; wxCheckBox *mGainCheckBox; @@ -88,9 +101,12 @@ private: wxTextCtrl *mLevelTextCtrl; wxStaticText *mLeveldB; wxStaticText *mWarning; + wxCheckBox *mUseLoudnessCheckBox; wxCheckBox *mStereoIndCheckBox; bool mCreating; + Biquad mR128HSF; + Biquad mR128HPF; DECLARE_EVENT_TABLE() }; diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp index bb05ffda5..109fff037 100644 --- a/src/effects/ScienFilter.cpp +++ b/src/effects/ScienFilter.cpp @@ -218,12 +218,7 @@ unsigned EffectScienFilter::GetAudioOutCount() bool EffectScienFilter::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++) - { - mpBiquad[iPair].fPrevIn = 0; - mpBiquad[iPair].fPrevPrevIn = 0; - mpBiquad[iPair].fPrevOut = 0; - mpBiquad[iPair].fPrevPrevOut = 0; - } + mpBiquad[iPair].Reset(); return true; } @@ -235,7 +230,7 @@ size_t EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, size_t { mpBiquad[iPair].pfIn = ibuf; mpBiquad[iPair].pfOut = outBlock[0]; - Biquad_Process(&mpBiquad[iPair], blockLen); + mpBiquad[iPair].Process(blockLen); ibuf = outBlock[0]; } diff --git a/src/effects/ScienFilter.h b/src/effects/ScienFilter.h index 034f4d60a..cadbca6e0 100644 --- a/src/effects/ScienFilter.h +++ b/src/effects/ScienFilter.h @@ -102,7 +102,7 @@ private: int mFilterSubtype; // lowpass, highpass int mOrder; int mOrderIndex; - ArrayOf mpBiquad; + ArrayOf mpBiquad; double mdBMax; double mdBMin;