Merge loudness effect from mmmaise

This commit is contained in:
SteveDaulton 2019-11-27 21:14:05 +00:00
commit fe7434bc9f
26 changed files with 1897 additions and 599 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 */; };
@ -284,6 +285,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 */; };
@ -1995,6 +1997,8 @@
1790B01309883BFD008A330A /* ClickRemoval.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = ClickRemoval.h; sourceTree = "<group>"; tabWidth = 3; };
1790B01409883BFD008A330A /* Compressor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Compressor.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B01509883BFD008A330A /* Compressor.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Compressor.h; sourceTree = "<group>"; tabWidth = 3; };
1790B01609883BFD008A330A /* EBUR128.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = EBUR128.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B01619883BFD008A330A /* EBUR128.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = EBUR128.h; sourceTree = "<group>"; tabWidth = 3; };
1790B01709883BFD008A330A /* Echo.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Echo.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B01809883BFD008A330A /* Echo.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Echo.h; sourceTree = "<group>"; tabWidth = 3; };
1790B01909883BFD008A330A /* Effect.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 30; indentWidth = 3; path = Effect.cpp; sourceTree = "<group>"; tabWidth = 3; };
@ -2012,6 +2016,8 @@
1790B02B09883BFD008A330A /* Leveller.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Leveller.h; sourceTree = "<group>"; tabWidth = 3; };
1790B02C09883BFD008A330A /* LoadEffects.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = LoadEffects.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B02D09883BFD008A330A /* LoadEffects.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = LoadEffects.h; sourceTree = "<group>"; tabWidth = 3; };
1790B02D19883BFD008A330A /* Loudness.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Loudness.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B02D29883BFD008A330A /* Loudness.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Loudness.h; sourceTree = "<group>"; tabWidth = 3; };
1790B02E09883BFD008A330A /* Noise.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = Noise.cpp; sourceTree = "<group>"; tabWidth = 3; };
1790B02F09883BFD008A330A /* Noise.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.c.h; path = Noise.h; sourceTree = "<group>"; tabWidth = 3; };
1790B03009883BFD008A330A /* NoiseRemoval.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = NoiseRemoval.cpp; sourceTree = "<group>"; tabWidth = 3; };
@ -4707,6 +4713,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 */,
@ -4738,6 +4746,8 @@
1790B02B09883BFD008A330A /* Leveller.h */,
1790B02C09883BFD008A330A /* LoadEffects.cpp */,
1790B02D09883BFD008A330A /* LoadEffects.h */,
1790B02D19883BFD008A330A /* Loudness.cpp */,
1790B02D29883BFD008A330A /* Loudness.h */,
28D587C50E264CBB009C7DEA /* lv2 */,
1790B02E09883BFD008A330A /* Noise.cpp */,
1790B02F09883BFD008A330A /* Noise.h */,
@ -8421,6 +8431,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 */,
@ -8430,6 +8441,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 */,

View File

@ -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
@ -252,6 +253,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

View File

@ -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.

View File

@ -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 \
@ -449,6 +451,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 \

View File

@ -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,
@ -398,17 +398,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 \
@ -774,6 +775,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) \
@ -785,6 +787,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) \
@ -1310,6 +1313,7 @@ pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
runstatedir = @runstatedir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
@ -1519,17 +1523,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 \
@ -2011,6 +2016,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) \
@ -2033,6 +2040,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) \
@ -2802,6 +2811,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@
@ -2813,6 +2823,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@
@ -5509,6 +5520,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
@ -5663,6 +5688,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

View File

