Merge loudness effect from mmmaise
This commit is contained in:
commit
fe7434bc9f
12
Makefile.in
12
Makefile.in
|
@ -1,7 +1,7 @@
|
|||
# Makefile.in generated by automake 1.15 from Makefile.am.
|
||||
# Makefile.in generated by automake 1.15.1 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2014 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1994-2017 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
|
@ -972,7 +972,7 @@ distdir: $(DISTFILES)
|
|||
! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
|
||||
|| chmod -R a+r "$(distdir)"
|
||||
dist-gzip: distdir
|
||||
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
|
||||
tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
|
||||
$(am__post_remove_distdir)
|
||||
|
||||
dist-bzip2: distdir
|
||||
|
@ -997,7 +997,7 @@ dist-shar: distdir
|
|||
@echo WARNING: "Support for shar distribution archives is" \
|
||||
"deprecated." >&2
|
||||
@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
|
||||
shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
|
||||
shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
|
||||
$(am__post_remove_distdir)
|
||||
|
||||
dist-zip: distdir
|
||||
|
@ -1015,7 +1015,7 @@ dist dist-all:
|
|||
distcheck: dist
|
||||
case '$(DIST_ARCHIVES)' in \
|
||||
*.tar.gz*) \
|
||||
GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
|
||||
eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
|
||||
*.tar.bz2*) \
|
||||
bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
|
||||
*.tar.lz*) \
|
||||
|
@ -1025,7 +1025,7 @@ distcheck: dist
|
|||
*.tar.Z*) \
|
||||
uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
|
||||
*.shar.gz*) \
|
||||
GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
|
||||
eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
|
||||
*.zip*) \
|
||||
unzip $(distdir).zip ;;\
|
||||
esac
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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, () ) \
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
|
@ -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);
|
||||
|
|
@ -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)
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue