From 6da48db1275719763dc4d4357a2f0c185cd6eff3 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Fri, 15 Mar 2019 15:56:17 +0100 Subject: [PATCH 01/18] Start work on new Loudness effect This is based on my old loudness effect prototype which was included in the Normalize effect. Create all source files and add them to all build systems. Currently, the effect only consists of a GUI mockup. Create Octave+mod-script-pipe based dummy unit-test as well. --- mac/Audacity.xcodeproj/project.pbxproj | 6 + src/CMakeLists.txt | 1 + src/Makefile.am | 2 + src/effects/LoadEffects.cpp | 2 + src/effects/Loudness.cpp | 297 ++++++++++++++++++ src/effects/Loudness.h | 99 ++++++ tests/octave/README.md | 16 + tests/octave/loudness_test.m | 126 ++++++++ tests/octave/run_test.m | 174 ++++++++++ win/Projects/Audacity/Audacity.vcxproj | 4 +- .../Audacity/Audacity.vcxproj.filters | 8 +- 11 files changed, 733 insertions(+), 2 deletions(-) create mode 100644 src/effects/Loudness.cpp create mode 100644 src/effects/Loudness.h create mode 100644 tests/octave/README.md create mode 100644 tests/octave/loudness_test.m create mode 100755 tests/octave/run_test.m diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 6f71b30ba..33f72c38b 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -284,6 +284,7 @@ 1790B13F09883BFD008A330A /* LadspaEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B02609883BFD008A330A /* LadspaEffect.cpp */; }; 1790B14109883BFD008A330A /* Leveller.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B02A09883BFD008A330A /* Leveller.cpp */; }; 1790B14209883BFD008A330A /* LoadEffects.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B02C09883BFD008A330A /* LoadEffects.cpp */; }; + 1790B14219883BFD008A330A /* Loudness.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B02D19883BFD008A330A /* Loudness.cpp */; }; 1790B14309883BFD008A330A /* Noise.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B02E09883BFD008A330A /* Noise.cpp */; }; 1790B14409883BFD008A330A /* NoiseRemoval.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B03009883BFD008A330A /* NoiseRemoval.cpp */; }; 1790B14509883BFD008A330A /* Normalize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B03209883BFD008A330A /* Normalize.cpp */; }; @@ -2012,6 +2013,8 @@ 1790B02B09883BFD008A330A /* Leveller.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Leveller.h; sourceTree = ""; tabWidth = 3; }; 1790B02C09883BFD008A330A /* LoadEffects.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = LoadEffects.cpp; sourceTree = ""; tabWidth = 3; }; 1790B02D09883BFD008A330A /* LoadEffects.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = LoadEffects.h; sourceTree = ""; tabWidth = 3; }; + 1790B02D19883BFD008A330A /* Loudness.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Loudness.cpp; sourceTree = ""; tabWidth = 3; }; + 1790B02D29883BFD008A330A /* Loudness.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Loudness.h; sourceTree = ""; tabWidth = 3; }; 1790B02E09883BFD008A330A /* Noise.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Noise.cpp; sourceTree = ""; tabWidth = 3; }; 1790B02F09883BFD008A330A /* Noise.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Noise.h; sourceTree = ""; tabWidth = 3; }; 1790B03009883BFD008A330A /* NoiseRemoval.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = NoiseRemoval.cpp; sourceTree = ""; tabWidth = 3; }; @@ -4744,6 +4747,8 @@ 1790B02B09883BFD008A330A /* Leveller.h */, 1790B02C09883BFD008A330A /* LoadEffects.cpp */, 1790B02D09883BFD008A330A /* LoadEffects.h */, + 1790B02D19883BFD008A330A /* Loudness.cpp */, + 1790B02D29883BFD008A330A /* Loudness.h */, 28D587C50E264CBB009C7DEA /* lv2 */, 1790B02E09883BFD008A330A /* Noise.cpp */, 1790B02F09883BFD008A330A /* Noise.h */, @@ -8442,6 +8447,7 @@ 5E000A211EC7B5D500E8FD93 /* SampleHandle.cpp in Sources */, 1790B14109883BFD008A330A /* Leveller.cpp in Sources */, 1790B14209883BFD008A330A /* LoadEffects.cpp in Sources */, + 1790B14219883BFD008A330A /* Loudness.cpp in Sources */, 5E0784311DF1E4F400CA76EA /* UserException.cpp in Sources */, 5E36A0A8217FA2430068E082 /* EditMenus.cpp in Sources */, 1790B14309883BFD008A330A /* Noise.cpp in Sources */, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf0ad318f..5c3ee164d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -252,6 +252,7 @@ set( EFFECTS_SOURCE ${CMAKE_SOURCE_DIRECTORY}effects/Invert.cpp ${CMAKE_SOURCE_DIRECTORY}effects/Leveller.cpp ${CMAKE_SOURCE_DIRECTORY}effects/LoadEffects.cpp + ${CMAKE_SOURCE_DIRECTORY}effects/Loudness.cpp ${CMAKE_SOURCE_DIRECTORY}effects/Noise.cpp ${CMAKE_SOURCE_DIRECTORY}effects/NoiseReduction.cpp ${CMAKE_SOURCE_DIRECTORY}effects/NoiseRemoval.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 469cead07..36a515cdf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -449,6 +449,8 @@ audacity_SOURCES = \ effects/Invert.h \ effects/LoadEffects.cpp \ effects/LoadEffects.h \ + effects/Loudness.cpp \ + effects/Loudness.h \ effects/Noise.cpp \ effects/Noise.h \ effects/NoiseReduction.cpp \ diff --git a/src/effects/LoadEffects.cpp b/src/effects/LoadEffects.cpp index ea162d120..527843c0d 100644 --- a/src/effects/LoadEffects.cpp +++ b/src/effects/LoadEffects.cpp @@ -31,6 +31,7 @@ #include "Equalization.h" #include "Fade.h" #include "Invert.h" +#include "Loudness.h" #include "Noise.h" #ifdef EXPERIMENTAL_NOISE_REDUCTION #include "NoiseReduction.h" @@ -122,6 +123,7 @@ EFFECT( FILTER_CURVE, EffectEqualization, (kEqOptionCurve) ) \ EFFECT( GRAPHIC_EQ, EffectEqualization, (kEqOptionGraphic) ) \ EFFECT( INVERT, EffectInvert, () ) \ + EFFECT( LOUDNESS , EffectLoudness, () ) \ EFFECT( NORMALIZE, EffectNormalize, () ) \ EFFECT( PHASER, EffectPhaser, () ) \ EFFECT( REPAIR, EffectRepair, () ) \ diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp new file mode 100644 index 000000000..bbef6c44a --- /dev/null +++ b/src/effects/Loudness.cpp @@ -0,0 +1,297 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Loudness.cpp + + Max Maisel + +*******************************************************************//** + +\class EffectLoudness +\brief An Effect to bring the loudness level up to a chosen level. + +*//*******************************************************************/ + + +#include "../Audacity.h" // for rint from configwin.h +#include "Loudness.h" + +#include + +#include +#include + +#include "../Internat.h" +#include "../Prefs.h" +#include "../Shuttle.h" +#include "../ShuttleGui.h" +#include "../WaveTrack.h" +#include "../widgets/valnum.h" + +enum kNormalizeTargets +{ + kLoudness, + kRMS, + nAlgos +}; + +static const ComponentInterfaceSymbol kNormalizeTargetStrings[nAlgos] = +{ + { XO("perceived loudness") }, + { XO("RMS") } +}; +// Define keys, defaults, minimums, and maximums for the effect parameters +// +// Name Type Key Def Min Max Scale +Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 ); +Param( LUFSLevel, double, wxT("LUFSLevel"), -23.0, -145.0, 0.0, 1 ); +Param( RMSLevel, double, wxT("RMSLevel"), -20.0, -145.0, 0.0, 1 ); +Param( DualMono, bool, wxT("DualMono"), true, false, true, 1 ); +Param( NormalizeTo, int, wxT("NormalizeTo"), kLoudness , 0 , nAlgos-1, 1 ); + +BEGIN_EVENT_TABLE(EffectLoudness, wxEvtHandler) + EVT_CHOICE(wxID_ANY, EffectLoudness::OnUpdateUI) + EVT_CHECKBOX(wxID_ANY, EffectLoudness::OnUpdateUI) + EVT_TEXT(wxID_ANY, EffectLoudness::OnUpdateUI) +END_EVENT_TABLE() + +EffectLoudness::EffectLoudness() +{ + mStereoInd = DEF_StereoInd; + mLUFSLevel = DEF_LUFSLevel; + mRMSLevel = DEF_RMSLevel; + mDualMono = DEF_DualMono; + mNormalizeTo = DEF_NormalizeTo; + + SetLinearEffectFlag(false); +} + +EffectLoudness::~EffectLoudness() +{ +} + +// ComponentInterface implementation + +ComponentInterfaceSymbol EffectLoudness::GetSymbol() +{ + return LOUDNESS_PLUGIN_SYMBOL; +} + +wxString EffectLoudness::GetDescription() +{ + return _("Sets the loudness of one or more tracks"); +} + +wxString EffectLoudness::ManualPage() +{ + return wxT("Loudness"); +} + +// EffectDefinitionInterface implementation + +EffectType EffectLoudness::GetType() +{ + return EffectTypeProcess; +} + +// EffectClientInterface implementation +bool EffectLoudness::DefineParams( ShuttleParams & S ) +{ + S.SHUTTLE_PARAM( mStereoInd, StereoInd ); + S.SHUTTLE_PARAM( mLUFSLevel, LUFSLevel ); + S.SHUTTLE_PARAM( mRMSLevel, RMSLevel ); + S.SHUTTLE_PARAM( mDualMono, DualMono ); + S.SHUTTLE_PARAM( mNormalizeTo, NormalizeTo ); + return true; +} + +bool EffectLoudness::GetAutomationParameters(CommandParameters & parms) +{ + parms.Write(KEY_StereoInd, mStereoInd); + parms.Write(KEY_LUFSLevel, mLUFSLevel); + parms.Write(KEY_RMSLevel, mRMSLevel); + parms.Write(KEY_DualMono, mDualMono); + parms.Write(KEY_NormalizeTo, mNormalizeTo); + + return true; +} + +bool EffectLoudness::SetAutomationParameters(CommandParameters & parms) +{ + ReadAndVerifyBool(StereoInd); + ReadAndVerifyDouble(LUFSLevel); + ReadAndVerifyDouble(RMSLevel); + ReadAndVerifyBool(DualMono); + ReadAndVerifyBool(NormalizeTo); + + mStereoInd = StereoInd; + mLUFSLevel = LUFSLevel; + mRMSLevel = RMSLevel; + mDualMono = DualMono; + mNormalizeTo = NormalizeTo; + + return true; +} + +// Effect implementation + +bool EffectLoudness::CheckWhetherSkipEffect() +{ + return false; +} + +bool EffectLoudness::Startup() +{ + wxString base = wxT("/Effects/Loudness/"); + // Load the old "current" settings + if (gPrefs->Exists(base)) + { + mStereoInd = true; + mDualMono = DEF_DualMono; + mNormalizeTo = kLoudness; + mLUFSLevel = DEF_LUFSLevel; + mRMSLevel = DEF_RMSLevel; + + SaveUserPreset(GetCurrentSettingsGroup()); + + gPrefs->Flush(); + } + return true; +} + +bool EffectLoudness::Process() +{ + // TODO + return true; +} + +void EffectLoudness::PopulateOrExchange(ShuttleGui & S) +{ + S.StartVerticalLay(0); + { + S.StartMultiColumn(2, wxALIGN_CENTER); + { + S.StartVerticalLay(false); + { + S.StartHorizontalLay(wxALIGN_LEFT, false); + { + S.AddVariableText(_("Normalize"), false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + + auto targetChoices = LocalizedStrings(kNormalizeTargetStrings, nAlgos); + mNormalizeToCtl = S.AddChoice(wxEmptyString, targetChoices, mNormalizeTo); + mNormalizeToCtl->SetValidator(wxGenericValidator(&mNormalizeTo)); + S.AddVariableText(_("to"), false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + + FloatingPointValidator vldLevel(2, &mLUFSLevel, + NumValidatorStyle::ONE_TRAILING_ZERO); + vldLevel.SetRange( MIN_LUFSLevel, MAX_LUFSLevel); + + mLevelTextCtrl = S.AddTextBox( {}, wxT(""), 10); + /* i18n-hint: LUFS is a particular method for measuring loudnesss */ + mLevelTextCtrl->SetName( _("Loudness LUFS")); + mLevelTextCtrl->SetValidator(vldLevel); + /* i18n-hint: LUFS is a particular method for measuring loudnesss */ + mLeveldB = S.AddVariableText(_("LUFS"), false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + mWarning = S.AddVariableText( {}, false, + wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); + } + S.EndHorizontalLay(); + + mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"), + mStereoInd ? wxT("true") : wxT("false")); + mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd)); + + mDualMonoCheckBox = S.AddCheckBox(_("Treat mono as dual-mono (recommended)"), + mDualMono ? wxT("true") : wxT("false")); + mDualMonoCheckBox->SetValidator(wxGenericValidator(&mDualMono)); + } + S.EndVerticalLay(); + } + S.EndMultiColumn(); + } + S.EndVerticalLay(); + // To ensure that the UpdateUI on creation sets the prompts correctly. + mGUINormalizeTo = !mNormalizeTo; +} + +bool EffectLoudness::TransferDataToWindow() +{ + if (!mUIParent->TransferDataToWindow()) + { + return false; + } + + UpdateUI(); + return true; +} + +bool EffectLoudness::TransferDataFromWindow() +{ + if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) + { + return false; + } + return true; +} + +// EffectLoudness implementation + +// TODO + +bool EffectLoudness::UpdateProgress() +{ + mProgressVal += (double(1+mProcStereo) * double(mTrackBufferLen) + / (double(GetNumWaveTracks()) * double(mSteps) * mTrackLen)); + return !TotalProgress(mProgressVal, mProgressMsg); +} + +void EffectLoudness::OnUpdateUI(wxCommandEvent & WXUNUSED(evt)) +{ + UpdateUI(); +} + +void EffectLoudness::UpdateUI() +{ + if (!mUIParent->TransferDataFromWindow()) + { + mWarning->SetLabel(_("(Maximum 0dB)")); + // TODO: recalculate layout here + EnableApply(false); + return; + } + mWarning->SetLabel(wxT("")); + EnableApply(true); + + // Changing the prompts causes an unwanted UpdateUI event. + // This 'guard' stops that becoming an infinite recursion. + if (mNormalizeTo != mGUINormalizeTo) + { + mGUINormalizeTo = mNormalizeTo; + if(mNormalizeTo == kLoudness) + { + FloatingPointValidator vldLevel(2, &mLUFSLevel, NumValidatorStyle::ONE_TRAILING_ZERO); + vldLevel.SetRange(MIN_LUFSLevel, MAX_LUFSLevel); + mLevelTextCtrl->SetValidator(vldLevel); + /* i18n-hint: LUFS is a particular method for measuring loudnesss */ + mLevelTextCtrl->SetName(_("Loudness LUFS")); + mLevelTextCtrl->SetValue(wxString::FromDouble(mLUFSLevel)); + /* i18n-hint: LUFS is a particular method for measuring loudnesss */ + mLeveldB->SetLabel(_("LUFS")); + } + else // RMS + { + FloatingPointValidator vldLevel(2, &mRMSLevel, NumValidatorStyle::ONE_TRAILING_ZERO); + vldLevel.SetRange(MIN_RMSLevel, MAX_RMSLevel); + mLevelTextCtrl->SetValidator(vldLevel); + mLevelTextCtrl->SetName(_("RMS dB")); + mLevelTextCtrl->SetValue(wxString::FromDouble(mRMSLevel)); + mLeveldB->SetLabel(_("dB")); + } + } + + mDualMonoCheckBox->Enable(mNormalizeTo == kLoudness); +} diff --git a/src/effects/Loudness.h b/src/effects/Loudness.h new file mode 100644 index 000000000..ad50ac6b0 --- /dev/null +++ b/src/effects/Loudness.h @@ -0,0 +1,99 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + Loudness.h + + Max Maisel + +**********************************************************************/ + +#ifndef __AUDACITY_EFFECT_LOUDNESS__ +#define __AUDACITY_EFFECT_LOUDNESS__ + +#include +#include +#include +#include +#include +#include + +#include "Effect.h" +#include "Biquad.h" + +class ShuttleGui; + +#define LOUDNESS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Loudness") } + +class EffectLoudness final : public Effect +{ +public: + EffectLoudness(); + virtual ~EffectLoudness(); + + // ComponentInterface implementation + + ComponentInterfaceSymbol GetSymbol() override; + wxString GetDescription() override; + wxString ManualPage() override; + + // EffectDefinitionInterface implementation + + EffectType GetType() override; + + // EffectClientInterface implementation + + bool DefineParams( ShuttleParams & S ) override; + bool GetAutomationParameters(CommandParameters & parms) override; + bool SetAutomationParameters(CommandParameters & parms) override; + + // Effect implementation + + bool CheckWhetherSkipEffect() override; + bool Startup() override; + bool Process() override; + void PopulateOrExchange(ShuttleGui & S) override; + bool TransferDataToWindow() override; + bool TransferDataFromWindow() override; + +private: + // EffectLoudness implementation + + bool UpdateProgress(); + void OnUpdateUI(wxCommandEvent & evt); + void UpdateUI(); + +private: + bool mStereoInd; + double mLUFSLevel; + double mRMSLevel; + bool mDualMono; + int mNormalizeTo; + int mGUINormalizeTo; + + double mCurT0; + double mCurT1; + double mProgressVal; + int mSteps; + wxString mProgressMsg; + double mTrackLen; + double mCurRate; + + sampleCount mCount; + + wxTextCtrl *mLevelTextCtrl; + wxStaticText *mLeveldB; + wxStaticText *mWarning; + wxCheckBox *mStereoIndCheckBox; + wxChoice *mNormalizeToCtl; + wxCheckBox *mDualMonoCheckBox; + + Floats mTrackBuffer[2]; // MM: must be increased once surround channels are supported + size_t mTrackBufferLen; + size_t mTrackBufferCapacity; + bool mProcStereo; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/tests/octave/README.md b/tests/octave/README.md new file mode 100644 index 000000000..32b781be2 --- /dev/null +++ b/tests/octave/README.md @@ -0,0 +1,16 @@ +# Unit tests for Audacity effects + +These unit tests check the correctness of Audacity's effect calculations +against GNU octave. Therefore some simple deterministic and random +sample data is generated and passed to Audacity via mod-script-pipe. + +To run a test, run: (replace `` with the correct filename) +``` +./run_test.m +``` + +The tests will print the results to the terminal and will return 0 on +success and non-zero on error. + +To run those tests you need a Linux system with GNU Octave, +octave-forge-signal and Audacity mod-script-pipe installed. diff --git a/tests/octave/loudness_test.m b/tests/octave/loudness_test.m new file mode 100644 index 000000000..ef67bdca0 --- /dev/null +++ b/tests/octave/loudness_test.m @@ -0,0 +1,126 @@ +## Audacity Loudness effect unit test +# +# Max Maisel +# +# This tests the Loudness effect with 30 seconds long pseudo-random stereo +# noise sequences. The test sequences have different amplitudes per +# channel and sometimes a DC component. For best test coverage, irrelevant +# parameters for the current operation are randomly varied. +# + +printf("Running Loudness effect tests.\n"); +printf("This requires the octave-forge-signal package to be installed.\n"); + +pkg load signal; + +EXPORT_TEST_SIGNALS = true; +TEST_LUFS_HELPER = true; +# LUFS need a higher epsilon because they are a logarithmic unit. +LUFS_epsilon = 0.02; + +# A straightforward and simple LUFS implementation which can +# be easily compared with the specification ITU-R BS.1770-4. +function [gated_lufs] = calc_LUFS(x, fs) + # HSF + f0 = 38.13547087602444; + Q = 0.5003270373238773; + K = tan(pi * f0 / fs); + + rb0 = 1.0; + rb1 = -2.0; + rb2 = 1.0; + ra0 = 1.0; + ra1 = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); + ra2 = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); + + rb = [rb0 rb1 rb2]; + ra = [ra0 ra1 ra2]; + + # HPF + db = 3.999843853973347; + f0 = 1681.974450955533; + Q = 0.7071752369554196; + K = tan(pi * f0 / fs); + Vh = power(10.0, db / 20.0); + Vb = power(Vh, 0.4996667741545416); + + pa0 = 1.0; + a0 = 1.0 + K / Q + K * K; + pb0 = (Vh + Vb * K / Q + K * K) / a0; + pb1 = 2.0 * (K * K - Vh) / a0; + pb2 = (Vh - Vb * K / Q + K * K) / a0; + pa1 = 2.0 * (K * K - 1.0) / a0; + pa2 = (1.0 - K / Q + K * K) / a0; + + pb = [pb0 pb1 pb2]; + pa = [pa0 pa1 pa2]; + + # Apply k-weighting + x = filter(rb, ra, x, [], 1); + x = filter(pb, pa, x, [], 1); + + # - gating blocks (every 100 ms over 400 ms) + block_size = 0.4*fs; + block_overlap = 0.3*fs; + block_count = floor((size(x)(1)-block_size)/(block_size-block_overlap))+1+1; + + x_blocked = zeros(block_size, block_count, size(x)(2)); + for i=1:1:size(x)(2) + x_blocked(:,:,i) = buffer(x(:,i), block_size, 0.3*fs, 'nodelay'); + end + + lufs_blocked = 1/(block_size)*sum(x_blocked.^2, 1); + lufs_blocked = sum(lufs_blocked, 3); + + # Apply absolute threshold + GAMMA_A = -70; + lufs_blocked = -0.691 + 10*log10(lufs_blocked); + valid_blocks = length(lufs_blocked); + valid_blocks = valid_blocks - length(lufs_blocked(lufs_blocked < GAMMA_A)); + lufs_blocked(lufs_blocked < GAMMA_A) = -100; + lufs_blocked = 10.^((lufs_blocked+0.691)/10); + + # Apply relative threshold + GAMMA_R = -0.691 + 10*log10(sum(lufs_blocked)/valid_blocks) - 10; + lufs_blocked = -0.691 + 10*log10(lufs_blocked); + valid_blocks = length(lufs_blocked); + valid_blocks = valid_blocks - length(lufs_blocked(lufs_blocked < GAMMA_R)); + lufs_blocked(lufs_blocked < GAMMA_R) = -100; + lufs_blocked = 10.^((lufs_blocked+0.691)/10); + hold off + + gated_lufs = -0.691 + 10*log10(sum(lufs_blocked)/valid_blocks); +end + +if TEST_LUFS_HELPER + printf("Running calc_LUFS() selftest.\n"); + printf("Compare the following results with a trusted LUFS calculator.\n"); + + fs = 44100; + k = 1:1:60*fs; + x = 0.3*sin(2*pi*1000/fs*k) + 0.2*sin(2*pi*1200/fs*k); + x = (x .* [1:1:30*fs, 30*fs:-1:1]./60./fs).'; + + audiowrite(cstrcat(pwd(), "/LUFS-selftest1.wav"), x, fs); + printf("LUFS-selftest1.wav should be %f LUFS\n", calc_LUFS(x, fs)); + + randn("seed", 1); + x = [0.2*randn(2, 10*fs) zeros(2, 10*fs) 0.1*randn(2, 10*fs)].'; + x(:,1) = x(:,1) * 0.4 + 0.2; + + audiowrite(cstrcat(pwd(), "/LUFS-selftest2.wav"), x, fs); + printf("LUFS-selftest2.wav should be %f LUFS\n", calc_LUFS(x, fs)); + + fs = 8000; + randn("seed", 2); + x = [0.2*randn(2, 10*fs) zeros(2, 10*fs) 0.1*randn(2, 10*fs)].'; + x(:,1) = x(:,1) * 0.6 - 0.1; + + # MMM: I'm not sure how trustworthy free loudness meters are + # in case of non-standard sample rates. + audiowrite(cstrcat(pwd(), "/LUFS-selftest3.wav"), x, fs); + printf("LUFS-selftest3.wav should be %f LUFS\n", calc_LUFS(x, fs)); +end + +# TODO: add tests here + diff --git a/tests/octave/run_test.m b/tests/octave/run_test.m new file mode 100755 index 000000000..17b909984 --- /dev/null +++ b/tests/octave/run_test.m @@ -0,0 +1,174 @@ +#!/usr/bin/octave -qf +## Audacity Octave unit test runner +# +# Max Maisel +# + +if !(nargin == 1 || nargin == 2) + printf("Usage: ./testbench.m [-v]\n"); + exit(2); +end + +arg_list = argv(); + +if exist(arg_list{1}, "file") != 2 + printf("Specified test file does not exist!\n"); + exit(2); +end + +global VERBOSE; +VERBOSE=0; + +if nargin == 2 + if strcmp(arg_list{2}, "-v") == 1 + VERBOSE=1; + else + printf("Unknown argument %s. Abort!", arg_list{2}); + exit(2); + end +end + +## Initialization and helper functions +UID=num2str(getuid()); +PIPE_TO_PATH=strcat("/tmp/audacity_script_pipe.to.", UID); +PIPE_FROM_PATH=strcat("/tmp/audacity_script_pipe.from.", UID); +TMP_FILENAME=strcat(pwd(), "/tmp.wav"); + +printf("Open scripting pipes, this may freeze if Audacity does not run...\n"); + +global PIPE_TO; +global PIPE_FROM; +global PRINT_COMMANDS; +PRINT_COMMANDS=false; +PIPE_TO=fopen(PIPE_TO_PATH, "w"); +PIPE_FROM=fopen(PIPE_FROM_PATH, "r"); + +## aud-do helper function +function aud_do(command) + global PIPE_TO; + global PIPE_FROM; + global PRINT_COMMANDS; + if PRINT_COMMANDS + puts(command); + end + fwrite(PIPE_TO, command); + fflush(PIPE_TO); + do + string = fgets(PIPE_FROM); + if PRINT_COMMANDS + puts(string); + end + fflush(stdout); + until strncmp(string, "BatchCommand finished:", length("BatchCommand finished:")); +end + +## Float equal comparison helper +function [ret] = float_eq(x, y, eps=0.001) + ret = abs(x - y) < eps; +end + +## Test report helper +global TESTS_FAILED; +global TESTS_RUN; +global TESTS_SKIPPED; +global CURRENT_TEST; +TESTS_FAILED = 0; +TESTS_RUN = 0; +TESTS_SKIPPED = 0; +CURRENT_TEST=""; + +function plot_failure(x, y) + global VERBOSE; + if VERBOSE == 0 + return; + end + + figure(1) + plot(x, 'r') + hold on + plot(y, 'b') + plot(log10(abs(x-y)), 'g') + hold off + legend("Audacity", "Octave", "log-delta", "location", "southeast") + input("Press enter to continue", "s") +end + +function do_test_equ(x, y, msg, eps=0.001, skip = false) + cmp = all(all(float_eq(x, y, eps))); + if do_test(cmp, msg, skip) == 0 + plot_failure(x, y); + end +end + +function do_test_neq(x, y, msg, eps=0.001, skip = false) + cmp = all(all(!float_eq(x, y, eps))); + if do_test(cmp, msg, skip) == 0 + plot_failure(x, y); + end +end + +function do_test_gte(x, y, msg, skip = false) + cmp = all(all(x >= y)); + if do_test(cmp, msg, skip) == 0 + plot_failure(x, y); + end +end + +function do_test_lte(x, y, msg, skip = false) + cmp = all(all(x <= y)); + if do_test(cmp, msg, skip) == 0 + plot_failure(x, y); + end +end + +function result = do_test(result, msg, skip = false) + global TESTS_RUN; + global TESTS_FAILED; + global TESTS_SKIPPED; + global CURRENT_TEST; + TESTS_RUN = TESTS_RUN + 1; + + suffix = ""; + if !strcmp(msg, "") + suffix = cstrcat(" - ", msg); + end + + if skip + TESTS_SKIPPED = TESTS_SKIPPED + 1; + printf(cstrcat("[Skip]: ", CURRENT_TEST, suffix, "\n")); + result = 1; + else + if result + printf(cstrcat("[Success]: ", CURRENT_TEST, suffix, "\n")); + return; + else + TESTS_FAILED = TESTS_FAILED + 1; + printf(cstrcat("[Failed]: ", CURRENT_TEST, suffix, "\n")); + return; + end + end +end + +## Run tests +printf("Starting tests...\n"); +source(arg_list{1}); + +## Cleanup and result reporting +unlink(TMP_FILENAME); +fclose(PIPE_FROM); +fclose(PIPE_TO); + +printf("%d tests run, %d tests failed, %d tests skipped\n", TESTS_RUN, TESTS_FAILED, TESTS_SKIPPED); +if TESTS_FAILED != 0 + printf("Some tests failed!\n"); + if VERBOSE == 0 + printf("Re-run with -v option for details.\n"); + end + exit(1); +elseif TESTS_SKIPPED != 0 + printf("Some tests were skipped!\n"); +else + printf("All tests succeeded!\n"); +end + +exit(0) diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 545464844..0f021cecf 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -364,6 +364,7 @@ + @@ -764,6 +765,7 @@ + @@ -1348,4 +1350,4 @@ - \ No newline at end of file + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index c9d60b5af..014750295 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -452,6 +452,9 @@ src\effects + + src\effects + src\effects @@ -1609,6 +1612,9 @@ src\effects + + src\effects + src\effects @@ -2683,4 +2689,4 @@ Resources - \ No newline at end of file + From 064ddb5a54ec04aedfe9a1f2a5b54cc3b9d57be9 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 4 Aug 2018 22:45:20 +0200 Subject: [PATCH 02/18] Refactored filter calculation out of ScienFilter into Biquad. This allows easy code reuse in other effects. --- src/effects/Biquad.cpp | 281 ++++++++++++++++++++++++++++++++++++ src/effects/Biquad.h | 21 ++- src/effects/ScienFilter.cpp | 260 +++------------------------------ src/effects/ScienFilter.h | 6 +- 4 files changed, 321 insertions(+), 247 deletions(-) diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index bab5c6915..ff1961d72 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -10,8 +10,11 @@ Max Maisel ***********************************************************************/ #include "Biquad.h" +#include "Audacity.h" +#include #define square(a) ((a)*(a)) +#define PI M_PI Biquad::Biquad() { @@ -39,6 +42,284 @@ void Biquad::Process(int iNumSamples) *pfOut++ = ProcessOne(*pfIn++); } +const double Biquad::s_fChebyCoeffs[MAX_Order][MAX_Order + 1] = +{ + // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial) + // Coeffs are in the order 0, 1, 2...9 + { 0, 1}, // order 1 + {-1, 0, 2}, // order 2 etc. + { 0, -3, 0, 4}, + { 1, 0, -8, 0, 8}, + { 0, 5, 0, -20, 0, 16}, + {-1, 0, 18, 0, -48, 0, 32}, + { 0, -7, 0, 56, 0, -112, 0, 64}, + { 1, 0, -32, 0, 160, 0, -256, 0, 128}, + { 0, 9, 0, -120, 0, 432, 0, -576, 0, 256}, + {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512} +}; + +// order: filter order +// fn: nyquist frequency, i.e. half sample rate +// fc: cutoff frequency +// subtype: highpass or lowpass +ArrayOf Biquad::CalcButterworthFilter(int order, double fn, double fc, int subtype) +{ + ArrayOf pBiquad(size_t((order+1) / 2), true); + // Set up the coefficients in all the biquads + float fNorm = fc / fn; + if (fNorm >= 0.9999) + fNorm = 0.9999F; + float fC = tan (PI * fNorm / 2); + float fDCPoleDistSqr = 1.0F; + float fZPoleX, fZPoleY; + + if ((order & 1) == 0) + { + // Even order + for (int iPair = 0; iPair < order/2; iPair++) + { + float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / order); + float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / order); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + pBiquad[iPair].fNumerCoeffs [B0] = 1; + if (subtype == kLowPass) // LOWPASS + pBiquad[iPair].fNumerCoeffs [B1] = 2; + else + pBiquad[iPair].fNumerCoeffs [B1] = -2; + pBiquad[iPair].fNumerCoeffs [B2] = 1; + pBiquad[iPair].fDenomCoeffs [A1] = -2 * fZPoleX; + pBiquad[iPair].fDenomCoeffs [A2] = square(fZPoleX) + square(fZPoleY); + if (subtype == kLowPass) // LOWPASS + fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + else + fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + } + } + else + { + // Odd order - first do the 1st-order section + float fSPoleX = -fC; + float fSPoleY = 0; + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + pBiquad[0].fNumerCoeffs [B0] = 1; + if (subtype == kLowPass) // LOWPASS + pBiquad[0].fNumerCoeffs [B1] = 1; + else + pBiquad[0].fNumerCoeffs [B1] = -1; + pBiquad[0].fNumerCoeffs [B2] = 0; + pBiquad[0].fDenomCoeffs [A1] = -fZPoleX; + pBiquad[0].fDenomCoeffs [A2] = 0; + if (subtype == kLowPass) // LOWPASS + fDCPoleDistSqr = 1 - fZPoleX; + else + fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist + for (int iPair = 1; iPair <= order/2; iPair++) + { + float fSPoleX = fC * cos (PI - iPair * PI / order); + float fSPoleY = fC * sin (PI - iPair * PI / order); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + pBiquad[iPair].fNumerCoeffs [B0] = 1; + if (subtype == kLowPass) // LOWPASS + pBiquad[iPair].fNumerCoeffs [B1] = 2; + else + pBiquad[iPair].fNumerCoeffs [B1] = -2; + pBiquad[iPair].fNumerCoeffs [B2] = 1; + pBiquad[iPair].fDenomCoeffs [A1] = -2 * fZPoleX; + pBiquad[iPair].fDenomCoeffs [A2] = square(fZPoleX) + square(fZPoleY); + if (subtype == kLowPass) // LOWPASS + fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + else + fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + } + } + pBiquad[0].fNumerCoeffs [B0] *= fDCPoleDistSqr / (1 << order); // mult by DC dist from poles, divide by dist from zeroes + pBiquad[0].fNumerCoeffs [B1] *= fDCPoleDistSqr / (1 << order); + pBiquad[0].fNumerCoeffs [B2] *= fDCPoleDistSqr / (1 << order); + + return std::move(pBiquad); +} + +// order: filter order +// fn: nyquist frequency, i.e. half sample rate +// fc: cutoff frequency +// ripple: passband ripple in dB +// subtype: highpass or lowpass +ArrayOf Biquad::CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int subtype) +{ + ArrayOf pBiquad(size_t((order+1) / 2), true); + // Set up the coefficients in all the biquads + float fNorm = fc / fn; + if (fNorm >= 0.9999) + fNorm = 0.9999F; + float fC = tan (PI * fNorm / 2); + float fDCPoleDistSqr = 1.0F; + float fZPoleX, fZPoleY; + float fZZeroX; + float beta = cos (fNorm*PI); + + double eps; eps = sqrt (pow (10.0, wxMax(0.001, ripple) / 10.0) - 1); + double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order; + // Assume even order to start + for (int iPair = 0; iPair < order/2; iPair++) + { + float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * order)); + float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * order)); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + if (subtype == kLowPass) // LOWPASS + { + fZZeroX = -1; + fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist + } + pBiquad[iPair].fNumerCoeffs [B0] = fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B1] = -2 * fZZeroX * fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B2] = fDCPoleDistSqr; + pBiquad[iPair].fDenomCoeffs [A1] = -2 * fZPoleX; + pBiquad[iPair].fDenomCoeffs [A2] = square(fZPoleX) + square(fZPoleY); + } + if ((order & 1) == 0) + { + float fTemp = DB_TO_LINEAR(-wxMax(0.001, ripple)); // at DC the response is down R dB (for even-order) + pBiquad[0].fNumerCoeffs [B0] *= fTemp; + pBiquad[0].fNumerCoeffs [B1] *= fTemp; + pBiquad[0].fNumerCoeffs [B2] *= fTemp; + } + else + { + // Odd order - now do the 1st-order section + float fSPoleX = -fC * sinh (a); + float fSPoleY = 0; + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + if (subtype == kLowPass) // LOWPASS + { + fZZeroX = -1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); + fDCPoleDistSqr /= 2; // dist from zero at Nyquist + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist + fDCPoleDistSqr /= 2; // dist from zero at Nyquist + } + pBiquad[(order-1)/2].fNumerCoeffs [B0] = fDCPoleDistSqr; + pBiquad[(order-1)/2].fNumerCoeffs [B1] = -fZZeroX * fDCPoleDistSqr; + pBiquad[(order-1)/2].fNumerCoeffs [B2] = 0; + pBiquad[(order-1)/2].fDenomCoeffs [A1] = -fZPoleX; + pBiquad[(order-1)/2].fDenomCoeffs [A2] = 0; + } + return std::move(pBiquad); +} + +// order: filter order +// fn: nyquist frequency, i.e. half sample rate +// fc: cutoff frequency +// ripple: stopband ripple in dB +// subtype: highpass or lowpass +ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int subtype) +{ + ArrayOf pBiquad(size_t((order+1) / 2), true); + // Set up the coefficients in all the biquads + float fNorm = fc / fn; + if (fNorm >= 0.9999) + fNorm = 0.9999F; + float fC = tan (PI * fNorm / 2); + float fDCPoleDistSqr = 1.0F; + float fZPoleX, fZPoleY; + float fZZeroX, fZZeroY; + float beta = cos (fNorm*PI); + + float fSZeroX, fSZeroY; + float fSPoleX, fSPoleY; + double eps = DB_TO_LINEAR(-wxMax(0.001, ripple)); + double a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order; + + // Assume even order + for (int iPair = 0; iPair < order/2; iPair++) + { + ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * order)), + cosh (a) * cos ((2*iPair + 1) * PI / (2 * order)), + &fSPoleX, &fSPoleY); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + fSZeroX = 0; + fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * order)); + BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY); + + if (subtype == kLowPass) // LOWPASS + { + fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); + fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY); + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); + ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY); + fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist + fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY); + } + pBiquad[iPair].fNumerCoeffs [B0] = fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B1] = -2 * fZZeroX * fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr; + pBiquad[iPair].fDenomCoeffs [A1] = -2 * fZPoleX; + pBiquad[iPair].fDenomCoeffs [A2] = square(fZPoleX) + square(fZPoleY); + } + // Now, if it's odd order, we have one more to do + if (order & 1) + { + int iPair = (order-1)/2; // we'll do it as a biquad, but it's just first-order + ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * order)), + cosh (a) * cos ((2*iPair + 1) * PI / (2 * order)), + &fSPoleX, &fSPoleY); + BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); + fZZeroX = -1; // in the s-plane, the zero is at infinity + fZZeroY = 0; + if (subtype == kLowPass) // LOWPASS + { + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); + fDCPoleDistSqr /= 2; + } + else + { + // Highpass - do the digital LP->HP transform on the poles and zeroes + ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY); + fZZeroX = 1; + fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist + fDCPoleDistSqr /= 2; + } + pBiquad[iPair].fNumerCoeffs [B0] = fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B1] = -fZZeroX * fDCPoleDistSqr; + pBiquad[iPair].fNumerCoeffs [B2] = 0; + pBiquad[iPair].fDenomCoeffs [A1] = -fZPoleX; + pBiquad[iPair].fDenomCoeffs [A2] = 0; + } + return std::move(pBiquad); +} + +double Biquad::ChebyPoly(int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) +{ + // Calc cosh (Order * acosh (NormFreq)); + double x = 1; + double fSum = 0; + wxASSERT (Order >= MIN_Order && Order <= MAX_Order); + for (int i = 0; i <= Order; i++) + { + fSum += s_fChebyCoeffs [Order-1][i] * x; + x *= NormFreq; + } + return fSum; +} + void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) { float fDenom = square(fDenomR) + square(fDenomI); diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index 5ddd0dd79..321660c56 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -12,6 +12,7 @@ Max Maisel #ifndef __BIQUAD_H__ #define __BIQUAD_H__ +#include "MemoryX.h" /// \brief Represents a biquad digital filter. struct Biquad @@ -25,7 +26,11 @@ struct Biquad /// Numerator coefficient indices B0=0, B1, B2, /// Denominator coefficient indices - A1=0, A2 + A1=0, A2, + + /// Possible filter orders for the Calc...Filter(...) functions + MIN_Order = 1, + MAX_Order = 10 }; inline float ProcessOne(float fIn) @@ -50,6 +55,20 @@ struct Biquad float fPrevPrevIn; float fPrevOut; float fPrevPrevOut; + + enum kSubTypes + { + kLowPass, + kHighPass, + nSubTypes + }; + + static ArrayOf CalcButterworthFilter(int order, double fn, double fc, int type); + static ArrayOf CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type); + static ArrayOf CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type); + + static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1]; + static double ChebyPoly(int Order, double NormFreq); }; void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI); diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp index 70d67aeaf..a451906f7 100644 --- a/src/effects/ScienFilter.cpp +++ b/src/effects/ScienFilter.cpp @@ -105,9 +105,9 @@ static const EnumValueSymbol kTypeStrings[nTypes] = enum kSubTypes { - kLowPass, - kHighPass, - nSubTypes + kLowPass = Biquad::kLowPass, + kHighPass = Biquad::kHighPass, + nSubTypes = Biquad::nSubTypes }; static const EnumValueSymbol kSubTypeStrings[nSubTypes] = @@ -127,22 +127,6 @@ Param( Cutoff, float, wxT("Cutoff"), 1000.0, 1.0, FLT_MAX Param( Passband, float, wxT("PassbandRipple"), 1.0, 0.0, 100.0, 1 ); Param( Stopband, float, wxT("StopbandRipple"), 30.0, 0.0, 100.0, 1 ); -static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1] = -{ - // For Chebyshev polynomials of the first kind (see http://en.wikipedia.org/wiki/Chebyshev_polynomial) - // Coeffs are in the order 0, 1, 2...9 - { 0, 1}, // order 1 - {-1, 0, 2}, // order 2 etc. - { 0, -3, 0, 4}, - { 1, 0, -8, 0, 8}, - { 0, 5, 0, -20, 0, 16}, - {-1, 0, 18, 0, -48, 0, 32}, - { 0, -7, 0, 56, 0, -112, 0, 64}, - { 1, 0, -32, 0, 160, 0, -256, 0, 128}, - { 0, 9, 0, -120, 0, 432, 0, -576, 0, 256}, - {-1, 0, 50, 0, -400, 0, 1120, 0, -1280, 0, 512} -}; - //---------------------------------------------------------------------------- // EffectScienFilter //---------------------------------------------------------------------------- @@ -161,7 +145,6 @@ BEGIN_EVENT_TABLE(EffectScienFilter, wxEvtHandler) END_EVENT_TABLE() EffectScienFilter::EffectScienFilter() -: mpBiquad{ size_t( MAX_Order / 2 ), true } { mOrder = DEF_Order; mFilterType = DEF_Type; @@ -621,230 +604,20 @@ bool EffectScienFilter::TransferGraphLimitsFromWindow() return true; } -bool EffectScienFilter::CalcFilter() +void EffectScienFilter::CalcFilter() { - // Set up the coefficients in all the biquads - float fNorm = mCutoff / mNyquist; - if (fNorm >= 0.9999) - fNorm = 0.9999F; - float fC = tan (PI * fNorm / 2); - float fDCPoleDistSqr = 1.0F; - float fZPoleX, fZPoleY; - float fZZeroX, fZZeroY; - float beta = cos (fNorm*PI); switch (mFilterType) { - case kButterworth: // Butterworth - if ((mOrder & 1) == 0) - { - // Even order - for (int iPair = 0; iPair < mOrder/2; iPair++) - { - float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / mOrder); - float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / mOrder); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - mpBiquad[iPair].fNumerCoeffs [0] = 1; - if (mFilterSubtype == kLowPass) // LOWPASS - mpBiquad[iPair].fNumerCoeffs [1] = 2; - else - mpBiquad[iPair].fNumerCoeffs [1] = -2; - mpBiquad[iPair].fNumerCoeffs [2] = 1; - mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; - mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - if (mFilterSubtype == kLowPass) // LOWPASS - fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - else - fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - } - } - else - { - // Odd order - first do the 1st-order section - float fSPoleX = -fC; - float fSPoleY = 0; - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - mpBiquad[0].fNumerCoeffs [0] = 1; - if (mFilterSubtype == kLowPass) // LOWPASS - mpBiquad[0].fNumerCoeffs [1] = 1; - else - mpBiquad[0].fNumerCoeffs [1] = -1; - mpBiquad[0].fNumerCoeffs [2] = 0; - mpBiquad[0].fDenomCoeffs [0] = -fZPoleX; - mpBiquad[0].fDenomCoeffs [1] = 0; - if (mFilterSubtype == kLowPass) // LOWPASS - fDCPoleDistSqr = 1 - fZPoleX; - else - fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist - for (int iPair = 1; iPair <= mOrder/2; iPair++) - { - fSPoleX = fC * cos (PI - iPair * PI / mOrder); - fSPoleY = fC * sin (PI - iPair * PI / mOrder); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - mpBiquad[iPair].fNumerCoeffs [0] = 1; - if (mFilterSubtype == kLowPass) // LOWPASS - mpBiquad[iPair].fNumerCoeffs [1] = 2; - else - mpBiquad[iPair].fNumerCoeffs [1] = -2; - mpBiquad[iPair].fNumerCoeffs [2] = 1; - mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; - mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - if (mFilterSubtype == kLowPass) // LOWPASS - fDCPoleDistSqr *= Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - else - fDCPoleDistSqr *= Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - } - } - mpBiquad[0].fNumerCoeffs [0] *= fDCPoleDistSqr / (1 << mOrder); // mult by DC dist from poles, divide by dist from zeroes - mpBiquad[0].fNumerCoeffs [1] *= fDCPoleDistSqr / (1 << mOrder); - mpBiquad[0].fNumerCoeffs [2] *= fDCPoleDistSqr / (1 << mOrder); + case kButterworth: + mpBiquad = Biquad::CalcButterworthFilter(mOrder, mNyquist, mCutoff, mFilterSubtype); break; - - case kChebyshevTypeI: // Chebyshev Type 1 - double eps; eps = sqrt (pow (10.0, wxMax(0.001, mRipple) / 10.0) - 1); - double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / mOrder; - // Assume even order to start - for (int iPair = 0; iPair < mOrder/2; iPair++) - { - float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)); - float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - if (mFilterSubtype == kLowPass) // LOWPASS - { - fZZeroX = -1; - fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - fDCPoleDistSqr /= 2*2; // dist from zero at Nyquist - } - mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [2] = fDCPoleDistSqr; - mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; - mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - } - if ((mOrder & 1) == 0) - { - float fTemp = DB_TO_LINEAR(-wxMax(0.001, mRipple)); // at DC the response is down R dB (for even-order) - mpBiquad[0].fNumerCoeffs [0] *= fTemp; - mpBiquad[0].fNumerCoeffs [1] *= fTemp; - mpBiquad[0].fNumerCoeffs [2] *= fTemp; - } - else - { - // Odd order - now do the 1st-order section - float fSPoleX = -fC * sinh (a); - float fSPoleY = 0; - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - if (mFilterSubtype == kLowPass) // LOWPASS - { - fZZeroX = -1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); - fDCPoleDistSqr /= 2; // dist from zero at Nyquist - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist - fDCPoleDistSqr /= 2; // dist from zero at Nyquist - } - mpBiquad[(mOrder-1)/2].fNumerCoeffs [0] = fDCPoleDistSqr; - mpBiquad[(mOrder-1)/2].fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; - mpBiquad[(mOrder-1)/2].fNumerCoeffs [2] = 0; - mpBiquad[(mOrder-1)/2].fDenomCoeffs [0] = -fZPoleX; - mpBiquad[(mOrder-1)/2].fDenomCoeffs [1] = 0; - } + case kChebyshevTypeI: + mpBiquad = Biquad::CalcChebyshevType1Filter(mOrder, mNyquist, mCutoff, mRipple, mFilterSubtype); break; - - case kChebyshevTypeII: // Chebyshev Type 2 - float fSZeroX, fSZeroY; - float fSPoleX, fSPoleY; - eps = DB_TO_LINEAR(-wxMax(0.001, mStopbandRipple)); - a = log (1 / eps + sqrt(1 / square(eps) + 1)) / mOrder; - - // Assume even order - for (int iPair = 0; iPair < mOrder/2; iPair++) - { - ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)), - cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)), - &fSPoleX, &fSPoleY); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - fSZeroX = 0; - fSZeroY = fC / cos (((2 * iPair) + 1) * PI / (2 * mOrder)); - BilinTransform (fSZeroX, fSZeroY, &fZZeroX, &fZZeroY); - - if (mFilterSubtype == kLowPass) // LOWPASS - { - fDCPoleDistSqr = Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY); - fDCPoleDistSqr /= Calc2D_DistSqr (1, 0, fZZeroX, fZZeroY); - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -beta * fZPoleY, &fZPoleX, &fZPoleY); - ComplexDiv (beta - fZZeroX, -fZZeroY, 1 - beta * fZZeroX, -beta * fZZeroY, &fZZeroX, &fZZeroY); - fDCPoleDistSqr = Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY); // distance from Nyquist - fDCPoleDistSqr /= Calc2D_DistSqr (-1, 0, fZZeroX, fZZeroY); - } - mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [1] = -2 * fZZeroX * fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [2] = (square(fZZeroX) + square(fZZeroY)) * fDCPoleDistSqr; - mpBiquad[iPair].fDenomCoeffs [0] = -2 * fZPoleX; - mpBiquad[iPair].fDenomCoeffs [1] = square(fZPoleX) + square(fZPoleY); - } - // Now, if it's odd order, we have one more to do - if (mOrder & 1) - { - int iPair = (mOrder-1)/2; // we'll do it as a biquad, but it's just first-order - ComplexDiv (fC, 0, -sinh (a) * sin ((2*iPair + 1) * PI / (2 * mOrder)), - cosh (a) * cos ((2*iPair + 1) * PI / (2 * mOrder)), - &fSPoleX, &fSPoleY); - BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); - fZZeroX = -1; // in the s-plane, the zero is at infinity - fZZeroY = 0; - if (mFilterSubtype == kLowPass) // LOWPASS - { - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (1, 0, fZPoleX, fZPoleY)); - fDCPoleDistSqr /= 2; - } - else - { - // Highpass - do the digital LP->HP transform on the poles and zeroes - ComplexDiv (beta - fZPoleX, -fZPoleY, 1 - beta * fZPoleX, -fZPoleY, &fZPoleX, &fZPoleY); - fZZeroX = 1; - fDCPoleDistSqr = sqrt(Calc2D_DistSqr (-1, 0, fZPoleX, fZPoleY)); // distance from Nyquist - fDCPoleDistSqr /= 2; - } - mpBiquad[iPair].fNumerCoeffs [0] = fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [1] = -fZZeroX * fDCPoleDistSqr; - mpBiquad[iPair].fNumerCoeffs [2] = 0; - mpBiquad[iPair].fDenomCoeffs [0] = -fZPoleX; - mpBiquad[iPair].fDenomCoeffs [1] = 0; - } + case kChebyshevTypeII: + mpBiquad = Biquad::CalcChebyshevType2Filter(mOrder, mNyquist, mCutoff, mStopbandRipple, mFilterSubtype); break; } - return true; -} - -double EffectScienFilter::ChebyPoly(int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) -{ - // Calc cosh (Order * acosh (NormFreq)); - double x = 1; - double fSum = 0; - wxASSERT (Order >= MIN_Order && Order <= MAX_Order); - for (int i = 0; i <= Order; i++) - { - fSum += s_fChebyCoeffs [Order-1][i] * x; - x *= NormFreq; - } - return fSum; } float EffectScienFilter::FilterMagnAtFreq(float Freq) @@ -882,14 +655,17 @@ float EffectScienFilter::FilterMagnAtFreq(float Freq) case kChebyshevTypeI: // Chebyshev Type 1 double eps; eps = sqrt(pow (10.0, wxMax(0.001, mRipple)/10.0) - 1); + double chebyPolyVal; switch (mFilterSubtype) { case 0: // lowpass default: - Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(mOrder, FreqWarped/CutoffWarped)))); + chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped); + Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal))); break; case 1: - Magn = sqrt (1 / (1 + square(eps) * square(ChebyPoly(mOrder, CutoffWarped/FreqWarped)))); + chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped); + Magn = sqrt (1 / (1 + square(eps) * square(chebyPolyVal))); break; } break; @@ -900,10 +676,12 @@ float EffectScienFilter::FilterMagnAtFreq(float Freq) { case kLowPass: // lowpass default: - Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(mOrder, CutoffWarped/FreqWarped))))); + chebyPolyVal = Biquad::ChebyPoly(mOrder, CutoffWarped/FreqWarped); + Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal)))); break; case kHighPass: - Magn = sqrt (1 / (1 + 1 / (square(eps) * square(ChebyPoly(mOrder, FreqWarped/CutoffWarped))))); + chebyPolyVal = Biquad::ChebyPoly(mOrder, FreqWarped/CutoffWarped); + Magn = sqrt (1 / (1 + 1 / (square(eps) * square(chebyPolyVal)))); break; } break; diff --git a/src/effects/ScienFilter.h b/src/effects/ScienFilter.h index d24acab96..4a1c23b2c 100644 --- a/src/effects/ScienFilter.h +++ b/src/effects/ScienFilter.h @@ -69,12 +69,8 @@ private: // EffectScienFilter implementation bool TransferGraphLimitsFromWindow(); - bool CalcFilter(); - double ChebyPoly (int Order, double NormFreq); + void CalcFilter(); float FilterMagnAtFreq(float Freq); - - bool CalcFilterCoeffs (void); - void EnableDisableRippleCtl (int FilterType); void OnSize( wxSizeEvent & evt ); From 86ae0460c957a90cdbb1be7a49c026da570c11f9 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Mon, 6 Aug 2018 17:04:26 +0200 Subject: [PATCH 03/18] More Biquad refactoring. --- src/effects/Biquad.cpp | 45 +++++++++++++++++-------------------- src/effects/Biquad.h | 12 +++++----- src/effects/ScienFilter.cpp | 4 +--- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index ff1961d72..15cdedb66 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -18,8 +18,6 @@ Max Maisel Biquad::Biquad() { - pfIn = 0; - pfOut = 0; fNumerCoeffs[B0] = 1; fNumerCoeffs[B1] = 0; fNumerCoeffs[B2] = 0; @@ -36,7 +34,7 @@ void Biquad::Reset() fPrevPrevOut = 0; } -void Biquad::Process(int iNumSamples) +void Biquad::Process(float* pfIn, float* pfOut, int iNumSamples) { for (int i = 0; i < iNumSamples; i++) *pfOut++ = ProcessOne(*pfIn++); @@ -306,6 +304,26 @@ ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc return std::move(pBiquad); } +void Biquad::ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) +{ + float fDenom = square(fDenomR) + square(fDenomI); + *pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom; + *pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom; +} + +bool Biquad::BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY) +{ + float fDenom = square (1 - fSX) + square (fSY); + *pfZX = (1 - square (fSX) - square (fSY)) / fDenom; + *pfZY = 2 * fSY / fDenom; + return true; +} + +float Biquad::Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2) +{ + return square (fX1 - fX2) + square (fY1 - fY2); +} + double Biquad::ChebyPoly(int Order, double NormFreq) // NormFreq = 1 at the f0 point (where response is R dB down) { // Calc cosh (Order * acosh (NormFreq)); @@ -319,24 +337,3 @@ double Biquad::ChebyPoly(int Order, double NormFreq) // NormFreq = 1 at the f0 } return fSum; } - -void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) -{ - float fDenom = square(fDenomR) + square(fDenomI); - *pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom; - *pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom; -} - -bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY) -{ - float fDenom = square (1 - fSX) + square (fSY); - *pfZX = (1 - square (fSX) - square (fSY)) / fDenom; - *pfZY = 2 * fSY / fDenom; - return true; -} - -float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2) -{ - return square (fX1 - fX2) + square (fY1 - fY2); -} - diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index 321660c56..2261399bd 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -19,7 +19,7 @@ struct Biquad { Biquad(); void Reset(); - void Process(int iNumSamples); + void Process(float* pfIn, float* pfOut, int iNumSamples); enum { @@ -47,8 +47,6 @@ struct Biquad return fOut; } - float* pfIn; - float* pfOut; float fNumerCoeffs[3]; // B0 B1 B2 float fDenomCoeffs[2]; // A1 A2, A0 == 1.0 float fPrevIn; @@ -67,12 +65,12 @@ struct Biquad static ArrayOf CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type); static ArrayOf CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type); + static void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI); + static bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY); + static float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2); + static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1]; static double ChebyPoly(int Order, double NormFreq); }; -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); - #endif diff --git a/src/effects/ScienFilter.cpp b/src/effects/ScienFilter.cpp index a451906f7..137c8460e 100644 --- a/src/effects/ScienFilter.cpp +++ b/src/effects/ScienFilter.cpp @@ -218,9 +218,7 @@ size_t EffectScienFilter::ProcessBlock(float **inBlock, float **outBlock, size_t float *ibuf = inBlock[0]; for (int iPair = 0; iPair < (mOrder + 1) / 2; iPair++) { - mpBiquad[iPair].pfIn = ibuf; - mpBiquad[iPair].pfOut = outBlock[0]; - mpBiquad[iPair].Process(blockLen); + mpBiquad[iPair].Process(ibuf, outBlock[0], blockLen); ibuf = outBlock[0]; } From e439e8f4c7bb31fc0c75af435e94d5b5d0848c0b Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Mon, 6 Aug 2018 17:44:40 +0200 Subject: [PATCH 04/18] Consistently use double within Biquad filter coefficient calcultations. This avoids catastrophic rounding errors which can even lead to filter instability in some cases. This is a real problem in the coming IIR envelope detector. --- src/effects/Biquad.cpp | 69 +++++++++++++++++++++--------------------- src/effects/Biquad.h | 23 ++++++++------ 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index 15cdedb66..20c54e8f0 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -64,20 +64,20 @@ ArrayOf Biquad::CalcButterworthFilter(int order, double fn, double fc, i { ArrayOf pBiquad(size_t((order+1) / 2), true); // Set up the coefficients in all the biquads - float fNorm = fc / fn; + double fNorm = fc / fn; if (fNorm >= 0.9999) fNorm = 0.9999F; - float fC = tan (PI * fNorm / 2); - float fDCPoleDistSqr = 1.0F; - float fZPoleX, fZPoleY; + double fC = tan (PI * fNorm / 2); + double fDCPoleDistSqr = 1.0F; + double fZPoleX, fZPoleY; if ((order & 1) == 0) { // Even order for (int iPair = 0; iPair < order/2; iPair++) { - float fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / order); - float fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / order); + double fSPoleX = fC * cos (PI - (iPair + 0.5) * PI / order); + double fSPoleY = fC * sin (PI - (iPair + 0.5) * PI / order); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); pBiquad[iPair].fNumerCoeffs [B0] = 1; if (subtype == kLowPass) // LOWPASS @@ -96,8 +96,8 @@ ArrayOf Biquad::CalcButterworthFilter(int order, double fn, double fc, i else { // Odd order - first do the 1st-order section - float fSPoleX = -fC; - float fSPoleY = 0; + double fSPoleX = -fC; + double fSPoleY = 0; BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); pBiquad[0].fNumerCoeffs [B0] = 1; if (subtype == kLowPass) // LOWPASS @@ -113,8 +113,8 @@ ArrayOf Biquad::CalcButterworthFilter(int order, double fn, double fc, i fDCPoleDistSqr = fZPoleX + 1; // dist from Nyquist for (int iPair = 1; iPair <= order/2; iPair++) { - float fSPoleX = fC * cos (PI - iPair * PI / order); - float fSPoleY = fC * sin (PI - iPair * PI / order); + double fSPoleX = fC * cos (PI - iPair * PI / order); + double fSPoleY = fC * sin (PI - iPair * PI / order); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); pBiquad[iPair].fNumerCoeffs [B0] = 1; if (subtype == kLowPass) // LOWPASS @@ -146,22 +146,22 @@ ArrayOf Biquad::CalcChebyshevType1Filter(int order, double fn, double fc { ArrayOf pBiquad(size_t((order+1) / 2), true); // Set up the coefficients in all the biquads - float fNorm = fc / fn; + double fNorm = fc / fn; if (fNorm >= 0.9999) fNorm = 0.9999F; - float fC = tan (PI * fNorm / 2); - float fDCPoleDistSqr = 1.0F; - float fZPoleX, fZPoleY; - float fZZeroX; - float beta = cos (fNorm*PI); + double fC = tan (PI * fNorm / 2); + double fDCPoleDistSqr = 1.0F; + double fZPoleX, fZPoleY; + double fZZeroX; + double beta = cos (fNorm*PI); double eps; eps = sqrt (pow (10.0, wxMax(0.001, ripple) / 10.0) - 1); double a; a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order; // Assume even order to start for (int iPair = 0; iPair < order/2; iPair++) { - float fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * order)); - float fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * order)); + double fSPoleX = -fC * sinh (a) * sin ((2*iPair + 1) * PI / (2 * order)); + double fSPoleY = fC * cosh (a) * cos ((2*iPair + 1) * PI / (2 * order)); BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); if (subtype == kLowPass) // LOWPASS { @@ -185,7 +185,7 @@ ArrayOf Biquad::CalcChebyshevType1Filter(int order, double fn, double fc } if ((order & 1) == 0) { - float fTemp = DB_TO_LINEAR(-wxMax(0.001, ripple)); // at DC the response is down R dB (for even-order) + double fTemp = DB_TO_LINEAR(-wxMax(0.001, ripple)); // at DC the response is down R dB (for even-order) pBiquad[0].fNumerCoeffs [B0] *= fTemp; pBiquad[0].fNumerCoeffs [B1] *= fTemp; pBiquad[0].fNumerCoeffs [B2] *= fTemp; @@ -193,8 +193,8 @@ ArrayOf Biquad::CalcChebyshevType1Filter(int order, double fn, double fc else { // Odd order - now do the 1st-order section - float fSPoleX = -fC * sinh (a); - float fSPoleY = 0; + double fSPoleX = -fC * sinh (a); + double fSPoleY = 0; BilinTransform (fSPoleX, fSPoleY, &fZPoleX, &fZPoleY); if (subtype == kLowPass) // LOWPASS { @@ -228,17 +228,17 @@ ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc { ArrayOf pBiquad(size_t((order+1) / 2), true); // Set up the coefficients in all the biquads - float fNorm = fc / fn; + double fNorm = fc / fn; if (fNorm >= 0.9999) fNorm = 0.9999F; - float fC = tan (PI * fNorm / 2); - float fDCPoleDistSqr = 1.0F; - float fZPoleX, fZPoleY; - float fZZeroX, fZZeroY; - float beta = cos (fNorm*PI); + double fC = tan (PI * fNorm / 2); + double fDCPoleDistSqr = 1.0F; + double fZPoleX, fZPoleY; + double fZZeroX, fZZeroY; + double beta = cos (fNorm*PI); - float fSZeroX, fSZeroY; - float fSPoleX, fSPoleY; + double fSZeroX, fSZeroY; + double fSPoleX, fSPoleY; double eps = DB_TO_LINEAR(-wxMax(0.001, ripple)); double a = log (1 / eps + sqrt(1 / square(eps) + 1)) / order; @@ -304,22 +304,23 @@ ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc return std::move(pBiquad); } -void Biquad::ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI) +void Biquad::ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, + double* pfQuotientR, double* pfQuotientI) { - float fDenom = square(fDenomR) + square(fDenomI); + double fDenom = square(fDenomR) + square(fDenomI); *pfQuotientR = (fNumerR * fDenomR + fNumerI * fDenomI) / fDenom; *pfQuotientI = (fNumerI * fDenomR - fNumerR * fDenomI) / fDenom; } -bool Biquad::BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY) +bool Biquad::BilinTransform (double fSX, double fSY, double* pfZX, double* pfZY) { - float fDenom = square (1 - fSX) + square (fSY); + double fDenom = square (1 - fSX) + square (fSY); *pfZX = (1 - square (fSX) - square (fSY)) / fDenom; *pfZY = 2 * fSY / fDenom; return true; } -float Biquad::Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2) +float Biquad::Calc2D_DistSqr (double fX1, double fY1, double fX2, double fY2) { return square (fX1 - fX2) + square (fY1 - fY2); } diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index 2261399bd..dcb667e2b 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -35,7 +35,9 @@ struct Biquad inline float ProcessOne(float fIn) { - float fOut = fIn * fNumerCoeffs[B0] + + // Biquad must use double for all calculations. Otherwise some + // filters may have catastrophic rounding errors! + double fOut = double(fIn) * fNumerCoeffs[B0] + fPrevIn * fNumerCoeffs[B1] + fPrevPrevIn * fNumerCoeffs[B2] - fPrevOut * fDenomCoeffs[A1] - @@ -47,12 +49,12 @@ struct Biquad return fOut; } - float fNumerCoeffs[3]; // B0 B1 B2 - float fDenomCoeffs[2]; // A1 A2, A0 == 1.0 - float fPrevIn; - float fPrevPrevIn; - float fPrevOut; - float fPrevPrevOut; + double fNumerCoeffs[3]; // B0 B1 B2 + double fDenomCoeffs[2]; // A1 A2, A0 == 1.0 + double fPrevIn; + double fPrevPrevIn; + double fPrevOut; + double fPrevPrevOut; enum kSubTypes { @@ -65,9 +67,10 @@ struct Biquad static ArrayOf CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type); static ArrayOf CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type); - static void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI); - static bool BilinTransform (float fSX, float fSY, float* pfZX, float* pfZY); - static float Calc2D_DistSqr (float fX1, float fY1, float fX2, float fY2); + static void ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, + double* pfQuotientR, double* pfQuotientI); + static bool BilinTransform (double fSX, double fSY, double* pfZX, double* pfZY); + static float Calc2D_DistSqr (double fX1, double fY1, double fX2, double fY2); static const double s_fChebyCoeffs[MAX_Order][MAX_Order + 1]; static double ChebyPoly(int Order, double NormFreq); From c3d25cfb44c2ff7906473efff6b7e93b9ef5f9e5 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Fri, 15 Mar 2019 16:22:53 +0100 Subject: [PATCH 05/18] Remove all Loudness related code from Normalize effect. --- src/Experimental.h | 3 - src/effects/Normalize.cpp | 282 +++----------------------------------- src/effects/Normalize.h | 24 ---- 3 files changed, 22 insertions(+), 287 deletions(-) diff --git a/src/Experimental.h b/src/Experimental.h index f41da64da..c1dcc2548 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -253,9 +253,6 @@ // PRL 31 July 2018 #define EXPERIMENTAL_DRAGGABLE_PLAY_HEAD -// mmm-1 22 Aug 2018 -//#define EXPERIMENTAL_R128_NORM - // JKC 29 July 2019 // OD_DATA made experimental. It is on the way out because // it is dangerous and has too many bugs. See bug 536 for example. diff --git a/src/effects/Normalize.cpp b/src/effects/Normalize.cpp index 2efb7ca2a..636367602 100644 --- a/src/effects/Normalize.cpp +++ b/src/effects/Normalize.cpp @@ -6,7 +6,6 @@ Dominic Mazzoni Vaughan Johnson (Preview) - Max Maisel (Loudness) *******************************************************************//** @@ -43,10 +42,6 @@ Param( PeakLevel, double, wxT("PeakLevel"), -1.0, -145.0, 0.0, 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 ); -#ifdef EXPERIMENTAL_R128_NORM -Param( LUFSLevel, double, wxT("LUFSLevel"), -23.0, -145.0, 0.0, 1 ); -Param( UseLoudness, bool, wxT("UseLoudness"), false, false, true, 1 ); -#endif BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler) EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI) @@ -59,10 +54,6 @@ EffectNormalize::EffectNormalize() mDC = DEF_RemoveDC; mGain = DEF_ApplyGain; mStereoInd = DEF_StereoInd; -#ifdef EXPERIMENTAL_R128_NORM - mLUFSLevel = DEF_LUFSLevel; - mUseLoudness = DEF_UseLoudness; -#endif SetLinearEffectFlag(false); } @@ -80,11 +71,7 @@ ComponentInterfaceSymbol EffectNormalize::GetSymbol() wxString EffectNormalize::GetDescription() { -#ifdef EXPERIMENTAL_R128_NORM - return _("Sets the peak amplitude or loudness of one or more tracks"); -#else return _("Sets the peak amplitude of one or more tracks"); -#endif } wxString EffectNormalize::ManualPage() @@ -105,10 +92,6 @@ bool EffectNormalize::DefineParams( ShuttleParams & S ){ S.SHUTTLE_PARAM( mGain, ApplyGain ); S.SHUTTLE_PARAM( mDC, RemoveDC ); S.SHUTTLE_PARAM( mStereoInd, StereoInd ); -#ifdef EXPERIMENTAL_R128_NORM - S.SHUTTLE_PARAM( mLUFSLevel, LUFSLevel ); - S.SHUTTLE_PARAM( mUseLoudness, UseLoudness ); -#endif return true; } @@ -118,10 +101,6 @@ bool EffectNormalize::GetAutomationParameters(CommandParameters & parms) parms.Write(KEY_ApplyGain, mGain); parms.Write(KEY_RemoveDC, mDC); parms.Write(KEY_StereoInd, mStereoInd); -#ifdef EXPERIMENTAL_R128_NORM - parms.Write(KEY_LUFSLevel, mLUFSLevel); - parms.Write(KEY_UseLoudness, mUseLoudness); -#endif return true; } @@ -132,19 +111,11 @@ bool EffectNormalize::SetAutomationParameters(CommandParameters & parms) ReadAndVerifyBool(ApplyGain); ReadAndVerifyBool(RemoveDC); ReadAndVerifyBool(StereoInd); -#ifdef EXPERIMENTAL_R128_NORM - ReadAndVerifyDouble(LUFSLevel); - ReadAndVerifyBool(UseLoudness); -#endif mPeakLevel = PeakLevel; mGain = ApplyGain; mDC = RemoveDC; mStereoInd = StereoInd; -#ifdef EXPERIMENTAL_R128_NORM - mLUFSLevel = LUFSLevel; - mUseLoudness = UseLoudness; -#endif return true; } @@ -180,10 +151,6 @@ bool EffectNormalize::Startup() mPeakLevel = -mPeakLevel; boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L); mStereoInd = (boolProxy == 1); -#ifdef EXPERIMENTAL_R128_NORM - mUseLoudness = false; - mLUFSLevel = DEF_LUFSLevel; -#endif SaveUserPreset(GetCurrentSettingsGroup()); @@ -203,20 +170,8 @@ bool EffectNormalize::Process() float ratio; if( mGain ) { -#ifdef EXPERIMENTAL_R128_NORM - 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(mLUFSLevel*2, MIN_LUFSLevel, MAX_LUFSLevel)); - } - else { - // same value used for all tracks - ratio = DB_TO_LINEAR(TrapDouble(mPeakLevel, MIN_PeakLevel, MAX_PeakLevel)); - } -#else // same value used for all tracks ratio = DB_TO_LINEAR(TrapDouble(mPeakLevel, MIN_PeakLevel, MAX_PeakLevel)); -#endif } else { ratio = 1.0; @@ -257,16 +212,8 @@ bool EffectNormalize::Process() wxString trackName = track->GetName(); float extent; -#ifdef EXPERIMENTAL_R128_NORM - if (mUseLoudness) - // Loudness: use sum of both tracks. - // As a result, stereo tracks appear about 3 LUFS louder, - // as specified. - extent = 0; - else -#endif - // Will compute a maximum - extent = std::numeric_limits::lowest(); + // Will compute a maximum + extent = std::numeric_limits::lowest(); std::vector offsets; wxString msg; @@ -287,12 +234,7 @@ bool EffectNormalize::Process() AnalyseTrack( channel, msg, progress, offset, extent2 ); if ( ! bGoodResult ) goto break2; -#ifdef EXPERIMENTAL_R128_NORM - if (mUseLoudness) - extent += extent2; - else -#endif - extent = std::max( extent, extent2 ); + extent = std::max( extent, extent2 ); offsets.push_back(offset); // TODO: more-than-two-channels-message msg = topMsg + @@ -302,18 +244,6 @@ bool EffectNormalize::Process() // Compute the multiplier using extent if( (extent > 0) && mGain ) { mMult = ratio / extent; -#ifdef EXPERIMENTAL_R128_NORM - if(mUseLoudness) { - // PRL: See commit 9cbb67a for the origin of the next line, - // which has no effect because mMult is again overwritten. What - // was the intent? - - // LUFS is defined as -0.691 dB + 10*log10(sum(channels)) - mMult /= 0.8529037031; - // LUFS are related to square values so the multiplier must be the root. - mMult = sqrt(ratio / extent); - } -#endif } else mMult = 1.0; @@ -373,12 +303,7 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S) // which that is will depend on translation. So decide that here. // (strictly we should count pixels, not characters). wxString prompt1 = _("Normalize peak amplitude to"); -#ifdef EXPERIMENTAL_R128_NORM - wxString prompt2 = _("Normalize loudness to"); - wxString longerPrompt = ((prompt1.length() > prompt2.length()) ? prompt1 : prompt2) + " "; -#else wxString longerPrompt = prompt1 + " "; -#endif // Now make the checkbox. mGainCheckBox = S.AddCheckBox(longerPrompt, mGain); @@ -398,11 +323,6 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S) wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); } S.EndHorizontalLay(); -#ifdef EXPERIMENTAL_R128_NORM - mUseLoudnessCheckBox = S.AddCheckBox(_("Use loudness instead of peak amplitude"), - mUseLoudness); - mUseLoudnessCheckBox->SetValidator(wxGenericValidator(&mGUIUseLoudness)); -#endif mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"), mStereoInd); mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd)); @@ -412,10 +332,6 @@ void EffectNormalize::PopulateOrExchange(ShuttleGui & S) S.EndMultiColumn(); } S.EndVerticalLay(); -#ifdef EXPERIMENTAL_R128_NORM - // To ensure that the UpdateUI on creation sets the prompts correctly. - mUseLoudness = !mGUIUseLoudness; -#endif mCreating = false; } @@ -451,51 +367,27 @@ bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg, if(mGain) { -#ifdef EXPERIMENTAL_R128_NORM - 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; - } - - // EBU R128: z_i = mean square without root - extent = mSqSum / mCount.as_double(); + // 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 (ProjectFileManager::GetODFlags( *track )) { + // update the gui + if (ProgressResult::Cancelled == mProgress->Update( + 0, _("Waiting for waveform to finish computing...")) ) + return false; + wxMilliSleep(100); } - else + + // 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; + + if(mDC) { -#endif - // 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 (ProjectFileManager::GetODFlags( *track )) { - // 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; - - if(mDC) - { - result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset); - min += offset; - max += offset; - } -#ifdef EXPERIMENTAL_R128_NORM + result = AnalyseTrackData(track, msg, progress, ANALYSE_DC, offset); + min += offset; + max += offset; } -#endif } else if(mDC) { @@ -510,10 +402,7 @@ bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg, min = -1.0, max = 1.0; // sensible defaults? offset = 0.0; } -#ifdef EXPERIMENTAL_R128_NORM - if(!mUseLoudness) -#endif - extent = fmax(fabs(min), fabs(max)); + extent = fmax(fabs(min), fabs(max)); return result; } @@ -540,9 +429,6 @@ bool EffectNormalize::AnalyseTrackData(const WaveTrack * track, const wxString & mSum = 0.0; // dc offset inits mCount = 0; -#ifdef EXPERIMENTAL_R128_NORM - mSqSum = 0.0; // rms init -#endif sampleCount blockSamples; sampleCount totalSamples = 0; @@ -565,12 +451,6 @@ bool EffectNormalize::AnalyseTrackData(const WaveTrack * track, const wxString & //Process the buffer. if(op == ANALYSE_DC) AnalyseDataDC(buffer.get(), block); -#ifdef EXPERIMENTAL_R128_NORM - else if(op == ANALYSE_LOUDNESS) - AnalyseDataLoudness(buffer.get(), block); - else if(op == ANALYSE_LOUDNESS_DC) - AnalyseDataLoudnessDC(buffer.get(), block); -#endif //Increment s one blockfull of samples s += block; @@ -658,40 +538,6 @@ void EffectNormalize::AnalyseDataDC(float *buffer, size_t len) mCount += len; } -#ifdef EXPERIMENTAL_R128_NORM -/// @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; -} -#endif - void EffectNormalize::ProcessData(float *buffer, size_t len, float offset) { for(decltype(len) i = 0; i < len; i++) { @@ -700,56 +546,6 @@ void EffectNormalize::ProcessData(float *buffer, size_t len, float offset) } } -#ifdef EXPERIMENTAL_R128_NORM -// EBU R128 parameter sampling rate adaption after -// Mansbridge, Stuart, Saoirse Finn, and Joshua D. Reiss. -// "Implementation and Evaluation of Autonomous Multi-track Fader Control." -// Paper presented at the 132nd Audio Engineering Society Convention, -// Budapest, Hungary, 2012." -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); -} - -// EBU R128 parameter sampling rate adaption after -// Mansbridge, Stuart, Saoirse Finn, and Joshua D. Reiss. -// "Implementation and Evaluation of Autonomous Multi-track Fader Control." -// Paper presented at the 132nd Audio Engineering Society Convention, -// Budapest, Hungary, 2012." -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; -} -#endif - void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt)) { UpdateUI(); @@ -766,44 +562,10 @@ void EffectNormalize::UpdateUI() } mWarning->SetLabel(wxT("")); -#ifdef EXPERIMENTAL_R128_NORM - // Changing the prompts causes an unwanted UpdateUI event. - // This 'guard' stops that becoming an infinite recursion. - if (mUseLoudness != mGUIUseLoudness) - { - mUseLoudness = mGUIUseLoudness; - if (mUseLoudness) - { - FloatingPointValidator vldLevel(2, &mLUFSLevel, NumValidatorStyle::ONE_TRAILING_ZERO); - vldLevel.SetRange(MIN_LUFSLevel, MAX_LUFSLevel); - mLevelTextCtrl->SetValidator(vldLevel); - /* i18n-hint: LUFS is a particular method for measuring loudnesss */ - mLevelTextCtrl->SetName(_("Loudness LUFS")); - mLevelTextCtrl->SetValue(wxString::FromDouble(mLUFSLevel)); - /* i18n-hint: LUFS is a particular method for measuring loudnesss */ - mLeveldB->SetLabel(_("LUFS")); - mGainCheckBox->SetLabelText(_("Normalize loudness to")); - } - else - { - FloatingPointValidator vldLevel(2, &mPeakLevel, NumValidatorStyle::ONE_TRAILING_ZERO); - vldLevel.SetRange(MIN_PeakLevel, MAX_PeakLevel); - mLevelTextCtrl->SetValidator(vldLevel); - mLevelTextCtrl->SetName(_("Peak amplitude dB")); - mLevelTextCtrl->SetValue(wxString::FromDouble(mPeakLevel)); - mLeveldB->SetLabel(_("dB")); - mGainCheckBox->SetLabelText(_("Normalize peak amplitude to")); - } - } -#endif - // Disallow level stuff if not normalizing mLevelTextCtrl->Enable(mGain); mLeveldB->Enable(mGain); mStereoIndCheckBox->Enable(mGain); -#ifdef EXPERIMENTAL_R128_NORM - mUseLoudnessCheckBox->Enable(mGain); -#endif // Disallow OK/Preview if doing nothing EnableApply(mGain || mDC); diff --git a/src/effects/Normalize.h b/src/effects/Normalize.h index 2e334f9b9..b3a176460 100644 --- a/src/effects/Normalize.h +++ b/src/effects/Normalize.h @@ -6,7 +6,6 @@ Dominic Mazzoni Vaughan Johnson (Preview) - Max Maisel (Loudness) **********************************************************************/ @@ -71,17 +70,8 @@ private: bool AnalyseTrackData(const WaveTrack * track, const wxString &msg, double &progress, AnalyseOperation op, float &offset); void AnalyseDataDC(float *buffer, size_t len); -#ifdef EXPERIMENTAL_R128_NORM - void AnalyseDataLoudness(float *buffer, size_t len); - void AnalyseDataLoudnessDC(float *buffer, size_t len); -#endif void ProcessData(float *buffer, size_t len, float offset); -#ifdef EXPERIMENTAL_R128_NORM - void CalcEBUR128HPF(float fs); - void CalcEBUR128HSF(float fs); -#endif - void OnUpdateUI(wxCommandEvent & evt); void UpdateUI(); @@ -90,19 +80,11 @@ private: bool mGain; bool mDC; bool mStereoInd; -#ifdef EXPERIMENTAL_R128_NORM - double mLUFSLevel; - bool mUseLoudness; - bool mGUIUseLoudness; -#endif double mCurT0; double mCurT1; float mMult; double mSum; -#ifdef EXPERIMENTAL_R128_NORM - double mSqSum; -#endif sampleCount mCount; wxCheckBox *mGainCheckBox; @@ -111,12 +93,6 @@ private: wxStaticText *mLeveldB; wxStaticText *mWarning; wxCheckBox *mStereoIndCheckBox; -#ifdef EXPERIMENTAL_R128_NORM - wxCheckBox *mUseLoudnessCheckBox; - - Biquad mR128HSF; - Biquad mR128HPF; -#endif bool mCreating; From c002165952fd97d68aa1535cae9c3dd32bd2be0c Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Fri, 15 Mar 2019 16:27:32 +0100 Subject: [PATCH 06/18] Add EBU R128 k-weighting filter implementation to Biquad. --- src/effects/Biquad.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++ src/effects/Biquad.h | 1 + 2 files changed, 50 insertions(+) diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index 20c54e8f0..183a9be69 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -304,6 +304,55 @@ ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc return std::move(pBiquad); } +// fs: sample rate +// returns array of two Biquads +// +// EBU R128 parameter sampling rate adaption after +// Mansbridge, Stuart, Saoirse Finn, and Joshua D. Reiss. +// "Implementation and Evaluation of Autonomous Multi-track Fader Control." +// Paper presented at the 132nd Audio Engineering Society Convention, +// Budapest, Hungary, 2012." +ArrayOf Biquad::CalcEBUR128WeightingFilter(float fs) +{ + ArrayOf pBiquad(size_t(2), true); + + // + // HSF pre filter + // + 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; + + pBiquad[0].fNumerCoeffs[Biquad::B0] = (Vh + Vb * K / Q + K * K) / a0; + pBiquad[0].fNumerCoeffs[Biquad::B1] = 2.0 * (K * K - Vh) / a0; + pBiquad[0].fNumerCoeffs[Biquad::B2] = (Vh - Vb * K / Q + K * K) / a0; + + pBiquad[0].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / a0; + pBiquad[0].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / a0; + + // + // HPF weighting filter + // + f0 = 38.13547087602444; + Q = 0.5003270373238773; + K = tan(M_PI * f0 / fs); + + pBiquad[1].fNumerCoeffs[Biquad::B0] = 1.0; + pBiquad[1].fNumerCoeffs[Biquad::B1] = -2.0; + pBiquad[1].fNumerCoeffs[Biquad::B2] = 1.0; + + pBiquad[1].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); + pBiquad[1].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); + + return std::move(pBiquad); +} + void Biquad::ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, double* pfQuotientR, double* pfQuotientI) { diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index dcb667e2b..29bb8c746 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -66,6 +66,7 @@ struct Biquad static ArrayOf CalcButterworthFilter(int order, double fn, double fc, int type); static ArrayOf CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type); static ArrayOf CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type); + static ArrayOf CalcEBUR128WeightingFilter(float fs); static void ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, double* pfQuotientR, double* pfQuotientI); From d8424755d156f91f53c2c455b2a16ad546b2df28 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Fri, 15 Mar 2019 19:41:52 +0100 Subject: [PATCH 07/18] Add buffer handling and flow control code to new Loudness effect. Currently, the effect still does nothing. --- src/effects/Loudness.cpp | 236 ++++++++++++++++++++++++++++++++++++++- src/effects/Loudness.h | 15 ++- 2 files changed, 247 insertions(+), 4 deletions(-) diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp index bbef6c44a..c1f9f007d 100644 --- a/src/effects/Loudness.cpp +++ b/src/effects/Loudness.cpp @@ -160,10 +160,76 @@ bool EffectLoudness::Startup() return true; } +// TODO: more method extraction bool EffectLoudness::Process() { - // TODO - return true; + if(mNormalizeTo == kLoudness) + // LU use 10*log10(...) instead of 20*log10(...) + // so multiply level by 2 and use standard DB_TO_LINEAR macro. + mRatio = DB_TO_LINEAR(TrapDouble(mLUFSLevel*2, MIN_LUFSLevel, MAX_LUFSLevel)); + + // Iterate over each track + this->CopyInputTracks(); // Set up mOutputTracks. + bool bGoodResult = true; + wxString topMsg = _("Normalizing Loudness...\n"); + + AllocBuffers(); + mProgressVal = 0; + + for(auto track : mOutputTracks->Selected() + + (mStereoInd ? &Track::Any : &Track::IsLeader)) + { + // Get start and end times from track + // PRL: No accounting for multiple channels ? + 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; + + // Get the track rate + mCurRate = track->GetRate(); + + wxString msg; + auto trackName = track->GetName(); + mSteps = 2; + + mProgressMsg = + topMsg + wxString::Format(_("Analyzing: %s"), trackName); + + auto range = mStereoInd + ? TrackList::SingletonRange(track) + : TrackList::Channels(track); + + mProcStereo = range.size() > 1; + + InitTrackAnalysis(); + if(!ProcessOne(range, true)) + { + // Processing failed -> abort + bGoodResult = false; + break; + } + + // Calculate normalization values the analysis results + float extent = 1.0; + // TODO: add it in separate method + mMult = mRatio / extent; + + mProgressMsg = topMsg + wxString::Format(_("Processing: %s"), trackName); + if(!ProcessOne(range, false)) + { + // Processing failed -> abort + bGoodResult = false; + break; + } + } + + this->ReplaceProcessedTracks(bGoodResult); + FreeBuffers(); + return bGoodResult; } void EffectLoudness::PopulateOrExchange(ShuttleGui & S) @@ -240,7 +306,171 @@ bool EffectLoudness::TransferDataFromWindow() // EffectLoudness implementation -// TODO +/// Get required buffer size for the largest whole track and allocate buffers. +/// This reduces the amount of allocations required. +void EffectLoudness::AllocBuffers() +{ + mTrackBufferCapacity = 0; + bool stereoTrackFound = false; + double maxSampleRate = 0; + mProcStereo = false; + + for(auto track : mOutputTracks->Selected() + &Track::Any) + { + mTrackBufferCapacity = std::max(mTrackBufferCapacity, track->GetMaxBlockSize()); + maxSampleRate = std::max(maxSampleRate, track->GetRate()); + + // There is a stereo track + if(track->IsLeader()) + stereoTrackFound = true; + } + + // TODO: hist and block buffers go here + + // Initiate a processing buffer. This buffer will (most likely) + // be shorter than the length of the track being processed. + mTrackBuffer[0].reinit(mTrackBufferCapacity); + + if(!mStereoInd && stereoTrackFound) + mTrackBuffer[1].reinit(mTrackBufferCapacity); +} + +void EffectLoudness::FreeBuffers() +{ + mTrackBuffer[0].reset(); + mTrackBuffer[1].reset(); + // TODO: additional destroy -> function +} + +void EffectLoudness::InitTrackAnalysis() +{ + mCount = 0; + // TODO: additional init goes here +} + +/// ProcessOne() takes a track, transforms it to bunch of buffer-blocks, +/// and executes ProcessData, on it... +/// uses mMult to normalize a track. +/// mMult must be set before this is called +/// In analyse mode, it executes the selected analyse operation on it... +/// mMult does not have to be set before this is called +bool EffectLoudness::ProcessOne(TrackIterRange range, bool analyse) +{ + WaveTrack* track = *range.begin(); + + // Transform the marker timepoints to samples + auto start = track->TimeToLongSamples(mCurT0); + auto end = track->TimeToLongSamples(mCurT1); + + // Get the length of the buffer (as double). len is + // used simply to calculate a progress meter, so it is easier + // to make it a double now than it is to do it later + mTrackLen = (end - start).as_double(); + + // Abort if the right marker is not to the right of the left marker + if(mCurT1 <= mCurT0) + return false; + + // Go through the track one buffer at a time. s counts which + // sample the current buffer starts at. + auto s = start; + 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 + auto blockLen = limitSampleBufferSize( + track->GetBestBlockSize(s), + mTrackBufferCapacity); + + const size_t remainingLen = (end - s).as_size_t(); + blockLen = blockLen > remainingLen ? remainingLen : blockLen; + if(!LoadBufferBlock(range, s, blockLen)) + return false; + + // Process the buffer. + if(analyse) + { + if(!AnalyseBufferBlock()) + return false; + } + else + { + if(!ProcessBufferBlock()) + return false; + } + + sleep(1); + + if(!analyse) + StoreBufferBlock(range, s, blockLen); + + // Increment s one blockfull of samples + s += blockLen; + } + + // Return true because the effect processing succeeded ... unless cancelled + return true; +} + +bool EffectLoudness::LoadBufferBlock(TrackIterRange range, + sampleCount pos, size_t len) +{ + sampleCount read_size = -1; + // Get the samples from the track and put them in the buffer + int idx = 0; + for(auto channel : range) + { + channel->Get((samplePtr) mTrackBuffer[idx].get(), floatSample, pos, len, + fillZero, true, &read_size); + mTrackBufferLen = read_size.as_size_t(); + + // Fail if we read different sample count from stereo pair tracks. + // Ignore this check during first iteration (read_size == -1). + if(read_size.as_size_t() != mTrackBufferLen && read_size != -1) + return false; + + ++idx; + } + return true; +} + +/// Calculates sample sum (for DC) and EBU R128 weighted square sum +/// (for loudness). +bool EffectLoudness::AnalyseBufferBlock() +{ + // TODO: analysis loop goes here + mCount += mTrackBufferLen; + + if(!UpdateProgress()) + return false; + return true; +} + +bool EffectLoudness::ProcessBufferBlock() +{ + for(size_t i = 0; i < mTrackBufferLen; i++) + { + mTrackBuffer[0][i] = mTrackBuffer[0][i] * mMult; + if(mProcStereo) + mTrackBuffer[1][i] = mTrackBuffer[1][i] * mMult; + } + + if(!UpdateProgress()) + return false; + return true; +} + +void EffectLoudness::StoreBufferBlock(TrackIterRange range, + sampleCount pos, size_t len) +{ + int idx = 0; + for(auto channel : range) + { + // Copy the newly-changed samples back onto the track. + channel->Set((samplePtr) mTrackBuffer[idx].get(), floatSample, pos, len); + ++idx; + } +} bool EffectLoudness::UpdateProgress() { diff --git a/src/effects/Loudness.h b/src/effects/Loudness.h index ad50ac6b0..441c0928b 100644 --- a/src/effects/Loudness.h +++ b/src/effects/Loudness.h @@ -4,7 +4,7 @@ Loudness.h - Max Maisel + Max Maisel (based on Normalize effect) **********************************************************************/ @@ -59,6 +59,17 @@ public: private: // EffectLoudness implementation + void AllocBuffers(); + void FreeBuffers(); + void InitTrackAnalysis(); + bool ProcessOne(TrackIterRange range, bool analyse); + bool LoadBufferBlock(TrackIterRange range, + sampleCount pos, size_t len); + bool AnalyseBufferBlock(); + bool ProcessBufferBlock(); + void StoreBufferBlock(TrackIterRange range, + sampleCount pos, size_t len); + bool UpdateProgress(); void OnUpdateUI(wxCommandEvent & evt); void UpdateUI(); @@ -79,6 +90,8 @@ private: double mTrackLen; double mCurRate; + float mMult; + float mRatio; sampleCount mCount; wxTextCtrl *mLevelTextCtrl; From a94cda94aea9b0cf8350a199d736433a4ebfaedc Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 16 Mar 2019 10:14:15 +0100 Subject: [PATCH 08/18] Create empty EBUR128 class. This class will contain all EBU-R128 related analysis code. Add all new files to all build systems as well. --- mac/Audacity.xcodeproj/project.pbxproj | 6 +++++ src/CMakeLists.txt | 1 + src/Makefile.am | 2 ++ src/effects/EBUR128.cpp | 12 ++++++++++ src/effects/EBUR128.h | 24 +++++++++++++++++++ win/Projects/Audacity/Audacity.vcxproj | 2 ++ .../Audacity/Audacity.vcxproj.filters | 6 +++++ 7 files changed, 53 insertions(+) create mode 100644 src/effects/EBUR128.cpp create mode 100644 src/effects/EBUR128.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 33f72c38b..0a3b5aff4 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -276,6 +276,7 @@ 1790B13609883BFD008A330A /* ChangeTempo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01009883BFD008A330A /* ChangeTempo.cpp */; }; 1790B13709883BFD008A330A /* ClickRemoval.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01209883BFD008A330A /* ClickRemoval.cpp */; }; 1790B13809883BFD008A330A /* Compressor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01409883BFD008A330A /* Compressor.cpp */; }; + 1790B13819883BFD008A330A /* EBUR128.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01609883BFD008A330A /* EBUR128.cpp */; }; 1790B13909883BFD008A330A /* Echo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01709883BFD008A330A /* Echo.cpp */; }; 1790B13A09883BFD008A330A /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01909883BFD008A330A /* Effect.cpp */; }; 1790B13B09883BFD008A330A /* Equalization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1790B01B09883BFD008A330A /* Equalization.cpp */; }; @@ -1996,6 +1997,8 @@ 1790B01309883BFD008A330A /* ClickRemoval.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ClickRemoval.h; sourceTree = ""; tabWidth = 3; }; 1790B01409883BFD008A330A /* Compressor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Compressor.cpp; sourceTree = ""; tabWidth = 3; }; 1790B01509883BFD008A330A /* Compressor.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Compressor.h; sourceTree = ""; tabWidth = 3; }; + 1790B01609883BFD008A330A /* EBUR128.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = EBUR128.cpp; sourceTree = ""; tabWidth = 3; }; + 1790B01619883BFD008A330A /* EBUR128.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = EBUR128.h; sourceTree = ""; tabWidth = 3; }; 1790B01709883BFD008A330A /* Echo.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Echo.cpp; sourceTree = ""; tabWidth = 3; }; 1790B01809883BFD008A330A /* Echo.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Echo.h; sourceTree = ""; tabWidth = 3; }; 1790B01909883BFD008A330A /* Effect.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 30; indentWidth = 3; path = Effect.cpp; sourceTree = ""; tabWidth = 3; }; @@ -4716,6 +4719,8 @@ 1790B01309883BFD008A330A /* ClickRemoval.h */, 1790B01409883BFD008A330A /* Compressor.cpp */, 1790B01509883BFD008A330A /* Compressor.h */, + 1790B01609883BFD008A330A /* EBUR128.cpp */, + 1790B01619883BFD008A330A /* EBUR128.h */, 18D8314C0ED0F56200FD870D /* Contrast.cpp */, 18D8314D0ED0F56200FD870D /* Contrast.h */, 5E02BFF01D1164DF00EB7578 /* Distortion.cpp */, @@ -8438,6 +8443,7 @@ 5EF3E65A203FDACE006C6882 /* SetClipCommand.cpp in Sources */, 1790B13709883BFD008A330A /* ClickRemoval.cpp in Sources */, 1790B13809883BFD008A330A /* Compressor.cpp in Sources */, + 1790B13819883BFD008A330A /* EBUR128.cpp in Sources */, 1790B13909883BFD008A330A /* Echo.cpp in Sources */, 1790B13A09883BFD008A330A /* Effect.cpp in Sources */, 1790B13B09883BFD008A330A /* Equalization.cpp in Sources */, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c3ee164d..0ed847eb9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -240,6 +240,7 @@ set( EFFECTS_SOURCE ${CMAKE_SOURCE_DIRECTORY}effects/Contrast.cpp ${CMAKE_SOURCE_DIRECTORY}effects/Distortion.cpp ${CMAKE_SOURCE_DIRECTORY}effects/DtmfGen.cpp + ${CMAKE_SOURCE_DIRECTORY}effects/EBUR128.cpp ${CMAKE_SOURCE_DIRECTORY}effects/Echo.cpp ${CMAKE_SOURCE_DIRECTORY}effects/Effect.cpp ${CMAKE_SOURCE_DIRECTORY}effects/EffectManager.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 36a515cdf..1113e0e58 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -427,6 +427,8 @@ audacity_SOURCES = \ effects/Distortion.h \ effects/DtmfGen.cpp \ effects/DtmfGen.h \ + effects/EBUR128.cpp \ + effects/EBUR128.h \ effects/Echo.cpp \ effects/Echo.h \ effects/Effect.cpp \ diff --git a/src/effects/EBUR128.cpp b/src/effects/EBUR128.cpp new file mode 100644 index 000000000..107c5c5b9 --- /dev/null +++ b/src/effects/EBUR128.cpp @@ -0,0 +1,12 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EBUR128.cpp + +Max Maisel + +***********************************************************************/ + +#include "EBUR128.h" + diff --git a/src/effects/EBUR128.h b/src/effects/EBUR128.h new file mode 100644 index 000000000..037b6537a --- /dev/null +++ b/src/effects/EBUR128.h @@ -0,0 +1,24 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EBUR128.h + +Max Maisel + +***********************************************************************/ + +#ifndef __EBUR128_H__ +#define __EBUR128_H__ + +#include "MemoryX.h" + +/// \brief Implements EBU-R128 loudness measurement. +class EBUR128 +{ +public: + +private: +}; + +#endif diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 0f021cecf..92fa5c083 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -355,6 +355,7 @@ + @@ -756,6 +757,7 @@ + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 014750295..8ce4733dc 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -425,6 +425,9 @@ src\effects + + src\effects + src\effects @@ -1585,6 +1588,9 @@ src\effects + + src\effects + src\effects From 8555018bd4e543bb532f65cbdd40009dc22b770a Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Thu, 21 Mar 2019 19:41:39 +0100 Subject: [PATCH 09/18] Implement EBUR128 integrative loudness class. --- src/effects/Biquad.cpp | 49 ------------ src/effects/Biquad.h | 1 - src/effects/EBUR128.cpp | 168 ++++++++++++++++++++++++++++++++++++++++ src/effects/EBUR128.h | 32 ++++++++ 4 files changed, 200 insertions(+), 50 deletions(-) diff --git a/src/effects/Biquad.cpp b/src/effects/Biquad.cpp index 183a9be69..20c54e8f0 100644 --- a/src/effects/Biquad.cpp +++ b/src/effects/Biquad.cpp @@ -304,55 +304,6 @@ ArrayOf Biquad::CalcChebyshevType2Filter(int order, double fn, double fc return std::move(pBiquad); } -// fs: sample rate -// returns array of two Biquads -// -// EBU R128 parameter sampling rate adaption after -// Mansbridge, Stuart, Saoirse Finn, and Joshua D. Reiss. -// "Implementation and Evaluation of Autonomous Multi-track Fader Control." -// Paper presented at the 132nd Audio Engineering Society Convention, -// Budapest, Hungary, 2012." -ArrayOf Biquad::CalcEBUR128WeightingFilter(float fs) -{ - ArrayOf pBiquad(size_t(2), true); - - // - // HSF pre filter - // - 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; - - pBiquad[0].fNumerCoeffs[Biquad::B0] = (Vh + Vb * K / Q + K * K) / a0; - pBiquad[0].fNumerCoeffs[Biquad::B1] = 2.0 * (K * K - Vh) / a0; - pBiquad[0].fNumerCoeffs[Biquad::B2] = (Vh - Vb * K / Q + K * K) / a0; - - pBiquad[0].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / a0; - pBiquad[0].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / a0; - - // - // HPF weighting filter - // - f0 = 38.13547087602444; - Q = 0.5003270373238773; - K = tan(M_PI * f0 / fs); - - pBiquad[1].fNumerCoeffs[Biquad::B0] = 1.0; - pBiquad[1].fNumerCoeffs[Biquad::B1] = -2.0; - pBiquad[1].fNumerCoeffs[Biquad::B2] = 1.0; - - pBiquad[1].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); - pBiquad[1].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); - - return std::move(pBiquad); -} - void Biquad::ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, double* pfQuotientR, double* pfQuotientI) { diff --git a/src/effects/Biquad.h b/src/effects/Biquad.h index 29bb8c746..dcb667e2b 100644 --- a/src/effects/Biquad.h +++ b/src/effects/Biquad.h @@ -66,7 +66,6 @@ struct Biquad static ArrayOf CalcButterworthFilter(int order, double fn, double fc, int type); static ArrayOf CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type); static ArrayOf CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type); - static ArrayOf CalcEBUR128WeightingFilter(float fs); static void ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI, double* pfQuotientR, double* pfQuotientI); diff --git a/src/effects/EBUR128.cpp b/src/effects/EBUR128.cpp index 107c5c5b9..0cf00bfff 100644 --- a/src/effects/EBUR128.cpp +++ b/src/effects/EBUR128.cpp @@ -10,3 +10,171 @@ Max Maisel #include "EBUR128.h" +EBUR128::EBUR128(double rate, size_t channels) + : mChannelCount(channels) + , mRate(rate) +{ + mBlockSize = ceil(0.4 * mRate); // 400 ms blocks + mBlockOverlap = ceil(0.1 * mRate); // 100 ms overlap + mLoudnessHist.reinit(HIST_BIN_COUNT, false); + mBlockRingBuffer.reinit(mBlockSize); + mWeightingFilter.reinit(mChannelCount, false); + for(size_t channel = 0; channel < mChannelCount; ++channel) + mWeightingFilter[channel] = CalcWeightingFilter(mRate); +} + +void EBUR128::Initialize() +{ + mSampleCount = 0; + mBlockRingPos = 0; + mBlockRingSize = 0; + memset(mLoudnessHist.get(), 0, HIST_BIN_COUNT*sizeof(long int)); + for(size_t channel = 0; channel < mChannelCount; ++channel) + { + mWeightingFilter[channel][0].Reset(); + mWeightingFilter[channel][1].Reset(); + } +} + +// fs: sample rate +// returns array of two Biquads +// +// EBU R128 parameter sampling rate adaption after +// Mansbridge, Stuart, Saoirse Finn, and Joshua D. Reiss. +// "Implementation and Evaluation of Autonomous Multi-track Fader Control." +// Paper presented at the 132nd Audio Engineering Society Convention, +// Budapest, Hungary, 2012." +ArrayOf EBUR128::CalcWeightingFilter(double fs) +{ + ArrayOf pBiquad(size_t(2), true); + + // + // HSF pre filter + // + 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; + + pBiquad[0].fNumerCoeffs[Biquad::B0] = (Vh + Vb * K / Q + K * K) / a0; + pBiquad[0].fNumerCoeffs[Biquad::B1] = 2.0 * (K * K - Vh) / a0; + pBiquad[0].fNumerCoeffs[Biquad::B2] = (Vh - Vb * K / Q + K * K) / a0; + + pBiquad[0].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / a0; + pBiquad[0].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / a0; + + // + // HPF weighting filter + // + f0 = 38.13547087602444; + Q = 0.5003270373238773; + K = tan(M_PI * f0 / fs); + + pBiquad[1].fNumerCoeffs[Biquad::B0] = 1.0; + pBiquad[1].fNumerCoeffs[Biquad::B1] = -2.0; + pBiquad[1].fNumerCoeffs[Biquad::B2] = 1.0; + + pBiquad[1].fDenomCoeffs[Biquad::A1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); + pBiquad[1].fDenomCoeffs[Biquad::A2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); + + return std::move(pBiquad); +} + +void EBUR128::ProcessSampleFromChannel(float x_in, size_t channel) +{ + double value; + value = mWeightingFilter[channel][0].ProcessOne(x_in); + value = mWeightingFilter[channel][1].ProcessOne(value); + if(channel == 0) + mBlockRingBuffer[mBlockRingPos] = value * value; + else + { + // Add the power of additional channels to the power of first channel. + // As a result, stereo tracks appear about 3 LUFS louder, as specified. + mBlockRingBuffer[mBlockRingPos] += value * value; + } +} + +void EBUR128::NextSample() +{ + ++mBlockRingPos; + ++mBlockRingSize; + + if(mBlockRingPos % mBlockOverlap == 0) + { + // Process new full block. As incomplete blocks shall be discarded + // according to the EBU R128 specification there is no need for + // some special logic for the last blocks. + if(mBlockRingSize >= mBlockSize) + { + // Reset mBlockRingSize to full state to avoid overflow. + // The actual value of mBlockRingSize does not matter + // since this is only used to detect if blocks are complete (>= mBlockSize). + mBlockRingSize = mBlockSize; + + size_t idx; + double blockVal = 0; + for(size_t i = 0; i < mBlockSize; ++i) + blockVal += mBlockRingBuffer[i]; + + // Histogram values are simplified log10() immediate values + // without -0.691 + 10*(...) to safe computing power. This is + // possible because these constant cancel out anyway during the + // following processing steps. + blockVal = log10(blockVal/double(mBlockSize)); + // log(blockVal) is within ]-inf, 1] + idx = round((blockVal - GAMMA_A) * double(HIST_BIN_COUNT) / -GAMMA_A - 1); + + // idx is within ]-inf, HIST_BIN_COUNT-1], discard indices below 0 + // as they are below the EBU R128 absolute threshold anyway. + if(idx >= 0 && idx < HIST_BIN_COUNT) + ++mLoudnessHist[idx]; + } + } + // Close the ring. + if(mBlockRingPos == mBlockSize) + mBlockRingPos = 0; + ++mSampleCount; +} + +double EBUR128::IntegrativeLoudness() +{ + // EBU R128: z_i = mean square without root + + // Calculate Gamma_R from histogram. + double sum_v = 0; + double val; + long int sum_c = 0; + for(size_t i = 0; i < HIST_BIN_COUNT; ++i) + { + val = -GAMMA_A / double(HIST_BIN_COUNT) * (i+1) + GAMMA_A; + sum_v += pow(10, val) * mLoudnessHist[i]; + sum_c += mLoudnessHist[i]; + } + + // Histogram values are simplified log(x^2) immediate values + // without -0.691 + 10*(...) to safe computing power. This is + // possible because they will cancel out anyway. + // The -1 in the line below is the -10 LUFS from the EBU R128 + // specification without the scaling factor of 10. + double Gamma_R = log10(sum_v/sum_c) - 1; + size_t idx_R = round((Gamma_R - GAMMA_A) * double(HIST_BIN_COUNT) / -GAMMA_A - 1); + + // Apply Gamma_R threshold and calculate gated loudness (extent). + sum_v = 0; + sum_c = 0; + for(size_t i = idx_R+1; i < HIST_BIN_COUNT; ++i) + { + val = -GAMMA_A / double(HIST_BIN_COUNT) * (i+1) + GAMMA_A; + sum_v += pow(10, val) * mLoudnessHist[i]; + sum_c += mLoudnessHist[i]; + } + // LUFS is defined as -0.691 dB + 10*log10(sum(channels)) + return 0.8529037031 * sum_v / sum_c; +} + diff --git a/src/effects/EBUR128.h b/src/effects/EBUR128.h index 037b6537a..883a61d94 100644 --- a/src/effects/EBUR128.h +++ b/src/effects/EBUR128.h @@ -11,14 +11,46 @@ Max Maisel #ifndef __EBUR128_H__ #define __EBUR128_H__ +#include "Biquad.h" #include "MemoryX.h" +#include "SampleFormat.h" /// \brief Implements EBU-R128 loudness measurement. class EBUR128 { public: + EBUR128(double rate, size_t channels); + EBUR128(const EBUR128&) = delete; + EBUR128(EBUR128&&) = delete; + ~EBUR128() = default; + + static ArrayOf CalcWeightingFilter(double fs); + void Initialize(); + void ProcessSampleFromChannel(float x_in, size_t channel); + void NextSample(); + double IntegrativeLoudness(); + inline double IntegrativeLoudnessToLUFS(double loudness) + { return 10 * log10(loudness); } private: + static const size_t HIST_BIN_COUNT = 65536; + /// EBU R128 absolute threshold + static constexpr double GAMMA_A = (-70.0 + 0.691) / 10.0; + ArrayOf mLoudnessHist; + Doubles mBlockRingBuffer; + size_t mSampleCount; + size_t mBlockRingPos; + size_t mBlockRingSize; + size_t mBlockSize; + size_t mBlockOverlap; + size_t mChannelCount; + double mRate; + + /// This is be an array of arrays of the type + /// mWeightingFilter[CHANNEL][FILTER] with + /// CHANNEL = LEFT/RIGHT (0/1) and + /// FILTER = HSF/HPF (0/1) + ArrayOf> mWeightingFilter; }; #endif From d9608cddea49cb33815fd9475f2b4f822913b772 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Thu, 21 Mar 2019 19:42:52 +0100 Subject: [PATCH 10/18] Implement perceived loudness normalization in new Loudness effect. --- src/effects/Loudness.cpp | 42 +++++++++++++++++++++------------------- src/effects/Loudness.h | 4 ++-- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp index c1f9f007d..7ae368f34 100644 --- a/src/effects/Loudness.cpp +++ b/src/effects/Loudness.cpp @@ -147,7 +147,7 @@ bool EffectLoudness::Startup() // Load the old "current" settings if (gPrefs->Exists(base)) { - mStereoInd = true; + mStereoInd = false; mDualMono = DEF_DualMono; mNormalizeTo = kLoudness; mLUFSLevel = DEF_LUFSLevel; @@ -160,7 +160,6 @@ bool EffectLoudness::Startup() return true; } -// TODO: more method extraction bool EffectLoudness::Process() { if(mNormalizeTo == kLoudness) @@ -205,7 +204,8 @@ bool EffectLoudness::Process() mProcStereo = range.size() > 1; - InitTrackAnalysis(); + mLoudnessProcessor.reset(new EBUR128(mCurRate, range.size())); + mLoudnessProcessor->Initialize(); if(!ProcessOne(range, true)) { // Processing failed -> abort @@ -214,10 +214,17 @@ bool EffectLoudness::Process() } // Calculate normalization values the analysis results - float extent = 1.0; - // TODO: add it in separate method + float extent = mLoudnessProcessor->IntegrativeLoudness(); mMult = mRatio / extent; + // Target half the LUFS value if mono (or independent processed stereo) + // shall be treated as dual mono. + if(range.size() == 1 && (mDualMono || track->GetChannel() != Track::MonoChannel)) + mMult /= 2.0; + + // LUFS are related to square values so the multiplier must be the root. + mMult = sqrt(mMult); + mProgressMsg = topMsg + wxString::Format(_("Processing: %s"), trackName); if(!ProcessOne(range, false)) { @@ -228,6 +235,7 @@ bool EffectLoudness::Process() } this->ReplaceProcessedTracks(bGoodResult); + mLoudnessProcessor.reset(); FreeBuffers(); return bGoodResult; } @@ -325,9 +333,7 @@ void EffectLoudness::AllocBuffers() stereoTrackFound = true; } - // TODO: hist and block buffers go here - - // Initiate a processing buffer. This buffer will (most likely) + // Initiate a processing buffer. This buffer will (most likely) // be shorter than the length of the track being processed. mTrackBuffer[0].reinit(mTrackBufferCapacity); @@ -339,13 +345,6 @@ void EffectLoudness::FreeBuffers() { mTrackBuffer[0].reset(); mTrackBuffer[1].reset(); - // TODO: additional destroy -> function -} - -void EffectLoudness::InitTrackAnalysis() -{ - mCount = 0; - // TODO: additional init goes here } /// ProcessOne() takes a track, transforms it to bunch of buffer-blocks, @@ -399,8 +398,6 @@ bool EffectLoudness::ProcessOne(TrackIterRange range, bool analyse) return false; } - sleep(1); - if(!analyse) StoreBufferBlock(range, s, blockLen); @@ -438,8 +435,13 @@ bool EffectLoudness::LoadBufferBlock(TrackIterRange range, /// (for loudness). bool EffectLoudness::AnalyseBufferBlock() { - // TODO: analysis loop goes here - mCount += mTrackBufferLen; + for(size_t i = 0; i < mTrackBufferLen; i++) + { + mLoudnessProcessor->ProcessSampleFromChannel(mTrackBuffer[0][i], 0); + if(mProcStereo) + mLoudnessProcessor->ProcessSampleFromChannel(mTrackBuffer[1][i], 1); + mLoudnessProcessor->NextSample(); + } if(!UpdateProgress()) return false; @@ -496,7 +498,7 @@ void EffectLoudness::UpdateUI() mWarning->SetLabel(wxT("")); EnableApply(true); - // Changing the prompts causes an unwanted UpdateUI event. + // Changing the prompts causes an unwanted UpdateUI event. // This 'guard' stops that becoming an infinite recursion. if (mNormalizeTo != mGUINormalizeTo) { diff --git a/src/effects/Loudness.h b/src/effects/Loudness.h index 441c0928b..f334b662a 100644 --- a/src/effects/Loudness.h +++ b/src/effects/Loudness.h @@ -20,6 +20,7 @@ #include "Effect.h" #include "Biquad.h" +#include "EBUR128.h" class ShuttleGui; @@ -61,7 +62,6 @@ private: void AllocBuffers(); void FreeBuffers(); - void InitTrackAnalysis(); bool ProcessOne(TrackIterRange range, bool analyse); bool LoadBufferBlock(TrackIterRange range, sampleCount pos, size_t len); @@ -92,7 +92,7 @@ private: float mMult; float mRatio; - sampleCount mCount; + std::unique_ptr mLoudnessProcessor; wxTextCtrl *mLevelTextCtrl; wxStaticText *mLeveldB; From 1d60a3882fe6c5513755a7dbb74255bf1f7737ad Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 23 Mar 2019 11:24:08 +0100 Subject: [PATCH 11/18] Add colors to Octave unit test results. --- tests/octave/run_test.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/octave/run_test.m b/tests/octave/run_test.m index 17b909984..20b203cec 100755 --- a/tests/octave/run_test.m +++ b/tests/octave/run_test.m @@ -128,6 +128,11 @@ function result = do_test(result, msg, skip = false) global CURRENT_TEST; TESTS_RUN = TESTS_RUN + 1; + ANSI_COLOR_RED = "\x1b[31m"; + ANSI_COLOR_GREEN = "\x1b[32m"; + ANSI_COLOR_YELLOW = "\x1b[33m"; + ANSI_COLOR_RESET = "\x1b[0m"; + suffix = ""; if !strcmp(msg, "") suffix = cstrcat(" - ", msg); @@ -135,15 +140,21 @@ function result = do_test(result, msg, skip = false) if skip TESTS_SKIPPED = TESTS_SKIPPED + 1; + printf(ANSI_COLOR_YELLOW); printf(cstrcat("[Skip]: ", CURRENT_TEST, suffix, "\n")); + printf(ANSI_COLOR_RESET); result = 1; else if result + printf(ANSI_COLOR_GREEN); printf(cstrcat("[Success]: ", CURRENT_TEST, suffix, "\n")); + printf(ANSI_COLOR_RESET); return; else TESTS_FAILED = TESTS_FAILED + 1; + printf(ANSI_COLOR_RED); printf(cstrcat("[Failed]: ", CURRENT_TEST, suffix, "\n")); + printf(ANSI_COLOR_RESET); return; end end From 2649179ef3b6cdf88a924a1163c25f275dbf25b7 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Fri, 22 Mar 2019 17:58:01 +0100 Subject: [PATCH 12/18] Add Loudness effect perceived loudness unit tests. --- tests/octave/loudness_test.m | 112 ++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/tests/octave/loudness_test.m b/tests/octave/loudness_test.m index ef67bdca0..4ab3e1580 100644 --- a/tests/octave/loudness_test.m +++ b/tests/octave/loudness_test.m @@ -122,5 +122,115 @@ if TEST_LUFS_HELPER printf("LUFS-selftest3.wav should be %f LUFS\n", calc_LUFS(x, fs)); end -# TODO: add tests here +## Test Loudness LUFS mode: stereo dependent +CURRENT_TEST = "Loudness LUFS mode, keep DC and stereo balance"; +randn("seed", 1); +fs= 44100; +# Include some silecne in the test signal to test loudness gating +# and vary the overall loudness over time. +x = [0.1*randn(15*fs, 2).', zeros(5*fs, 2).', 0.1*randn(15*fs, 2).'].'; +x(:,1) = x(:,1) .* sin(2*pi/fs/35*(1:1:35*fs)).' .* 1.2; +x(:,2) = x(:,2) .* sin(2*pi/fs/35*(1:1:35*fs)).'; +audiowrite(TMP_FILENAME, x, fs); +if EXPORT_TEST_SIGNALS + audiowrite(cstrcat(pwd(), "/Loudness-LUFS-stereo-test.wav"), x, fs); +end + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: LUFSLevel=-23 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +do_test_equ(calc_LUFS(y, fs), -23, "loudness", LUFS_epsilon); +do_test_neq(calc_LUFS(y(:,1), fs), calc_LUFS(y(:,2), fs), "stereo balance", 1); + +## Test Loudness LUFS mode, stereo independent +CURRENT_TEST = "Loudness LUFS mode, stereo independence"; +audiowrite(TMP_FILENAME, x, fs); +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: LUFSLevel=-23 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +# Independently processed stereo channels have half the target loudness. +do_test_equ(calc_LUFS(y(:,1), fs), -26, "channel 1 loudness", LUFS_epsilon); +do_test_equ(calc_LUFS(y(:,2), fs), -26, "channel 2 loudness", LUFS_epsilon); + +## Test Loudness LUFS mode: mono as mono +CURRENT_TEST = "Test Loudness LUFS mode: mono as mono"; +x = x(:,1); +audiowrite(TMP_FILENAME, x, fs); +if EXPORT_TEST_SIGNALS + audiowrite(cstrcat(pwd(), "/Loudness-LUFS-mono-test.wav"), x, fs); +end + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: LUFSLevel=-26 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +do_test_equ(calc_LUFS(y, fs), -26, "loudness", LUFS_epsilon); + +## Test Loudness LUFS mode: mono as dual-mono +CURRENT_TEST = "Test Loudness LUFS mode: mono as dual-mono"; +audiowrite(TMP_FILENAME, x, fs); + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: LUFSLevel=-26 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +# This shall be 3 LU quieter as it is compared to strict spec. +do_test_equ(calc_LUFS(y, fs), -29, "loudness", LUFS_epsilon); + +## Test Loudness LUFS mode: multi-rate project +CURRENT_TEST = "Test Loudness LUFS mode: multi-rate project"; +audiowrite(TMP_FILENAME, x, fs); + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); + +randn("seed", 2); +fs1= 8000; +x1 = [0.2*randn(2, 10*fs1) zeros(2, 10*fs1) 0.1*randn(2, 10*fs1)].'; +x1(:,1) = x1(:,1) * 0.6; +audiowrite(TMP_FILENAME, x1, fs1); +if EXPORT_TEST_SIGNALS + audiowrite(cstrcat(pwd(), "/Loudness-LUFS-stereo-test-8kHz.wav"), x1, fs1); +end + +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("Loudness: LUFSLevel=-30 DualMono=0 NormalizeTo=0 StereoIndependent=0\n"); + +aud_do("SelectTracks: Track=0 TrackCount=1 Mode=Set\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); +system("sync"); +y = audioread(TMP_FILENAME); + +aud_do("SelectTracks: Track=1 TrackCount=1 Mode=Set\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); +system("sync"); +y1 = audioread(TMP_FILENAME); + +do_test_equ(calc_LUFS(y, fs), -30, "loudness track 1", LUFS_epsilon); +# XXX: Audacity does not export at 8kHz through scripting thus this test is expected to fail! +# To ensure that this works you have to set the project rate to 8 kHz, +# export the track and check the results manually. +do_test_equ(calc_LUFS(y1, fs1), -30, "loudness track 2", LUFS_epsilon, true); +# No stereo balance check for track 1 - it's a mono track. +do_test_neq(calc_LUFS(y1(:,1), fs), calc_LUFS(y1(:,2), fs), "stereo balance track 2", LUFS_epsilon); From ed1e7ee37af15bae09349cadb5325747b8822fc5 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 23 Mar 2019 12:00:42 +0100 Subject: [PATCH 13/18] Implement RMS normalization in new Loudness effect. --- src/effects/Loudness.cpp | 77 +++++++++++++++++++++++++++++++++------- src/effects/Loudness.h | 2 ++ 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp index 7ae368f34..1038fe911 100644 --- a/src/effects/Loudness.cpp +++ b/src/effects/Loudness.cpp @@ -24,6 +24,7 @@ #include "../Internat.h" #include "../Prefs.h" +#include "../ProjectFileManager.h" #include "../Shuttle.h" #include "../ShuttleGui.h" #include "../WaveTrack.h" @@ -166,6 +167,8 @@ bool EffectLoudness::Process() // LU use 10*log10(...) instead of 20*log10(...) // so multiply level by 2 and use standard DB_TO_LINEAR macro. mRatio = DB_TO_LINEAR(TrapDouble(mLUFSLevel*2, MIN_LUFSLevel, MAX_LUFSLevel)); + else // RMS + mRatio = DB_TO_LINEAR(TrapDouble(mRMSLevel, MIN_RMSLevel, MAX_RMSLevel)); // Iterate over each track this->CopyInputTracks(); // Set up mOutputTracks. @@ -204,26 +207,55 @@ bool EffectLoudness::Process() mProcStereo = range.size() > 1; - mLoudnessProcessor.reset(new EBUR128(mCurRate, range.size())); - mLoudnessProcessor->Initialize(); - if(!ProcessOne(range, true)) + if(mNormalizeTo == kLoudness) { - // Processing failed -> abort - bGoodResult = false; - break; + mLoudnessProcessor.reset(new EBUR128(mCurRate, range.size())); + mLoudnessProcessor->Initialize(); + if(!ProcessOne(range, true)) + { + // Processing failed -> abort + bGoodResult = false; + break; + } + } + else // RMS + { + size_t idx = 0; + for(auto channel : range) + { + if(!GetTrackRMS(channel, mRMS[idx])) + { + bGoodResult = false; + return false; + } + ++idx; + } + mSteps = 1; } // Calculate normalization values the analysis results - float extent = mLoudnessProcessor->IntegrativeLoudness(); + float extent; + if(mNormalizeTo == kLoudness) + extent = mLoudnessProcessor->IntegrativeLoudness(); + else // RMS + { + extent = mRMS[0]; + if(mProcStereo) + // RMS: use average RMS, average must be calculated in quadratic domain. + extent = sqrt((mRMS[0] * mRMS[0] + mRMS[1] * mRMS[1]) / 2.0); + } mMult = mRatio / extent; - // Target half the LUFS value if mono (or independent processed stereo) - // shall be treated as dual mono. - if(range.size() == 1 && (mDualMono || track->GetChannel() != Track::MonoChannel)) - mMult /= 2.0; + if(mNormalizeTo == kLoudness) + { + // Target half the LUFS value if mono (or independent processed stereo) + // shall be treated as dual mono. + if(range.size() == 1 && (mDualMono || track->GetChannel() != Track::MonoChannel)) + mMult /= 2.0; - // LUFS are related to square values so the multiplier must be the root. - mMult = sqrt(mMult); + // LUFS are related to square values so the multiplier must be the root. + mMult = sqrt(mMult); + } mProgressMsg = topMsg + wxString::Format(_("Processing: %s"), trackName); if(!ProcessOne(range, false)) @@ -347,6 +379,25 @@ void EffectLoudness::FreeBuffers() mTrackBuffer[1].reset(); } +bool EffectLoudness::GetTrackRMS(WaveTrack* track, float& rms) +{ + // 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 (ProjectFileManager::GetODFlags(*track)) { + // update the gui + if (ProgressResult::Cancelled == mProgress->Update( + 0, _("Waiting for waveform to finish computing...")) ) + return false; + wxMilliSleep(100); + } + + // set mRMS. No progress bar here as it's fast. + float _rms = track->GetRMS(mCurT0, mCurT1); // may throw + rms = _rms; + return true; +} + /// ProcessOne() takes a track, transforms it to bunch of buffer-blocks, /// and executes ProcessData, on it... /// uses mMult to normalize a track. diff --git a/src/effects/Loudness.h b/src/effects/Loudness.h index f334b662a..7a4be25fc 100644 --- a/src/effects/Loudness.h +++ b/src/effects/Loudness.h @@ -62,6 +62,7 @@ private: void AllocBuffers(); void FreeBuffers(); + bool GetTrackRMS(WaveTrack* track, float& rms); bool ProcessOne(TrackIterRange range, bool analyse); bool LoadBufferBlock(TrackIterRange range, sampleCount pos, size_t len); @@ -92,6 +93,7 @@ private: float mMult; float mRatio; + float mRMS[2]; std::unique_ptr mLoudnessProcessor; wxTextCtrl *mLevelTextCtrl; From 0bac2a5e75af934b6e35111e106d2d0afb1b59a2 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 23 Mar 2019 12:10:49 +0100 Subject: [PATCH 14/18] Add Loudness effect RMS unit tests. --- tests/octave/loudness_test.m | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/octave/loudness_test.m b/tests/octave/loudness_test.m index 4ab3e1580..e692bb3ad 100644 --- a/tests/octave/loudness_test.m +++ b/tests/octave/loudness_test.m @@ -234,3 +234,41 @@ do_test_equ(calc_LUFS(y1, fs1), -30, "loudness track 2", LUFS_epsilon, true); # No stereo balance check for track 1 - it's a mono track. do_test_neq(calc_LUFS(y1(:,1), fs), calc_LUFS(y1(:,2), fs), "stereo balance track 2", LUFS_epsilon); +## Test Loudness RMS mode: stereo independent +CURRENT_TEST = "Loudness RMS mode, stereo independent"; +randn("seed", 1); +fs= 44100; +x = 0.1*randn(30*fs, 2); +x(:,1) = x(:,1) * 0.6; +audiowrite(TMP_FILENAME, x, fs); +if EXPORT_TEST_SIGNALS + audiowrite(cstrcat(pwd(), "/Loudness-RMS-test.wav"), x, fs); +end + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: RMSLevel=-20 DualMono=0 NormalizeTo=1 StereoIndependent=1\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +do_test_equ(20*log10(sqrt(sum(y(:,1).*y(:,1)/length(y)))), -20, "channel 1 RMS"); +do_test_equ(20*log10(sqrt(sum(y(:,2).*y(:,2)/length(y)))), -20, "channel 2 RMS"); + +## Test Loudness RMS mode: stereo dependent +CURRENT_TEST = "Loudness RMS mode, stereo dependent"; +audiowrite(TMP_FILENAME, x, fs); + +aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); +aud_do("RemoveTracks:\n"); +aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); +aud_do("Loudness: RMSLevel=-22 DualMono=1 NormalizeTo=1 StereoIndependent=0\n"); +aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); +system("sync"); + +y = audioread(TMP_FILENAME); +# Stereo RMS must be calculated in quadratic domain. +do_test_equ(20*log10(sqrt(sum(rms(y).^2)/size(y)(2))), -22, "RMS"); +do_test_neq(20*log10(rms(y(:,1))), 20*log10(rms(y(:,2))), "stereo balance", 1); + From b4821228df79027133d92546ddc8c8c16e56ac56 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sat, 23 Mar 2019 15:27:28 +0100 Subject: [PATCH 15/18] Rename Loudness effect to "Loudness Normalization". --- src/effects/Loudness.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/effects/Loudness.h b/src/effects/Loudness.h index 7a4be25fc..291cde0a0 100644 --- a/src/effects/Loudness.h +++ b/src/effects/Loudness.h @@ -24,7 +24,7 @@ class ShuttleGui; -#define LOUDNESS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Loudness") } +#define LOUDNESS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Loudness Normalization") } class EffectLoudness final : public Effect { From f911bdb46c8109aafd0dda602d84216096cebe80 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Tue, 9 Apr 2019 19:20:01 +0200 Subject: [PATCH 16/18] Include ProgressDialog.h in Loudness effect. --- src/effects/Loudness.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/effects/Loudness.cpp b/src/effects/Loudness.cpp index 1038fe911..9ccf64e83 100644 --- a/src/effects/Loudness.cpp +++ b/src/effects/Loudness.cpp @@ -29,6 +29,7 @@ #include "../ShuttleGui.h" #include "../WaveTrack.h" #include "../widgets/valnum.h" +#include "../widgets/ProgressDialog.h" enum kNormalizeTargets { From 5f0c02273e02584e5e9faaa3f871f89b2b7263d2 Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Thu, 11 Apr 2019 17:25:38 +0200 Subject: [PATCH 17/18] Fix Loudness effect unit tests Introduce new helper functions which handle selection and update scripted selection to new upstream. --- tests/octave/loudness_test.m | 47 ++++++++++++++++++------------------ tests/octave/run_test.m | 12 +++++++++ 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/tests/octave/loudness_test.m b/tests/octave/loudness_test.m index e692bb3ad..4ab66f204 100644 --- a/tests/octave/loudness_test.m +++ b/tests/octave/loudness_test.m @@ -136,10 +136,10 @@ if EXPORT_TEST_SIGNALS audiowrite(cstrcat(pwd(), "/Loudness-LUFS-stereo-test.wav"), x, fs); end -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: LUFSLevel=-23 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: LUFSLevel=-23 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); system("sync"); @@ -150,10 +150,10 @@ do_test_neq(calc_LUFS(y(:,1), fs), calc_LUFS(y(:,2), fs), "stereo balance", 1); ## Test Loudness LUFS mode, stereo independent CURRENT_TEST = "Loudness LUFS mode, stereo independence"; audiowrite(TMP_FILENAME, x, fs); -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: LUFSLevel=-23 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: LUFSLevel=-23 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); system("sync"); @@ -170,10 +170,10 @@ if EXPORT_TEST_SIGNALS audiowrite(cstrcat(pwd(), "/Loudness-LUFS-mono-test.wav"), x, fs); end -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: LUFSLevel=-26 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: LUFSLevel=-26 DualMono=0 NormalizeTo=0 StereoIndependent=1\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); system("sync"); @@ -184,10 +184,10 @@ do_test_equ(calc_LUFS(y, fs), -26, "loudness", LUFS_epsilon); CURRENT_TEST = "Test Loudness LUFS mode: mono as dual-mono"; audiowrite(TMP_FILENAME, x, fs); -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: LUFSLevel=-26 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: LUFSLevel=-26 DualMono=1 NormalizeTo=0 StereoIndependent=0\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); system("sync"); @@ -199,8 +199,7 @@ do_test_equ(calc_LUFS(y, fs), -29, "loudness", LUFS_epsilon); CURRENT_TEST = "Test Loudness LUFS mode: multi-rate project"; audiowrite(TMP_FILENAME, x, fs); -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); randn("seed", 2); @@ -213,15 +212,15 @@ if EXPORT_TEST_SIGNALS end aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("Loudness: LUFSLevel=-30 DualMono=0 NormalizeTo=0 StereoIndependent=0\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: LUFSLevel=-30 DualMono=0 NormalizeTo=0 StereoIndependent=0\n"); -aud_do("SelectTracks: Track=0 TrackCount=1 Mode=Set\n"); +select_tracks(0, 1); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n")); system("sync"); y = audioread(TMP_FILENAME); -aud_do("SelectTracks: Track=1 TrackCount=1 Mode=Set\n"); +select_tracks(1, 1); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); system("sync"); y1 = audioread(TMP_FILENAME); @@ -245,10 +244,10 @@ if EXPORT_TEST_SIGNALS audiowrite(cstrcat(pwd(), "/Loudness-RMS-test.wav"), x, fs); end -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: RMSLevel=-20 DualMono=0 NormalizeTo=1 StereoIndependent=1\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: RMSLevel=-20 DualMono=0 NormalizeTo=1 StereoIndependent=1\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); system("sync"); @@ -260,10 +259,10 @@ do_test_equ(20*log10(sqrt(sum(y(:,2).*y(:,2)/length(y)))), -20, "channel 2 RMS") CURRENT_TEST = "Loudness RMS mode, stereo dependent"; audiowrite(TMP_FILENAME, x, fs); -aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); -aud_do("RemoveTracks:\n"); +remove_all_tracks(); aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\n")); -aud_do("Loudness: RMSLevel=-22 DualMono=1 NormalizeTo=1 StereoIndependent=0\n"); +select_tracks(0, 100); +aud_do("LoudnessNormalization: RMSLevel=-22 DualMono=1 NormalizeTo=1 StereoIndependent=0\n"); aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=2\n")); system("sync"); diff --git a/tests/octave/run_test.m b/tests/octave/run_test.m index 20b203cec..d14810334 100755 --- a/tests/octave/run_test.m +++ b/tests/octave/run_test.m @@ -62,6 +62,18 @@ function aud_do(command) until strncmp(string, "BatchCommand finished:", length("BatchCommand finished:")); end +## Selection helper functions +function remove_all_tracks() + aud_do("SelectTracks: Track=0 TrackCount=100 Mode=Set\n"); + aud_do("RemoveTracks:\n"); +end + +function select_tracks(num, count) + aud_do("Select: Start=0 Mode=Set\n"); + aud_do("SelCursorToTrackEnd:\n"); + aud_do(sprintf("SelectTracks: Track=%d TrackCount=%d Mode=Set\n", num, count)); +end + ## Float equal comparison helper function [ret] = float_eq(x, y, eps=0.001) ret = abs(x - y) < eps; From c97573abb5bd0cf9663b0375980f35ccbbeea8b6 Mon Sep 17 00:00:00 2001 From: SteveDaulton Date: Wed, 27 Nov 2019 21:00:51 +0000 Subject: [PATCH 18/18] update makefiles for Loudness effect on Linux --- Makefile.in | 12 +++---- help/Makefile.in | 4 +-- images/Makefile.in | 4 +-- lib-src/Makefile.in | 4 +-- src/Makefile.in | 87 ++++++++++++++++++++++++++++++++------------- tests/Makefile.in | 4 +-- 6 files changed, 77 insertions(+), 38 deletions(-) diff --git a/Makefile.in b/Makefile.in index 92ae33483..aff25b069 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -972,7 +972,7 @@ distdir: $(DISTFILES) ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r "$(distdir)" dist-gzip: distdir - tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz $(am__post_remove_distdir) dist-bzip2: distdir @@ -997,7 +997,7 @@ dist-shar: distdir @echo WARNING: "Support for shar distribution archives is" \ "deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 - shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz + shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz $(am__post_remove_distdir) dist-zip: distdir @@ -1015,7 +1015,7 @@ dist dist-all: distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.lz*) \ @@ -1025,7 +1025,7 @@ distcheck: dist *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ esac diff --git a/help/Makefile.in b/help/Makefile.in index 70f6ba300..d0eb6aee4 100644 --- a/help/Makefile.in +++ b/help/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/images/Makefile.in b/images/Makefile.in index 023f3df93..c395d6b42 100644 --- a/images/Makefile.in +++ b/images/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in index 2fbcf653c..8d29ec9e0 100644 --- a/lib-src/Makefile.in +++ b/lib-src/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/src/Makefile.in b/src/Makefile.in index b7cacf185..12d91c160 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -399,17 +399,18 @@ am__audacity_SOURCES_DIST = BlockFile.cpp BlockFile.h DirManager.cpp \ effects/ClickRemoval.h effects/Compressor.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ effects/Distortion.cpp effects/Distortion.h \ - effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ - effects/Echo.h effects/Effect.cpp effects/Effect.h \ - effects/EffectManager.cpp effects/EffectManager.h \ - effects/EffectRack.cpp effects/EffectRack.h \ - effects/Equalization.cpp effects/Equalization.h \ - effects/Equalization48x.cpp effects/Equalization48x.h \ - effects/Fade.cpp effects/Fade.h effects/FindClipping.cpp \ - effects/FindClipping.h effects/Generator.cpp \ - effects/Generator.h effects/Invert.cpp effects/Invert.h \ - effects/LoadEffects.cpp effects/LoadEffects.h \ - effects/Noise.cpp effects/Noise.h effects/NoiseReduction.cpp \ + effects/DtmfGen.cpp effects/DtmfGen.h effects/EBUR128.cpp \ + effects/EBUR128.h effects/Echo.cpp effects/Echo.h \ + effects/Effect.cpp effects/Effect.h effects/EffectManager.cpp \ + effects/EffectManager.h effects/EffectRack.cpp \ + effects/EffectRack.h effects/Equalization.cpp \ + effects/Equalization.h effects/Equalization48x.cpp \ + effects/Equalization48x.h effects/Fade.cpp effects/Fade.h \ + effects/FindClipping.cpp effects/FindClipping.h \ + effects/Generator.cpp effects/Generator.h effects/Invert.cpp \ + effects/Invert.h effects/LoadEffects.cpp effects/LoadEffects.h \ + effects/Loudness.cpp effects/Loudness.h effects/Noise.cpp \ + effects/Noise.h effects/NoiseReduction.cpp \ effects/NoiseReduction.h effects/NoiseRemoval.cpp \ effects/NoiseRemoval.h effects/Normalize.cpp \ effects/Normalize.h effects/Paulstretch.cpp \ @@ -778,6 +779,7 @@ am_audacity_OBJECTS = $(am__objects_1) audacity-AboutDialog.$(OBJEXT) \ effects/audacity-Contrast.$(OBJEXT) \ effects/audacity-Distortion.$(OBJEXT) \ effects/audacity-DtmfGen.$(OBJEXT) \ + effects/audacity-EBUR128.$(OBJEXT) \ effects/audacity-Echo.$(OBJEXT) \ effects/audacity-Effect.$(OBJEXT) \ effects/audacity-EffectManager.$(OBJEXT) \ @@ -789,6 +791,7 @@ am_audacity_OBJECTS = $(am__objects_1) audacity-AboutDialog.$(OBJEXT) \ effects/audacity-Generator.$(OBJEXT) \ effects/audacity-Invert.$(OBJEXT) \ effects/audacity-LoadEffects.$(OBJEXT) \ + effects/audacity-Loudness.$(OBJEXT) \ effects/audacity-Noise.$(OBJEXT) \ effects/audacity-NoiseReduction.$(OBJEXT) \ effects/audacity-NoiseRemoval.$(OBJEXT) \ @@ -1314,6 +1317,7 @@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ +runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ @@ -1523,17 +1527,18 @@ audacity_SOURCES = $(libaudacity_la_SOURCES) AboutDialog.cpp \ effects/ClickRemoval.h effects/Compressor.cpp \ effects/Compressor.h effects/Contrast.cpp effects/Contrast.h \ effects/Distortion.cpp effects/Distortion.h \ - effects/DtmfGen.cpp effects/DtmfGen.h effects/Echo.cpp \ - effects/Echo.h effects/Effect.cpp effects/Effect.h \ - effects/EffectManager.cpp effects/EffectManager.h \ - effects/EffectRack.cpp effects/EffectRack.h \ - effects/Equalization.cpp effects/Equalization.h \ - effects/Equalization48x.cpp effects/Equalization48x.h \ - effects/Fade.cpp effects/Fade.h effects/FindClipping.cpp \ - effects/FindClipping.h effects/Generator.cpp \ - effects/Generator.h effects/Invert.cpp effects/Invert.h \ - effects/LoadEffects.cpp effects/LoadEffects.h \ - effects/Noise.cpp effects/Noise.h effects/NoiseReduction.cpp \ + effects/DtmfGen.cpp effects/DtmfGen.h effects/EBUR128.cpp \ + effects/EBUR128.h effects/Echo.cpp effects/Echo.h \ + effects/Effect.cpp effects/Effect.h effects/EffectManager.cpp \ + effects/EffectManager.h effects/EffectRack.cpp \ + effects/EffectRack.h effects/Equalization.cpp \ + effects/Equalization.h effects/Equalization48x.cpp \ + effects/Equalization48x.h effects/Fade.cpp effects/Fade.h \ + effects/FindClipping.cpp effects/FindClipping.h \ + effects/Generator.cpp effects/Generator.h effects/Invert.cpp \ + effects/Invert.h effects/LoadEffects.cpp effects/LoadEffects.h \ + effects/Loudness.cpp effects/Loudness.h effects/Noise.cpp \ + effects/Noise.h effects/NoiseReduction.cpp \ effects/NoiseReduction.h effects/NoiseRemoval.cpp \ effects/NoiseRemoval.h effects/Normalize.cpp \ effects/Normalize.h effects/Paulstretch.cpp \ @@ -2017,6 +2022,8 @@ effects/audacity-Distortion.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-DtmfGen.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) +effects/audacity-EBUR128.$(OBJEXT): effects/$(am__dirstamp) \ + effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Echo.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Effect.$(OBJEXT): effects/$(am__dirstamp) \ @@ -2039,6 +2046,8 @@ effects/audacity-Invert.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-LoadEffects.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) +effects/audacity-Loudness.$(OBJEXT): effects/$(am__dirstamp) \ + effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-Noise.$(OBJEXT): effects/$(am__dirstamp) \ effects/$(DEPDIR)/$(am__dirstamp) effects/audacity-NoiseReduction.$(OBJEXT): effects/$(am__dirstamp) \ @@ -2808,6 +2817,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Contrast.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Distortion.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-DtmfGen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-EBUR128.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Echo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Effect.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-EffectManager.Po@am__quote@ @@ -2819,6 +2829,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Generator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Invert.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-LoadEffects.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Loudness.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-Noise.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-NoiseReduction.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@effects/$(DEPDIR)/audacity-NoiseRemoval.Po@am__quote@ @@ -5515,6 +5526,20 @@ effects/audacity-DtmfGen.obj: effects/DtmfGen.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-DtmfGen.obj `if test -f 'effects/DtmfGen.cpp'; then $(CYGPATH_W) 'effects/DtmfGen.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/DtmfGen.cpp'; fi` +effects/audacity-EBUR128.o: effects/EBUR128.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-EBUR128.o -MD -MP -MF effects/$(DEPDIR)/audacity-EBUR128.Tpo -c -o effects/audacity-EBUR128.o `test -f 'effects/EBUR128.cpp' || echo '$(srcdir)/'`effects/EBUR128.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-EBUR128.Tpo effects/$(DEPDIR)/audacity-EBUR128.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/EBUR128.cpp' object='effects/audacity-EBUR128.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-EBUR128.o `test -f 'effects/EBUR128.cpp' || echo '$(srcdir)/'`effects/EBUR128.cpp + +effects/audacity-EBUR128.obj: effects/EBUR128.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-EBUR128.obj -MD -MP -MF effects/$(DEPDIR)/audacity-EBUR128.Tpo -c -o effects/audacity-EBUR128.obj `if test -f 'effects/EBUR128.cpp'; then $(CYGPATH_W) 'effects/EBUR128.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/EBUR128.cpp'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-EBUR128.Tpo effects/$(DEPDIR)/audacity-EBUR128.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/EBUR128.cpp' object='effects/audacity-EBUR128.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-EBUR128.obj `if test -f 'effects/EBUR128.cpp'; then $(CYGPATH_W) 'effects/EBUR128.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/EBUR128.cpp'; fi` + effects/audacity-Echo.o: effects/Echo.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Echo.o -MD -MP -MF effects/$(DEPDIR)/audacity-Echo.Tpo -c -o effects/audacity-Echo.o `test -f 'effects/Echo.cpp' || echo '$(srcdir)/'`effects/Echo.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Echo.Tpo effects/$(DEPDIR)/audacity-Echo.Po @@ -5669,6 +5694,20 @@ effects/audacity-LoadEffects.obj: effects/LoadEffects.cpp @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-LoadEffects.obj `if test -f 'effects/LoadEffects.cpp'; then $(CYGPATH_W) 'effects/LoadEffects.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/LoadEffects.cpp'; fi` +effects/audacity-Loudness.o: effects/Loudness.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Loudness.o -MD -MP -MF effects/$(DEPDIR)/audacity-Loudness.Tpo -c -o effects/audacity-Loudness.o `test -f 'effects/Loudness.cpp' || echo '$(srcdir)/'`effects/Loudness.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Loudness.Tpo effects/$(DEPDIR)/audacity-Loudness.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/Loudness.cpp' object='effects/audacity-Loudness.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Loudness.o `test -f 'effects/Loudness.cpp' || echo '$(srcdir)/'`effects/Loudness.cpp + +effects/audacity-Loudness.obj: effects/Loudness.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Loudness.obj -MD -MP -MF effects/$(DEPDIR)/audacity-Loudness.Tpo -c -o effects/audacity-Loudness.obj `if test -f 'effects/Loudness.cpp'; then $(CYGPATH_W) 'effects/Loudness.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Loudness.cpp'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Loudness.Tpo effects/$(DEPDIR)/audacity-Loudness.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='effects/Loudness.cpp' object='effects/audacity-Loudness.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -c -o effects/audacity-Loudness.obj `if test -f 'effects/Loudness.cpp'; then $(CYGPATH_W) 'effects/Loudness.cpp'; else $(CYGPATH_W) '$(srcdir)/effects/Loudness.cpp'; fi` + effects/audacity-Noise.o: effects/Noise.cpp @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(audacity_CPPFLAGS) $(CPPFLAGS) $(audacity_CXXFLAGS) $(CXXFLAGS) -MT effects/audacity-Noise.o -MD -MP -MF effects/$(DEPDIR)/audacity-Noise.Tpo -c -o effects/audacity-Noise.o `test -f 'effects/Noise.cpp' || echo '$(srcdir)/'`effects/Noise.cpp @am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) effects/$(DEPDIR)/audacity-Noise.Tpo effects/$(DEPDIR)/audacity-Noise.Po diff --git a/tests/Makefile.in b/tests/Makefile.in index 57fb24c36..205a112d9 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2014 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it,