@ -10,13 +10,14 @@ Max Maisel
***********************************************************************/
#include "Biquad.h"
#include "Audacity.h"
#include <cmath>
#define square(a) ((a)*(a))
#define PI M_PI
Biquad::Biquad()
{
pfIn = 0;
pfOut = 0;
fNumerCoeffs[B0] = 1;
fNumerCoeffs[B1] = 0;
fNumerCoeffs[B2] = 0;
@ -33,29 +34,307 @@ 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++);
}
void ComplexDiv (float fNumerR, float fNumerI, float fDenomR, float fDenomI, float* pfQuotientR, float* pfQuotientI)
const double Biquad::s_fChebyCoeffs[MAX_Order][MAX_Order + 1] =
{
float fDenom = square(fDenomR) + square(fDenomI);
// 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> Biquad::CalcButterworthFilter(int order, double fn, double fc, int subtype)
{
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
// Set up the coefficients in all the biquads
double fNorm = fc / fn;
if (fNorm >= 0.9999)
fNorm = 0.9999F;
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++)
{
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
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
double fSPoleX = -fC;
double 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++)
{
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
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> Biquad::CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int subtype)
{
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
// Set up the coefficients in all the biquads
double fNorm = fc / fn;
if (fNorm >= 0.9999)
fNorm = 0.9999F;
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++)
{
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
{
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)
{
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;
}
else
{
// Odd order - now do the 1st-order section
double fSPoleX = -fC * sinh (a);
double 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> Biquad::CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int subtype)
{
ArrayOf<Biquad> pBiquad(size_t((order+1) / 2), true);
// Set up the coefficients in all the biquads
double fNorm = fc / fn;
if (fNorm >= 0.9999)
fNorm = 0.9999F;
double fC = tan (PI * fNorm / 2);
double fDCPoleDistSqr = 1.0F;
double fZPoleX, fZPoleY;
double fZZeroX, fZZeroY;
double beta = cos (fNorm*PI);
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;
// 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);
}
void Biquad::ComplexDiv (double fNumerR, double fNumerI, double fDenomR, double fDenomI,
double* pfQuotientR, double* pfQuotientI)
{
double 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)
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 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);
}
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;
}

View File

@ -12,25 +12,32 @@ Max Maisel
#ifndef __BIQUAD_H__
#define __BIQUAD_H__
#include "MemoryX.h"
/// \brief Represents a biquad digital filter.
struct Biquad
{
Biquad();
void Reset();
void Process(int iNumSamples);
void Process(float* pfIn, float* pfOut, int iNumSamples);
enum
{
/// 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)
{
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] -
@ -42,18 +49,31 @@ struct Biquad
return fOut;
}
float* pfIn;
float* pfOut;
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
{
kLowPass,
kHighPass,
nSubTypes
};
static ArrayOf<Biquad> CalcButterworthFilter(int order, double fn, double fc, int type);
static ArrayOf<Biquad> CalcChebyshevType1Filter(int order, double fn, double fc, double ripple, int type);
static ArrayOf<Biquad> CalcChebyshevType2Filter(int order, double fn, double fc, double ripple, int type);
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);
};
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

180
src/effects/EBUR128.cpp Normal file
View File

@ -0,0 +1,180 @@
/**********************************************************************
Audacity: A Digital Audio Editor
EBUR128.cpp
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<Biquad> EBUR128::CalcWeightingFilter(double fs)
{
ArrayOf<Biquad> 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;
}

56
src/effects/EBUR128.h Normal file
View File

@ -0,0 +1,56 @@
/**********************************************************************
Audacity: A Digital Audio Editor
EBUR128.h
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<Biquad> 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<long int> 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<ArrayOf<Biquad>> mWeightingFilter;
};
#endif

View File

@ -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, () ) \

581
src/effects/Loudness.cpp Normal file
View File

@ -0,0 +1,581 @@
/**********************************************************************
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 <math.h>
#include <wx/intl.h>
#include <wx/valgen.h>
#include "../Internat.h"
#include "../Prefs.h"
#include "../ProjectFileManager.h"
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../WaveTrack.h"
#include "../widgets/valnum.h"
#include "../widgets/ProgressDialog.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 = false;
mDualMono = DEF_DualMono;
mNormalizeTo = kLoudness;
mLUFSLevel = DEF_LUFSLevel;
mRMSLevel = DEF_RMSLevel;
SaveUserPreset(GetCurrentSettingsGroup());
gPrefs->Flush();
}
return true;
}
bool EffectLoudness::Process()
{
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));
else // RMS
mRatio = DB_TO_LINEAR(TrapDouble(mRMSLevel, MIN_RMSLevel, MAX_RMSLevel));
// 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<WaveTrack>()
+ (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;
if(mNormalizeTo == kLoudness)
{
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;
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;
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);
}
mProgressMsg = topMsg + wxString::Format(_("Processing: %s"), trackName);
if(!ProcessOne(range, false))
{
// Processing failed -> abort
bGoodResult = false;
break;
}
}
this->ReplaceProcessedTracks(bGoodResult);
mLoudnessProcessor.reset();
FreeBuffers();
return bGoodResult;
}
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<double> 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
/// 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<WaveTrack>() + &Track::Any)
{
mTrackBufferCapacity = std::max(mTrackBufferCapacity, track->GetMaxBlockSize());
maxSampleRate = std::max(maxSampleRate, track->GetRate());
// There is a stereo track
if(track->IsLeader())
stereoTrackFound = true;
}
// 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();
}
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.
/// 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<WaveTrack> 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;
}
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<WaveTrack> 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()
{
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;
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<WaveTrack> 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()
{
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<double> 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<double> 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);
}

114
src/effects/Loudness.h Normal file
View File

@ -0,0 +1,114 @@
/**********************************************************************
Audacity: A Digital Audio Editor
Loudness.h
Max Maisel (based on Normalize effect)
**********************************************************************/
#ifndef __AUDACITY_EFFECT_LOUDNESS__
#define __AUDACITY_EFFECT_LOUDNESS__
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/event.h>
#include <wx/stattext.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include "Effect.h"
#include "Biquad.h"
#include "EBUR128.h"
class ShuttleGui;
#define LOUDNESS_PLUGIN_SYMBOL ComponentInterfaceSymbol{ XO("Loudness Normalization") }
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
void AllocBuffers();
void FreeBuffers();
bool GetTrackRMS(WaveTrack* track, float& rms);
bool ProcessOne(TrackIterRange<WaveTrack> range, bool analyse);
bool LoadBufferBlock(TrackIterRange<WaveTrack> range,
sampleCount pos, size_t len);
bool AnalyseBufferBlock();
bool ProcessBufferBlock();
void StoreBufferBlock(TrackIterRange<WaveTrack> range,
sampleCount pos, size_t len);
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;
float mMult;
float mRatio;
float mRMS[2];
std::unique_ptr<EBUR128> mLoudnessProcessor;
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

View File

@ -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<float>::lowest();
// Will compute a maximum
extent = std::numeric_limits<float>::lowest();
std::vector<float> 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<double> 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<double> 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);

View File

@ -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;

View File

@ -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;
@ -235,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];
}
@ -621,230 +602,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 +653,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 +674,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;

View File

@ -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 );

View File

@ -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,

16
tests/octave/README.md Normal file
View File

@ -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 `<desired_test.m>` with the correct filename)
```
./run_test.m <desired_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.

View File

@ -0,0 +1,273 @@
## 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
## 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
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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);
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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);
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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);
remove_all_tracks();
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"));
select_tracks(0, 100);
aud_do("LoudnessNormalization: LUFSLevel=-30 DualMono=0 NormalizeTo=0 StereoIndependent=0\n");
select_tracks(0, 1);
aud_do(cstrcat("Export2: Filename=\"", TMP_FILENAME, "\" NumChannels=1\n"));
system("sync");
y = audioread(TMP_FILENAME);
select_tracks(1, 1);
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);
## 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
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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);
remove_all_tracks();
aud_do(cstrcat("Import2: Filename=\"", TMP_FILENAME, "\"\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");
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);

197
tests/octave/run_test.m Executable file
View File

@ -0,0 +1,197 @@
#!/usr/bin/octave -qf
## Audacity Octave unit test runner
#
# Max Maisel
#
if !(nargin == 1 || nargin == 2)
printf("Usage: ./testbench.m <test-file.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
## 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;
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;
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);
end
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
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)

View File

@ -355,6 +355,7 @@
<ClCompile Include="..\..\..\src\effects\Compressor.cpp" />
<ClCompile Include="..\..\..\src\effects\Contrast.cpp" />
<ClCompile Include="..\..\..\src\effects\DtmfGen.cpp" />
<ClCompile Include="..\..\..\src\effects\EBUR128.cpp" />
<ClCompile Include="..\..\..\src\effects\Echo.cpp" />
<ClCompile Include="..\..\..\src\effects\Effect.cpp" />
<ClCompile Include="..\..\..\src\effects\EffectManager.cpp" />
@ -364,6 +365,7 @@
<ClCompile Include="..\..\..\src\effects\Generator.cpp" />
<ClCompile Include="..\..\..\src\effects\Invert.cpp" />
<ClCompile Include="..\..\..\src\effects\LoadEffects.cpp" />
<ClCompile Include="..\..\..\src\effects\Loudness.cpp" />
<ClCompile Include="..\..\..\src\effects\Noise.cpp" />
<ClCompile Include="..\..\..\src\effects\NoiseRemoval.cpp" />
<ClCompile Include="..\..\..\src\effects\Normalize.cpp" />
@ -755,6 +757,7 @@
<ClInclude Include="..\..\..\src\effects\Compressor.h" />
<ClInclude Include="..\..\..\src\effects\Contrast.h" />
<ClInclude Include="..\..\..\src\effects\DtmfGen.h" />
<ClInclude Include="..\..\..\src\effects\EBUR128.h" />
<ClInclude Include="..\..\..\src\effects\Echo.h" />
<ClInclude Include="..\..\..\src\effects\Effect.h" />
<ClInclude Include="..\..\..\src\effects\EffectManager.h" />
@ -764,6 +767,7 @@
<ClInclude Include="..\..\..\src\effects\Generator.h" />
<ClInclude Include="..\..\..\src\effects\Invert.h" />
<ClInclude Include="..\..\..\src\effects\LoadEffects.h" />
<ClInclude Include="..\..\..\src\effects\Loudness.h" />
<ClInclude Include="..\..\..\src\effects\Noise.h" />
<ClInclude Include="..\..\..\src\effects\NoiseRemoval.h" />
<ClInclude Include="..\..\..\src\effects\Normalize.h" />
@ -1342,4 +1346,4 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\ny.targets" />
</ImportGroup>
</Project>
</Project>

View File

@ -425,6 +425,9 @@
<ClCompile Include="..\..\..\src\effects\DtmfGen.cpp">
<Filter>src\effects</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\effects\EBUR128.cpp">
<Filter>src\effects</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\effects\Echo.cpp">
<Filter>src\effects</Filter>
</ClCompile>
@ -452,6 +455,9 @@
<ClCompile Include="..\..\..\src\effects\LoadEffects.cpp">
<Filter>src\effects</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\effects\Loudness.cpp">
<Filter>src\effects</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\effects\Noise.cpp">
<Filter>src\effects</Filter>
</ClCompile>
@ -1582,6 +1588,9 @@
<ClInclude Include="..\..\..\src\effects\DtmfGen.h">
<Filter>src\effects</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\effects\EBUR128.h">
<Filter>src\effects</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\effects\Echo.h">
<Filter>src\effects</Filter>
</ClInclude>
@ -1609,6 +1618,9 @@
<ClInclude Include="..\..\..\src\effects\LoadEffects.h">
<Filter>src\effects</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\effects\Loudness.h">
<Filter>src\effects</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\effects\Noise.h">
<Filter>src\effects</Filter>
</ClInclude>
@ -2665,4 +2677,4 @@
<Filter>Resources</Filter>
</Manifest>
</ItemGroup>
</Project>
</Project>