From 90ee2b0aa9bdb8397f312cb13a4273326767db42 Mon Sep 17 00:00:00 2001 From: jthedering <51533355+jthedering@users.noreply.github.com> Date: Sun, 10 Aug 2014 18:35:59 +0000 Subject: [PATCH] Version 0.9 Added: Filter type "IIR", which allows to specify a user-defined IIR filter of arbitrary order with custom coefficients. Improved: For playback devices, the APO is now installed both as GFX (post-mix) and LFX (pre-mix) APO. Normally the filtering happens in GFX, but the command "Stage" allows to select LFX to apply effects based on the number of input channels. Added: Expression language. The commands "If", "ElseIf", "Else", "EndIf", "Eval" and inline expressions allow to change the filter behaviour based on runtime conditions. Added: Command "Copy", which copies audio data between channels. Added: Command "Delay", which delays the audio on the currently selected channels. Improved: Configuration files are now locked exclusively for writing while reading them. Improved: There is now a transition (10 ms) from old to new configuration after loading. Improved: Substantial internal restructuring to improve extensibility and maintainability. Filtering commands now work exactly in the order they were specified. Fixed: When the microphone input was in stereo but the application requested a mono stream, the audio was heavily distorted. Fixed: The sine sweep generated by the Benchmark application had floating-point precision issues leading to severe artifacts when certain filters were applied. --- Benchmark/Benchmark.cpp | 89 ++- Benchmark/Benchmark.vcxproj | 93 ++- Benchmark/Benchmark.vcxproj.filters | 179 +++++ ClassFactory.cpp | 2 +- Configurator/Configurator.cpp | 4 +- Configurator/Configurator.h | 2 +- Configurator/Configurator.vcxproj | 41 +- Configurator/Configurator.vcxproj.filters | 54 ++ DeviceAPOInfo.cpp | 199 +++-- DeviceAPOInfo.h | 8 +- DllMain.cpp | 55 +- EqualizerAPO.cpp | 235 ++++-- EqualizerAPO.h | 24 +- EqualizerAPO.vcxproj | 92 ++- EqualizerAPO.vcxproj.filters | 194 +++++ FilterConfiguration.cpp | 180 +++++ FilterConfiguration.h | 62 ++ FilterEngine.cpp | 667 ++++++++++++++++ FilterEngine.h | 111 +++ IFilter.cpp | 65 ++ IFilter.h | 46 ++ IFilterFactory.h | 44 ++ ParametricEQ.cpp | 736 ------------------ ParametricEQ.h | 73 -- Setup/Configuration reference (online).url | 6 + Setup/Setup.nsi | 5 + Setup/config/iir_lowpass.txt | 18 + Setup/config/selective_delay.txt | 11 + StringHelper.cpp | 74 -- Wiki/Configuration reference.txt | 370 +++++++++ Wiki/Documentation.txt | 87 +-- Wiki/Stages.odg | Bin 0 -> 22653 bytes Wiki/Stages.png | Bin 0 -> 23495 bytes Wiki/Stages.svg | 393 ++++++++++ Wiki/Transfer function.odf | Bin 0 -> 9275 bytes Wiki/Transfer function.png | Bin 0 -> 5351 bytes build.bat | 14 +- BiQuad.cpp => filters/BiQuad.cpp | 48 +- BiQuad.h => filters/BiQuad.h | 9 +- filters/BiQuadFilter.cpp | 69 ++ filters/BiQuadFilter.h | 45 ++ filters/BiQuadFilterFactory.cpp | 248 ++++++ filters/BiQuadFilterFactory.h | 40 + filters/ChannelFilter.cpp | 88 +++ filters/ChannelFilter.h | 41 + filters/ChannelFilterFactory.cpp | 64 ++ filters/ChannelFilterFactory.h | 31 + filters/CopyFilter.cpp | 167 ++++ filters/CopyFilter.h | 74 ++ filters/CopyFilterFactory.cpp | 96 +++ filters/CopyFilterFactory.h | 31 + filters/DelayFilter.cpp | 112 +++ filters/DelayFilter.h | 44 ++ filters/DelayFilterFactory.cpp | 64 ++ filters/DelayFilterFactory.h | 31 + filters/DeviceFilterFactory.cpp | 116 +++ filters/DeviceFilterFactory.h | 38 + filters/ExpressionFilterFactory.cpp | 152 ++++ filters/ExpressionFilterFactory.h | 36 + filters/IIRFilter.cpp | 123 +++ filters/IIRFilter.h | 43 + filters/IIRFilterFactory.cpp | 108 +++ filters/IIRFilterFactory.h | 33 + filters/IfFilterFactory.cpp | 211 +++++ filters/IfFilterFactory.h | 45 ++ filters/IncludeFilterFactory.cpp | 63 ++ filters/IncludeFilterFactory.h | 35 + filters/PreampFilter.cpp | 51 ++ filters/PreampFilter.h | 37 + filters/PreampFilterFactory.cpp | 51 ++ filters/PreampFilterFactory.h | 31 + filters/StageFilterFactory.cpp | 112 +++ filters/StageFilterFactory.h | 42 + LogHelper.cpp => helpers/LogHelper.cpp | 9 +- LogHelper.h => helpers/LogHelper.h | 2 +- helpers/MemoryHelper.cpp | 112 +++ StringHelper.h => helpers/MemoryHelper.h | 19 +- .../PrecisionTimer.h | 0 .../RegistryHelper.cpp | 190 +++-- RegistryHelper.h => helpers/RegistryHelper.h | 12 +- helpers/StringHelper.cpp | 146 ++++ helpers/StringHelper.h | 37 + parser/RegexFunctions.cpp | 98 +++ parser/RegexFunctions.h | 40 + parser/RegistryFunctions.cpp | 105 +++ parser/RegistryFunctions.h | 48 ++ parser/StringOperators.cpp | 56 ++ parser/StringOperators.h | 31 + version.h | 4 +- 89 files changed, 6396 insertions(+), 1375 deletions(-) create mode 100644 Benchmark/Benchmark.vcxproj.filters create mode 100644 Configurator/Configurator.vcxproj.filters create mode 100644 EqualizerAPO.vcxproj.filters create mode 100644 FilterConfiguration.cpp create mode 100644 FilterConfiguration.h create mode 100644 FilterEngine.cpp create mode 100644 FilterEngine.h create mode 100644 IFilter.cpp create mode 100644 IFilter.h create mode 100644 IFilterFactory.h delete mode 100644 ParametricEQ.cpp delete mode 100644 ParametricEQ.h create mode 100644 Setup/Configuration reference (online).url create mode 100644 Setup/config/iir_lowpass.txt create mode 100644 Setup/config/selective_delay.txt delete mode 100644 StringHelper.cpp create mode 100644 Wiki/Configuration reference.txt create mode 100644 Wiki/Stages.odg create mode 100644 Wiki/Stages.png create mode 100644 Wiki/Stages.svg create mode 100644 Wiki/Transfer function.odf create mode 100644 Wiki/Transfer function.png rename BiQuad.cpp => filters/BiQuad.cpp (63%) rename BiQuad.h => filters/BiQuad.h (81%) create mode 100644 filters/BiQuadFilter.cpp create mode 100644 filters/BiQuadFilter.h create mode 100644 filters/BiQuadFilterFactory.cpp create mode 100644 filters/BiQuadFilterFactory.h create mode 100644 filters/ChannelFilter.cpp create mode 100644 filters/ChannelFilter.h create mode 100644 filters/ChannelFilterFactory.cpp create mode 100644 filters/ChannelFilterFactory.h create mode 100644 filters/CopyFilter.cpp create mode 100644 filters/CopyFilter.h create mode 100644 filters/CopyFilterFactory.cpp create mode 100644 filters/CopyFilterFactory.h create mode 100644 filters/DelayFilter.cpp create mode 100644 filters/DelayFilter.h create mode 100644 filters/DelayFilterFactory.cpp create mode 100644 filters/DelayFilterFactory.h create mode 100644 filters/DeviceFilterFactory.cpp create mode 100644 filters/DeviceFilterFactory.h create mode 100644 filters/ExpressionFilterFactory.cpp create mode 100644 filters/ExpressionFilterFactory.h create mode 100644 filters/IIRFilter.cpp create mode 100644 filters/IIRFilter.h create mode 100644 filters/IIRFilterFactory.cpp create mode 100644 filters/IIRFilterFactory.h create mode 100644 filters/IfFilterFactory.cpp create mode 100644 filters/IfFilterFactory.h create mode 100644 filters/IncludeFilterFactory.cpp create mode 100644 filters/IncludeFilterFactory.h create mode 100644 filters/PreampFilter.cpp create mode 100644 filters/PreampFilter.h create mode 100644 filters/PreampFilterFactory.cpp create mode 100644 filters/PreampFilterFactory.h create mode 100644 filters/StageFilterFactory.cpp create mode 100644 filters/StageFilterFactory.h rename LogHelper.cpp => helpers/LogHelper.cpp (87%) rename LogHelper.h => helpers/LogHelper.h (91%) create mode 100644 helpers/MemoryHelper.cpp rename StringHelper.h => helpers/MemoryHelper.h (64%) rename Benchmark/PerformanceCounter.h => helpers/PrecisionTimer.h (100%) rename RegistryHelper.cpp => helpers/RegistryHelper.cpp (59%) rename RegistryHelper.h => helpers/RegistryHelper.h (81%) create mode 100644 helpers/StringHelper.cpp create mode 100644 helpers/StringHelper.h create mode 100644 parser/RegexFunctions.cpp create mode 100644 parser/RegexFunctions.h create mode 100644 parser/RegistryFunctions.cpp create mode 100644 parser/RegistryFunctions.h create mode 100644 parser/StringOperators.cpp create mode 100644 parser/StringOperators.h diff --git a/Benchmark/Benchmark.cpp b/Benchmark/Benchmark.cpp index 5360e7d..38d2d6a 100644 --- a/Benchmark/Benchmark.cpp +++ b/Benchmark/Benchmark.cpp @@ -17,20 +17,27 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifdef DEBUG +#include +#include +#endif #include #define _USE_MATH_DEFINES #include #include #include #include +#include -#include "../version.h" -#include "../ParametricEQ.h" -#include "../LogHelper.h" -#include "../StringHelper.h" -#include "PerformanceCounter.h" +#include "version.h" +#include "FilterEngine.h" +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/PrecisionTimer.h" +#include "helpers/MemoryHelper.h" using namespace std; +using namespace mup; int main(int argc, char** argv) { @@ -54,13 +61,18 @@ int main(int argc, char** argv) TCLAP::ValueArg inputArg("i", "input", "File to load sound data from instead of generating sweep", false, "", "string", cmd); TCLAP::ValueArg rateArg("r", "rate", "Sample rate of generated sweep (Default: 44100)", false, 44100, "integer", cmd); TCLAP::ValueArg toArg("t", "to", "End frequency of generated sweep in Hz (Default: 20000.0)", false, 20000.0f, "float", cmd); - TCLAP::ValueArg fromArg("f", "from", "Start frequency of generated sweep in Hz (Default: 0.1)", false, 0.1f, "float", cmd); + TCLAP::ValueArg fromArg("f", "from", "Start frequency of generated sweep in Hz (Default: 0.1)", false, 1.0f, "float", cmd); TCLAP::ValueArg lengthArg("l", "length", "Length of generated sweep in seconds (Default: 200.0)", false, 200.0f, "float", cmd); TCLAP::ValueArg channelArg("c", "channels", "Number of channels of generated sweep (Default: 2)", false, 2, "integer", cmd); cmd.parse(argc, argv); - LogHelper::set(stderr, verboseArg.getValue(), true, true); + bool verbose = verboseArg.getValue(); + LogHelper::set(stderr, verbose, true, true); +#ifdef _DEBUG + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); + //_CrtSetBreakAlloc(3318); +#endif unsigned sampleRate; unsigned channelCount; @@ -80,8 +92,12 @@ int main(int argc, char** argv) string input = inputArg.getValue(); if(input != "") { - SF_INFO info; printf("Reading sound data from %s\n", input.c_str()); + + PrecisionTimer timer; + timer.start(); + + SF_INFO info; SNDFILE* inFile = sf_open(input.c_str(), SFM_READ, &info); if(inFile == NULL) { @@ -103,6 +119,9 @@ int main(int argc, char** argv) sf_close(inFile); inFile = NULL; + + double readTime = timer.stop(); + printf("Reading input file took %g seconds\n", readTime); } else { @@ -117,32 +136,49 @@ int main(int argc, char** argv) printf("No input file given, so generating linear sine sweep from %g to %g Hz over %g seconds\n", sweepFrom, sweepTo, length); + PrecisionTimer timer; + timer.start(); + buf = new float[frameCount * channelCount]; + for(unsigned i=0; i max) max = f; if(f > 1.0f) @@ -188,12 +224,13 @@ int main(int argc, char** argv) sf_count_t numWritten = 0; while(numWritten < frameCount) - numWritten += sf_writef_float(outFile, buf + numWritten * channelCount, frameCount - numWritten); + numWritten += sf_writef_float(outFile, buf2 + numWritten * channelCount, frameCount - numWritten); sf_close(outFile); outFile = NULL; delete[] buf; + delete[] buf2; if(!noPauseArg.getValue()) system("pause"); @@ -202,7 +239,7 @@ int main(int argc, char** argv) } catch(TCLAP::ArgException e) { - printf("Error: %s for arg %s\n", e.error(), e.argId()); + printf("Error: %s for arg %s\n", e.error().c_str(), e.argId().c_str()); return -1; } -} +} \ No newline at end of file diff --git a/Benchmark/Benchmark.vcxproj b/Benchmark/Benchmark.vcxproj index 4d1a62c..08a2108 100644 --- a/Benchmark/Benchmark.vcxproj +++ b/Benchmark/Benchmark.vcxproj @@ -64,23 +64,23 @@ true - C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) - C:\Program Files\libsndfile\lib;$(LibraryPath) + $(SolutionDir);C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) + C:\Program Files (x86)\libsndfile\lib;$(LibraryPath) true - C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) - C:\Program Files\libsndfile\lib64;$(LibraryPath) + $(SolutionDir);C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) + C:\Program Files\libsndfile\lib;$(LibraryPath) false - C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) - C:\Program Files\libsndfile\lib;$(LibraryPath) + $(SolutionDir);C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) + C:\Program Files (x86)\libsndfile\lib;$(LibraryPath) false - C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) - C:\Program Files\libsndfile\lib64;$(LibraryPath) + $(SolutionDir);C:\Program Files\tclap-1.2.1\include;C:\Program Files\libsndfile\include;$(IncludePath) + C:\Program Files\libsndfile\lib;$(LibraryPath) @@ -93,7 +93,7 @@ Console true - version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) + muparserxd.lib;version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) @@ -107,7 +107,7 @@ Console true - version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) + muparserxd.lib;version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) @@ -126,7 +126,7 @@ true true true - version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) + muparserx.lib;version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) @@ -145,24 +145,71 @@ true true true - version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) + muparserx.lib;version.lib;Shlwapi.lib;libsndfile-1.lib;%(AdditionalDependencies) - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Benchmark/Benchmark.vcxproj.filters b/Benchmark/Benchmark.vcxproj.filters new file mode 100644 index 0000000..ea87009 --- /dev/null +++ b/Benchmark/Benchmark.vcxproj.filters @@ -0,0 +1,179 @@ + + + + + + + + + parser + + + parser + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + helpers + + + helpers + + + helpers + + + helpers + + + filters + + + filters + + + filters + + + parser + + + + + + + + + parser + + + parser + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + helpers + + + helpers + + + helpers + + + helpers + + + helpers + + + filters + + + filters + + + filters + + + parser + + + + + {af133437-31da-454b-abde-b62ce82af9b1} + + + {80c1c047-44e8-49fc-81d6-349c41e979a7} + + + {4bf76d90-8b50-493a-848b-019f88fa1f11} + + + \ No newline at end of file diff --git a/ClassFactory.cpp b/ClassFactory.cpp index dcda3e7..7028a09 100644 --- a/ClassFactory.cpp +++ b/ClassFactory.cpp @@ -18,7 +18,7 @@ */ #include "EqualizerAPO.h" -#include "LogHelper.h" +#include "helpers/LogHelper.h" #include "ClassFactory.h" long ClassFactory::lockCount = 0; diff --git a/Configurator/Configurator.cpp b/Configurator/Configurator.cpp index 45443dc..67479f0 100644 --- a/Configurator/Configurator.cpp +++ b/Configurator/Configurator.cpp @@ -20,8 +20,8 @@ #define WIN32_LEAN_AND_MEAN #include #include -#include "../StringHelper.h" -#include "../RegistryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/RegistryHelper.h" #include "Configurator.h" using namespace std; diff --git a/Configurator/Configurator.h b/Configurator/Configurator.h index faf0bcd..970090e 100644 --- a/Configurator/Configurator.h +++ b/Configurator/Configurator.h @@ -22,7 +22,7 @@ #include "resource.h" #include #include -#include "../DeviceAPOInfo.h" +#include "DeviceAPOInfo.h" class Configurator { diff --git a/Configurator/Configurator.vcxproj b/Configurator/Configurator.vcxproj index 7938cc6..b13d9b5 100644 --- a/Configurator/Configurator.vcxproj +++ b/Configurator/Configurator.vcxproj @@ -20,15 +20,20 @@ - - + + + + + - - + + + + @@ -89,35 +94,39 @@ true - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api false + $(LibraryPath);$(DDK_PATH)\lib\win7\i386 true - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api false + $(LibraryPath);$(DDK_PATH)\lib\win7\amd64 false - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api false + $(LibraryPath);$(DDK_PATH)\lib\win7\i386 false - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api false + $(LibraryPath);$(DDK_PATH)\lib\win7\amd64 NotUsing Level3 Disabled - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true - version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) @@ -125,12 +134,12 @@ NotUsing Level3 Disabled - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true - version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) @@ -140,14 +149,14 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true true true - version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) @@ -157,14 +166,14 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Windows true true true - version.lib;comctl32.lib;%(AdditionalDependencies) + audioeng.lib;version.lib;comctl32.lib;%(AdditionalDependencies) diff --git a/Configurator/Configurator.vcxproj.filters b/Configurator/Configurator.vcxproj.filters new file mode 100644 index 0000000..923bbbe --- /dev/null +++ b/Configurator/Configurator.vcxproj.filters @@ -0,0 +1,54 @@ + + + + + + + helpers + + + helpers + + + helpers + + + helpers + + + + + + + + helpers + + + helpers + + + helpers + + + helpers + + + helpers + + + + + + + + + + + + + + + {2584359f-ce98-42d5-b685-6c295bb97f68} + + + \ No newline at end of file diff --git a/DeviceAPOInfo.cpp b/DeviceAPOInfo.cpp index 8291786..afcc580 100644 --- a/DeviceAPOInfo.cpp +++ b/DeviceAPOInfo.cpp @@ -22,13 +22,12 @@ #include "DeviceAPOInfo.h" #include "EqualizerAPO.h" -#include "ParametricEQ.h" -#include "StringHelper.h" -#include "RegistryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/RegistryHelper.h" using namespace std; -#define commonKeyPath L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio" +#define commonKeyPath L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio" #define renderKeyPath commonKeyPath L"\\Render" #define captureKeyPath commonKeyPath L"\\Capture" static const wchar_t* connectionValueName = L"{a45c254e-df1c-4efd-8020-67d146a850e0},2"; @@ -84,51 +83,87 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid) isInstalled = false; isLFX = false; isAPO2 = false; - foundAPO2Values = false; + installAsAPO2 = false; if(!RegistryHelper::keyExists(keyPath + L"\\FxProperties")) { - originalApoGuid = APOGUID_NOKEY; + originalLfxGuid = APOGUID_NOKEY; + originalGfxGuid = APOGUID_NOKEY; } else { - originalApoGuid = APOGUID_NOVALUE; + originalLfxGuid = APOGUID_NOVALUE; + originalGfxGuid = APOGUID_NOVALUE; - bool found = tryAPOGuid(keyPath, lfxGuidValueName, true, false); - if(!found) - found = tryAPOGuid(keyPath, gfxGuidValueName, false, false); - if(!found) - found = tryAPOGuid(keyPath, lfxGuidValueName2, true, true); - if(!found) - found = tryAPOGuid(keyPath, gfxGuidValueName2, false, true); - - foundAPO2Values = RegistryHelper::isWindowsVersionAtLeast(6, 3) // Windows 8.1 + installAsAPO2 = RegistryHelper::isWindowsVersionAtLeast(6, 3) // Windows 8.1 && (RegistryHelper::valueExists(keyPath + L"\\FxProperties", lfxGuidValueName2) || RegistryHelper::valueExists(keyPath + L"\\FxProperties", gfxGuidValueName2)); - if(found) + wstring lfxValueName; + wstring gfxValueName; + if(installAsAPO2) { - if(RegistryHelper::valueExists(APP_REGPATH L"\\Child APOs", deviceGuid)) - originalApoGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs", deviceGuid); + lfxValueName = lfxGuidValueName2; + gfxValueName = gfxGuidValueName2; } else { - wstring guidValueName; - if(isInput) - { - guidValueName = lfxGuidValueName; - if(foundAPO2Values) - guidValueName = lfxGuidValueName2; - } - else - { - guidValueName = gfxGuidValueName; - if(foundAPO2Values) - guidValueName = gfxGuidValueName2; - } + lfxValueName = lfxGuidValueName; + gfxValueName = gfxGuidValueName; + } - if(RegistryHelper::valueExists(keyPath + L"\\FxProperties", guidValueName)) - originalApoGuid = RegistryHelper::readValue(keyPath + L"\\FxProperties", guidValueName); + bool found = false; + if(isInput) + found = tryAPOGuid(keyPath, lfxValueName, EQUALIZERAPO_LFX_GUID, true, installAsAPO2); + else + found = tryAPOGuid(keyPath, lfxValueName, EQUALIZERAPO_LFX_GUID, true, installAsAPO2) && tryAPOGuid(keyPath, gfxValueName, EQUALIZERAPO_GFX_GUID, false, installAsAPO2); + isDual = found; + + if(!found) + found = tryAPOGuid(keyPath, lfxGuidValueName, EQUALIZERAPO_GFX_GUID, true, false); + if(!found) + found = tryAPOGuid(keyPath, gfxGuidValueName, EQUALIZERAPO_GFX_GUID, false, false); + if(!found) + found = tryAPOGuid(keyPath, lfxGuidValueName2, EQUALIZERAPO_GFX_GUID, true, true); + if(!found) + found = tryAPOGuid(keyPath, gfxGuidValueName2, EQUALIZERAPO_GFX_GUID, false, true); + isInstalled = found; + + if(found) + { + if(RegistryHelper::keyExists(APP_REGPATH L"\\Child APOs\\" + deviceGuid)) + { + if(isAPO2) + { + originalLfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, lfxGuidValueName2); + originalGfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, gfxGuidValueName2); + } + else + { + originalLfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, lfxGuidValueName); + originalGfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, gfxGuidValueName); + } + } + else if(RegistryHelper::valueExists(APP_REGPATH L"\\Child APOs", deviceGuid)) + { + if(isLFX) + { + originalLfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs", deviceGuid); + originalGfxGuid = L""; + } + else + { + originalLfxGuid = L""; + originalGfxGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs", deviceGuid); + } + } + } + else + { + if(RegistryHelper::valueExists(keyPath + L"\\FxProperties", lfxValueName)) + originalLfxGuid = RegistryHelper::readValue(keyPath + L"\\FxProperties", lfxValueName); + if(RegistryHelper::valueExists(keyPath + L"\\FxProperties", gfxValueName)) + originalGfxGuid = RegistryHelper::readValue(keyPath + L"\\FxProperties", gfxValueName); } } @@ -137,38 +172,45 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid) bool DeviceAPOInfo::canBeUpgraded() { - return isInstalled && (!isInput && isLFX || !isAPO2 && foundAPO2Values); + return isInstalled && !isDual; } bool DeviceAPOInfo::isExperimental() { - return !isInstalled && originalApoGuid == APOGUID_NOKEY; + return !isInstalled && originalGfxGuid == APOGUID_NOKEY; } void DeviceAPOInfo::install() { RegistryHelper::createKey(APP_REGPATH L"\\Child APOs"); + RegistryHelper::createKey(APP_REGPATH L"\\Child APOs\\" + deviceGuid); - RegistryHelper::writeValue(APP_REGPATH L"\\Child APOs", deviceGuid, originalApoGuid); - - wstring keyPath; - wstring guidValueName; - if(isInput) + wstring lfxValueName; + wstring gfxValueName; + if(installAsAPO2) { - keyPath = captureKeyPath L"\\" + deviceGuid; - guidValueName = lfxGuidValueName; - if(foundAPO2Values) - guidValueName = lfxGuidValueName2; + lfxValueName = lfxGuidValueName2; + gfxValueName = gfxGuidValueName2; } else { - keyPath = renderKeyPath L"\\" + deviceGuid; - guidValueName = gfxGuidValueName; - if(foundAPO2Values) - guidValueName = gfxGuidValueName2; + lfxValueName = lfxGuidValueName; + gfxValueName = gfxGuidValueName; } - if(originalApoGuid == APOGUID_NOKEY) + RegistryHelper::writeValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, lfxValueName, originalLfxGuid); + if(isInput) + RegistryHelper::writeValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, gfxValueName, L""); + else + RegistryHelper::writeValue(APP_REGPATH L"\\Child APOs\\" + deviceGuid, gfxValueName, originalGfxGuid); + + wstring keyPath; + if(isInput) + keyPath = captureKeyPath L"\\" + deviceGuid; + else + keyPath = renderKeyPath L"\\" + deviceGuid; + + if(originalGfxGuid == APOGUID_NOKEY) { try { @@ -187,13 +229,22 @@ void DeviceAPOInfo::install() RegistryHelper::writeMultiValue(keyPath + L"\\FxProperties", sfxProcessingModesValueName, defaultProcessingModeValue); RegistryHelper::writeMultiValue(keyPath + L"\\FxProperties", mfxProcessingModesValueName, defaultProcessingModeValue); } - else if(originalApoGuid != APOGUID_NOVALUE) + else { - RegistryHelper::saveToFile(keyPath + L"\\FxProperties", guidValueName, - L"backup_" + StringHelper::replaceIllegalCharacters(deviceName) + L"_" + StringHelper::replaceIllegalCharacters(connectionName) + L".reg"); + vector valuenames; + if(originalLfxGuid != APOGUID_NOVALUE) + valuenames.push_back(lfxValueName); + if(!isInput && originalGfxGuid != APOGUID_NOVALUE) + valuenames.push_back(gfxValueName); + + if(!valuenames.empty()) + RegistryHelper::saveToFile(keyPath + L"\\FxProperties", valuenames, + L"backup_" + StringHelper::replaceIllegalCharacters(deviceName) + L"_" + StringHelper::replaceIllegalCharacters(connectionName) + L".reg"); } - RegistryHelper::writeValue(keyPath + L"\\FxProperties", guidValueName, RegistryHelper::getGuidString(__uuidof(EqualizerAPO))); + RegistryHelper::writeValue(keyPath + L"\\FxProperties", lfxValueName, RegistryHelper::getGuidString(EQUALIZERAPO_LFX_GUID)); + if(!isInput) + RegistryHelper::writeValue(keyPath + L"\\FxProperties", gfxValueName, RegistryHelper::getGuidString(EQUALIZERAPO_GFX_GUID)); } void DeviceAPOInfo::uninstall() @@ -204,40 +255,47 @@ void DeviceAPOInfo::uninstall() else keyPath = captureKeyPath L"\\" + deviceGuid; - if(originalApoGuid == APOGUID_NOKEY) + if(originalLfxGuid == APOGUID_NOKEY || originalGfxGuid == APOGUID_NOKEY) { RegistryHelper::deleteKey(keyPath + L"\\FxProperties"); } else { - wstring guidValueName; - if(isLFX) + wstring lfxValueName; + wstring gfxValueName; + if(isAPO2) { - guidValueName = lfxGuidValueName; - if(isAPO2) - guidValueName = lfxGuidValueName2; + lfxValueName = lfxGuidValueName2; + gfxValueName = gfxGuidValueName2; } else { - guidValueName = gfxGuidValueName; - if(isAPO2) - guidValueName = gfxGuidValueName2; + lfxValueName = lfxGuidValueName; + gfxValueName = gfxGuidValueName; } - if(originalApoGuid == APOGUID_NOVALUE) - RegistryHelper::deleteValue(keyPath + L"\\FxProperties", guidValueName); - else - RegistryHelper::writeValue(keyPath + L"\\FxProperties", guidValueName, originalApoGuid); + if(originalLfxGuid == APOGUID_NOVALUE) + RegistryHelper::deleteValue(keyPath + L"\\FxProperties", lfxValueName); + else if(originalLfxGuid != L"") + RegistryHelper::writeValue(keyPath + L"\\FxProperties", lfxValueName, originalLfxGuid); + + if(originalGfxGuid == APOGUID_NOVALUE) + RegistryHelper::deleteValue(keyPath + L"\\FxProperties", gfxValueName); + else if(originalGfxGuid != L"") + RegistryHelper::writeValue(keyPath + L"\\FxProperties", gfxValueName, originalGfxGuid); } if(RegistryHelper::valueExists(APP_REGPATH L"\\Child APOs", deviceGuid)) RegistryHelper::deleteValue(APP_REGPATH L"\\Child APOs", deviceGuid); - if(RegistryHelper::valueCount(APP_REGPATH L"\\Child APOs") == 0) + if(RegistryHelper::keyExists(APP_REGPATH L"\\Child APOs\\" + deviceGuid)) + RegistryHelper::deleteKey(APP_REGPATH L"\\Child APOs\\" + deviceGuid); + + if(RegistryHelper::keyEmpty(APP_REGPATH L"\\Child APOs")) RegistryHelper::deleteKey(APP_REGPATH L"\\Child APOs"); } -bool DeviceAPOInfo::tryAPOGuid(const wstring& keyPath, const wchar_t* valueName, bool lfx, bool apo2) +bool DeviceAPOInfo::tryAPOGuid(const wstring& keyPath, const wstring& valueName, GUID guid, bool lfx, bool apo2) { if(RegistryHelper::valueExists(keyPath + L"\\FxProperties", valueName)) { @@ -246,9 +304,8 @@ bool DeviceAPOInfo::tryAPOGuid(const wstring& keyPath, const wchar_t* valueName, GUID apoGuid; if(SUCCEEDED(CLSIDFromString(apoGuidString.c_str(), &apoGuid))) { - if(apoGuid == __uuidof(EqualizerAPO)) + if(apoGuid == guid) { - isInstalled = true; isLFX = lfx; isAPO2 = apo2; diff --git a/DeviceAPOInfo.h b/DeviceAPOInfo.h index bf65b5c..7e7f6dc 100644 --- a/DeviceAPOInfo.h +++ b/DeviceAPOInfo.h @@ -39,13 +39,15 @@ public: std::wstring deviceName; std::wstring connectionName; std::wstring deviceGuid; - std::wstring originalApoGuid; + std::wstring originalLfxGuid; + std::wstring originalGfxGuid; bool isInput; bool isInstalled; bool isLFX; bool isAPO2; - bool foundAPO2Values; + bool isDual; + bool installAsAPO2; private: - bool tryAPOGuid(const std::wstring& keyPath, const wchar_t* valueName, bool lfx, bool apo2); + bool tryAPOGuid(const std::wstring& keyPath, const std::wstring& valueName, GUID guid, bool lfx, bool apo2); }; diff --git a/DllMain.cpp b/DllMain.cpp index d4290c4..50dc4e9 100644 --- a/DllMain.cpp +++ b/DllMain.cpp @@ -23,8 +23,8 @@ #include "EqualizerAPO.h" #include "ClassFactory.h" -#include "RegistryHelper.h" -#include "LogHelper.h" +#include "helpers/RegistryHelper.h" +#include "helpers/LogHelper.h" using namespace std; @@ -48,7 +48,7 @@ STDAPI DllCanUnloadNow() STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) { - if(clsid != __uuidof(EqualizerAPO)) + if(clsid != EQUALIZERAPO_GFX_GUID && clsid != EQUALIZERAPO_LFX_GUID) return CLASS_E_CLASSNOTAVAILABLE; ClassFactory* factory = new ClassFactory(); @@ -66,26 +66,43 @@ STDAPI DllRegisterServer() wchar_t filename[1024]; GetModuleFileNameW(hModule, filename, sizeof(filename)/sizeof(wchar_t)); - HRESULT hr = RegisterAPO(EqualizerAPO::regProperties); + HRESULT hr = RegisterAPO(EqualizerAPO::regGfxProperties); if(FAILED(hr)) { - UnregisterAPO(__uuidof(EqualizerAPO)); + UnregisterAPO(EQUALIZERAPO_GFX_GUID); + return hr; + } + + hr = RegisterAPO(EqualizerAPO::regLfxProperties); + if(FAILED(hr)) + { + UnregisterAPO(EQUALIZERAPO_GFX_GUID); + UnregisterAPO(EQUALIZERAPO_LFX_GUID); return hr; } try { - wstring apoClsidString = RegistryHelper::getGuidString(__uuidof(EqualizerAPO)); + wstring apoClsidString = RegistryHelper::getGuidString(EQUALIZERAPO_GFX_GUID); - RegistryHelper::createKey(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString); - RegistryHelper::writeValue(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString, L"", L"EqualizerAPO Class"); - RegistryHelper::createKey(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); - RegistryHelper::writeValue(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"", filename); - RegistryHelper::writeValue(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"ThreadingModel", L"Both"); + RegistryHelper::createKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString, L"", L"EqualizerAPO GFX Class"); + RegistryHelper::createKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"", filename); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"ThreadingModel", L"Both"); + + apoClsidString = RegistryHelper::getGuidString(EQUALIZERAPO_LFX_GUID); + + RegistryHelper::createKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString, L"", L"EqualizerAPO LFX Class"); + RegistryHelper::createKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"", filename); + RegistryHelper::writeValue(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32", L"ThreadingModel", L"Both"); } catch(RegistryException e) { - UnregisterAPO(__uuidof(EqualizerAPO)); + UnregisterAPO(EQUALIZERAPO_GFX_GUID); + UnregisterAPO(EQUALIZERAPO_LFX_GUID); return E_FAIL; } @@ -96,17 +113,23 @@ STDAPI DllUnregisterServer() { try { - wstring apoClsidString = RegistryHelper::getGuidString(__uuidof(EqualizerAPO)); + wstring apoClsidString = RegistryHelper::getGuidString(EQUALIZERAPO_GFX_GUID); - RegistryHelper::deleteKey(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); - RegistryHelper::deleteKey(L"SOFTWARE\\Classes\\CLSID\\" + apoClsidString); + RegistryHelper::deleteKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); + RegistryHelper::deleteKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString); + + apoClsidString = RegistryHelper::getGuidString(EQUALIZERAPO_LFX_GUID); + + RegistryHelper::deleteKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString + L"\\InprocServer32"); + RegistryHelper::deleteKey(L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\" + apoClsidString); } catch(RegistryException e) { return E_FAIL; } - HRESULT hr = UnregisterAPO(__uuidof(EqualizerAPO)); + HRESULT hr = UnregisterAPO(EQUALIZERAPO_GFX_GUID); + UnregisterAPO(EQUALIZERAPO_LFX_GUID); return hr; } diff --git a/EqualizerAPO.cpp b/EqualizerAPO.cpp index c722620..32c5428 100644 --- a/EqualizerAPO.cpp +++ b/EqualizerAPO.cpp @@ -22,20 +22,23 @@ #define INITGUID #include -#include "LogHelper.h" -#include "RegistryHelper.h" +#include "helpers/LogHelper.h" +#include "helpers/RegistryHelper.h" #include "DeviceAPOInfo.h" #include "EqualizerAPO.h" using namespace std; long EqualizerAPO::instCount = 0; -const CRegAPOProperties<1> EqualizerAPO::regProperties( - __uuidof(EqualizerAPO), L"EqualizerAPO", L"Copyright (C) 2012", 1, 0, __uuidof(IAudioProcessingObject), +const CRegAPOProperties<1> EqualizerAPO::regGfxProperties( + EQUALIZERAPO_GFX_GUID, L"EqualizerAPO", L"Copyright (C) 2014", 1, 0, __uuidof(IAudioProcessingObject), + (APO_FLAG)( APO_FLAG_FRAMESPERSECOND_MUST_MATCH | APO_FLAG_BITSPERSAMPLE_MUST_MATCH | APO_FLAG_INPLACE)); +const CRegAPOProperties<1> EqualizerAPO::regLfxProperties( + EQUALIZERAPO_LFX_GUID, L"EqualizerAPO", L"Copyright (C) 2014", 1, 0, __uuidof(IAudioProcessingObject), (APO_FLAG)( APO_FLAG_FRAMESPERSECOND_MUST_MATCH | APO_FLAG_BITSPERSAMPLE_MUST_MATCH | APO_FLAG_INPLACE)); EqualizerAPO::EqualizerAPO(IUnknown* pUnkOuter) - :CBaseAudioProcessingObject(regProperties) + :CBaseAudioProcessingObject(regGfxProperties) { refCount = 1; if(pUnkOuter != NULL) @@ -101,6 +104,16 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) resetChild(); APOInitSystemEffects* initStruct = (APOInitSystemEffects*)pbyData; + GUID apoGuid = initStruct->APOInit.clsid; + try + { + TraceF(L"APO GUID: %s", RegistryHelper::getGuidString(apoGuid).c_str()); + } + catch(RegistryException e) + { + LogF(L"Could not convert apo guid to guid string"); + } + engine.setLfx((apoGuid == EQUALIZERAPO_LFX_GUID) != 0); PROPVARIANT var; PropVariantInit(&var); @@ -113,34 +126,32 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) wstring deviceGuid = var.pwszVal; TraceF(L"Endpoint GUID: %s", deviceGuid.c_str()); + wstring childApoGuid; + try { DeviceAPOInfo apoInfo; if(apoInfo.load(deviceGuid)) - peq.setDeviceInfo(apoInfo.deviceName, apoInfo.connectionName, apoInfo.deviceGuid); + { + engine.setDeviceInfo(apoInfo.isInput, apoInfo.deviceName, apoInfo.connectionName, apoInfo.deviceGuid); + + if(apoGuid == EQUALIZERAPO_LFX_GUID) + childApoGuid = apoInfo.originalLfxGuid; + else + childApoGuid = apoInfo.originalGfxGuid; + } } catch(RegistryException e) { - LogF(L"Could not read endpoint device info because of: %s", e.getMessage()); + LogF(L"Could not read endpoint device info because of: %s", e.getMessage().c_str()); } - wstring apoGuid; - try - { - apoGuid = RegistryHelper::readValue(APP_REGPATH L"\\Child APOs", var.pwszVal); - } - catch(RegistryException e) - { - LogF(L"Can't read child apo guid because of: %s", e.getMessage()); - return E_NOTFOUND; - } + TraceF(L"Child APO GUID: %s", childApoGuid.c_str()); - TraceF(L"Child APO GUID: %s", apoGuid.c_str()); - - if(apoGuid != APOGUID_NULL && apoGuid != APOGUID_NOKEY && apoGuid != APOGUID_NOVALUE) + if(childApoGuid != L"" && childApoGuid != APOGUID_NULL && childApoGuid != APOGUID_NOKEY && childApoGuid != APOGUID_NOVALUE) { GUID childGuid; - hr = CLSIDFromString(apoGuid.c_str(), &childGuid); + hr = CLSIDFromString(childApoGuid.c_str(), &childGuid); if(FAILED(hr)) { LogF(L"Can't convert guid string to guid"); @@ -210,18 +221,17 @@ HRESULT EqualizerAPO::IsInputFormatSupported(IAudioMediaType* pOutputFormat, LogF(L"Error in second GetUncompressedAudioFormat"); return hr; } - + TraceF(L"Output format = { %08X, %u, %u, %u, %f, %08X }", outFormat.guidFormatType.Data1, outFormat.dwSamplesPerFrame, outFormat.dwBytesPerSampleContainer, outFormat.dwValidBitsPerSample, outFormat.fFramesPerSecond, outFormat.dwChannelMask); - + if(childAPO) { hr = childAPO->IsInputFormatSupported(pOutputFormat, pRequestedInputFormat, ppSupportedInputFormat); if(SUCCEEDED(hr)) { TraceF(L"Success in IsInputFormatSupported of child apo"); - return hr; } else { @@ -230,42 +240,38 @@ HRESULT EqualizerAPO::IsInputFormatSupported(IAudioMediaType* pOutputFormat, } } - return CBaseAudioProcessingObject::IsInputFormatSupported(pOutputFormat, pRequestedInputFormat, ppSupportedInputFormat); -} + if(!childAPO || !SUCCEEDED(hr)) + { + hr = CBaseAudioProcessingObject::IsInputFormatSupported(pOutputFormat, pRequestedInputFormat, ppSupportedInputFormat); -void EqualizerAPO::APOProcess(UINT32 u32NumInputConnections, - APO_CONNECTION_PROPERTY** ppInputConnections, UINT32 u32NumOutputConnections, - APO_CONNECTION_PROPERTY** ppOutputConnections) -{ - switch( ppInputConnections[0]->u32BufferFlags ) - { - case BUFFER_VALID: - { - float* inputFrames = reinterpret_cast(ppInputConnections[0]->pBuffer); - float* outputFrames = reinterpret_cast(ppOutputConnections[0]->pBuffer); + // we do not support downmixing currently + if(hr == S_OK && inFormat.dwSamplesPerFrame > 2 && inFormat.dwSamplesPerFrame > outFormat.dwSamplesPerFrame) + { + *ppSupportedInputFormat = pOutputFormat; + hr = S_FALSE; + } + } - if(childRT) - { - childRT->APOProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, ppOutputConnections); - - peq.process(outputFrames, outputFrames, ppInputConnections[0]->u32ValidFrameCount); - } - else - peq.process(outputFrames, inputFrames, ppInputConnections[0]->u32ValidFrameCount); + if(hr == S_FALSE) + { + UNCOMPRESSEDAUDIOFORMAT supportedFormat; + HRESULT hr2 = (*ppSupportedInputFormat)->GetUncompressedAudioFormat(&supportedFormat); + if(FAILED(hr2)) + { + LogF(L"Error in third GetUncompressedAudioFormat"); + return hr2; + } - ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; - ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; + TraceF(L"InputFormat not accepted, SupportedInputFormat = { %08X, %u, %u, %u, %f, %08X }", + supportedFormat.guidFormatType.Data1, supportedFormat.dwSamplesPerFrame, supportedFormat.dwBytesPerSampleContainer, + supportedFormat.dwValidBitsPerSample, supportedFormat.fFramesPerSecond, supportedFormat.dwChannelMask); + } + else if(hr == S_OK) + { + TraceF(L"InputFormat accepted"); + } - break; - } - case BUFFER_SILENT: - { - ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; - ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; - - break; - } - } + return hr; } HRESULT EqualizerAPO::LockForProcess(UINT32 u32NumInputConnections, @@ -273,6 +279,35 @@ HRESULT EqualizerAPO::LockForProcess(UINT32 u32NumInputConnections, APO_CONNECTION_DESCRIPTOR** ppOutputConnections) { HRESULT hr; + + UNCOMPRESSEDAUDIOFORMAT inFormat; + hr = ppInputConnections[0]->pFormat->GetUncompressedAudioFormat(&inFormat); + if(FAILED(hr)) + { + LogF(L"Error in GetUncompressedAudioFormat in LockForProcess"); + return hr; + } + + unsigned maxInputFrameCount = ppInputConnections[0]->u32MaxFrameCount; + + TraceF(L"Input format in LockForProcess = { %08X, %u, %u, %u, %f, %08X, %u }", + inFormat.guidFormatType.Data1, inFormat.dwSamplesPerFrame, inFormat.dwBytesPerSampleContainer, + inFormat.dwValidBitsPerSample, inFormat.fFramesPerSecond, inFormat.dwChannelMask, maxInputFrameCount); + + UNCOMPRESSEDAUDIOFORMAT outFormat; + hr = ppOutputConnections[0]->pFormat->GetUncompressedAudioFormat(&outFormat); + if(FAILED(hr)) + { + LogF(L"Error in second GetUncompressedAudioFormat in LockForProcess"); + return hr; + } + + unsigned maxOutputFrameCount = ppOutputConnections[0]->u32MaxFrameCount; + + TraceF(L"Output format in LockForProcess = { %08X, %u, %u, %u, %f, %08X, %u }", + outFormat.guidFormatType.Data1, outFormat.dwSamplesPerFrame, outFormat.dwBytesPerSampleContainer, + outFormat.dwValidBitsPerSample, outFormat.fFramesPerSecond, outFormat.dwChannelMask, maxOutputFrameCount); + if(childCfg != NULL) { hr = childCfg->LockForProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, @@ -288,35 +323,36 @@ HRESULT EqualizerAPO::LockForProcess(UINT32 u32NumInputConnections, LogF(L"Error in CBaseAudioProcessingObject::LockForProcess"); return hr; } - - UNCOMPRESSEDAUDIOFORMAT inFormat; - hr = ppInputConnections[0]->pFormat->GetUncompressedAudioFormat(&inFormat); - if(FAILED(hr)) + else if(hr == S_OK) { - LogF(L"Error in GetUncompressedAudioFormat in LockForProcess"); - return hr; + TraceF(L"LockForProcess successful"); } - - TraceF(L"Input format in LockForProcess = { %08X, %u, %u, %u, %f, %08X }", - inFormat.guidFormatType.Data1, inFormat.dwSamplesPerFrame, inFormat.dwBytesPerSampleContainer, - inFormat.dwValidBitsPerSample, inFormat.fFramesPerSecond, inFormat.dwChannelMask); - UNCOMPRESSEDAUDIOFORMAT outFormat; - hr = ppOutputConnections[0]->pFormat->GetUncompressedAudioFormat(&outFormat); - if(FAILED(hr)) - { - LogF(L"Error in second GetUncompressedAudioFormat in LockForProcess"); - return hr; - } - - TraceF(L"Output format in LockForProcess = { %08X, %u, %u, %u, %f, %08X }", - outFormat.guidFormatType.Data1, outFormat.dwSamplesPerFrame, outFormat.dwBytesPerSampleContainer, - outFormat.dwValidBitsPerSample, outFormat.fFramesPerSecond, outFormat.dwChannelMask); + unsigned maxFrameCount = maxInputFrameCount; + if(maxFrameCount == 0) + maxFrameCount = maxOutputFrameCount; - if(outFormat.dwChannelMask == 0 && inFormat.dwSamplesPerFrame == outFormat.dwSamplesPerFrame) - peq.initialize(outFormat.fFramesPerSecond, outFormat.dwSamplesPerFrame, inFormat.dwChannelMask); + unsigned realChannelCount; + if(childCfg != NULL) + realChannelCount = outFormat.dwSamplesPerFrame; else - peq.initialize(outFormat.fFramesPerSecond, outFormat.dwSamplesPerFrame, outFormat.dwChannelMask); + realChannelCount = inFormat.dwSamplesPerFrame; + + unsigned channelMask; + if(engine.isCapture()) + { + channelMask = inFormat.dwChannelMask; + if(channelMask == 0 && inFormat.dwSamplesPerFrame == outFormat.dwSamplesPerFrame) + channelMask = outFormat.dwChannelMask; + } + else + { + channelMask = outFormat.dwChannelMask; + if(channelMask == 0 && inFormat.dwSamplesPerFrame == outFormat.dwSamplesPerFrame) + channelMask = inFormat.dwChannelMask; + } + + engine.initialize(outFormat.fFramesPerSecond, inFormat.dwSamplesPerFrame, realChannelCount, outFormat.dwSamplesPerFrame, channelMask, maxFrameCount); return hr; } @@ -357,6 +393,43 @@ void EqualizerAPO::resetChild() } } +#pragma AVRT_CODE_BEGIN +void EqualizerAPO::APOProcess(UINT32 u32NumInputConnections, + APO_CONNECTION_PROPERTY** ppInputConnections, UINT32 u32NumOutputConnections, + APO_CONNECTION_PROPERTY** ppOutputConnections) +{ + switch( ppInputConnections[0]->u32BufferFlags ) + { + case BUFFER_VALID: + { + float* inputFrames = reinterpret_cast(ppInputConnections[0]->pBuffer); + float* outputFrames = reinterpret_cast(ppOutputConnections[0]->pBuffer); + + if(childRT) + { + childRT->APOProcess(u32NumInputConnections, ppInputConnections, u32NumOutputConnections, ppOutputConnections); + + engine.process(outputFrames, outputFrames, ppInputConnections[0]->u32ValidFrameCount); + } + else + engine.process(outputFrames, inputFrames, ppInputConnections[0]->u32ValidFrameCount); + + ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; + ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; + + break; + } + case BUFFER_SILENT: + { + ppOutputConnections[0]->u32ValidFrameCount = ppInputConnections[0]->u32ValidFrameCount; + ppOutputConnections[0]->u32BufferFlags = ppInputConnections[0]->u32BufferFlags; + + break; + } + } +} +#pragma AVRT_CODE_END + HRESULT EqualizerAPO::NonDelegatingQueryInterface(const IID& iid, void** ppv) { if(iid == __uuidof(IUnknown)) @@ -393,4 +466,4 @@ ULONG EqualizerAPO::NonDelegatingRelease() } return refCount; -} +} \ No newline at end of file diff --git a/EqualizerAPO.h b/EqualizerAPO.h index 4539fb6..04b32b9 100644 --- a/EqualizerAPO.h +++ b/EqualizerAPO.h @@ -22,7 +22,7 @@ #include #include #include -#include "ParametricEQ.h" +#include "FilterEngine.h" class INonDelegatingUnknown { @@ -31,7 +31,12 @@ class INonDelegatingUnknown virtual ULONG __stdcall NonDelegatingRelease() = 0; }; -class __declspec(uuid("{EC1CC9CE-FAED-4822-828A-82A81A6F018F}")) EqualizerAPO +// {EACD2258-FCAC-4FF4-B36D-419E924A6D79} +const GUID EQUALIZERAPO_LFX_GUID = { 0xeacd2258, 0xfcac, 0x4ff4, { 0xb3, 0x6d, 0x41, 0x9e, 0x92, 0x4a, 0x6d, 0x79 } }; +// {EC1CC9CE-FAED-4822-828A-82A81A6F018F} +const GUID EQUALIZERAPO_GFX_GUID = { 0xec1cc9ce, 0xfaed, 0x4822, { 0x82, 0x8a, 0x82, 0xa8, 0x1a, 0x6f, 0x01, 0x8f } }; + +class EqualizerAPO : public CBaseAudioProcessingObject, public IAudioSystemEffects, public INonDelegatingUnknown { public: @@ -49,29 +54,30 @@ public: virtual HRESULT __stdcall IsInputFormatSupported(IAudioMediaType* pOutputFormat, IAudioMediaType* pRequestedInputFormat, IAudioMediaType** ppSupportedInputFormat); - // IAudioProcessingObjectRT - virtual void __stdcall APOProcess(UINT32 u32NumInputConnections, - APO_CONNECTION_PROPERTY** ppInputConnections, UINT32 u32NumOutputConnections, - APO_CONNECTION_PROPERTY** ppOutputConnections); - // IAudioProcessingObjectConfiguration virtual HRESULT __stdcall LockForProcess(UINT32 u32NumInputConnections, APO_CONNECTION_DESCRIPTOR** ppInputConnections, UINT32 u32NumOutputConnections, APO_CONNECTION_DESCRIPTOR** ppOutputConnections); virtual HRESULT __stdcall UnlockForProcess(void); + // IAudioProcessingObjectRT + virtual void __stdcall APOProcess(UINT32 u32NumInputConnections, + APO_CONNECTION_PROPERTY** ppInputConnections, UINT32 u32NumOutputConnections, + APO_CONNECTION_PROPERTY** ppOutputConnections); + // INonDelegatingUnknown virtual HRESULT __stdcall NonDelegatingQueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall NonDelegatingAddRef(); virtual ULONG __stdcall NonDelegatingRelease(); static long instCount; - static const CRegAPOProperties<1> regProperties; + static const CRegAPOProperties<1> regGfxProperties; + static const CRegAPOProperties<1> regLfxProperties; private: long refCount; IUnknown* pUnkOuter; - ParametricEQ peq; + FilterEngine engine; void resetChild(); diff --git a/EqualizerAPO.vcxproj b/EqualizerAPO.vcxproj index 2d9ba68..0c21d12 100644 --- a/EqualizerAPO.vcxproj +++ b/EqualizerAPO.vcxproj @@ -66,22 +66,22 @@ true - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api $(LibraryPath);$(DDK_PATH)\lib\win7\i386 true - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api $(LibraryPath);$(DDK_PATH)\lib\win7\amd64 false - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api $(LibraryPath);$(DDK_PATH)\lib\win7\i386 false - $(IncludePath);$(DDK_PATH)\inc\api + $(SolutionDir);$(IncludePath);$(DDK_PATH)\inc\api $(LibraryPath);$(DDK_PATH)\lib\win7\amd64 @@ -89,12 +89,12 @@ NotUsing Level3 Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;_DEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) Windows true - version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) + muparserxd.lib;audioeng.lib;version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) EqualizerAPO.def @@ -106,12 +106,12 @@ NotUsing Level3 Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;_DEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) Windows true - version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) + muparserxd.lib;audioeng.lib;version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) EqualizerAPO.def @@ -125,7 +125,7 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;NDEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) Fast @@ -133,7 +133,7 @@ true true true - version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) + muparserx.lib;audioeng.lib;version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) EqualizerAPO.def @@ -144,7 +144,7 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) + WIN32;USE_WINDDK;NDEBUG;_WINDOWS;_USRDLL;EQUALIZERAPO_EXPORTS;%(PreprocessorDefinitions) Fast @@ -152,32 +152,80 @@ true true true - version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) + muparserx.lib;audioeng.lib;version.lib;wmcodecdspuuid.lib;AudioBaseProcessingObject.lib;audiomediatypecrt.lib;Shlwapi.lib;%(AdditionalDependencies) EqualizerAPO.def - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EqualizerAPO.vcxproj.filters b/EqualizerAPO.vcxproj.filters new file mode 100644 index 0000000..d515cb5 --- /dev/null +++ b/EqualizerAPO.vcxproj.filters @@ -0,0 +1,194 @@ + + + + + + + + + + + + parser + + + parser + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + helpers + + + helpers + + + helpers + + + helpers + + + filters + + + filters + + + filters + + + parser + + + + + + + + + + + + + + parser + + + parser + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + helpers + + + helpers + + + helpers + + + helpers + + + helpers + + + filters + + + filters + + + filters + + + parser + + + + + + + + + + + + {d8adcbe6-a53a-44e1-93fd-272b6e8983fb} + + + {792503e3-923f-4123-808f-5acbf7da01fe} + + + {df98049b-1ada-41ad-a5ab-0a175600a767} + + + \ No newline at end of file diff --git a/FilterConfiguration.cpp b/FilterConfiguration.cpp new file mode 100644 index 0000000..fcc9f18 --- /dev/null +++ b/FilterConfiguration.cpp @@ -0,0 +1,180 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "FilterEngine.h" +#include "helpers/MemoryHelper.h" +#include "FilterConfiguration.h" + +using namespace std; + +FilterConfiguration::FilterConfiguration(FilterEngine* engine, const vector& filterInfos, unsigned allChannelCount) +{ + this->allChannelCount = allChannelCount; + realChannelCount = engine->getRealChannelCount(); + outputChannelCount = engine->getOutputChannelCount(); + unsigned maxFrameCount = engine->getMaxFrameCount(); + + allSamples = (float**)MemoryHelper::alloc(allChannelCount * sizeof(float*)); + for(size_t i=0; ifilterInfos = (FilterInfo**)MemoryHelper::alloc(filterCount * sizeof(FilterInfo*)); + for(size_t i=0; ifilterInfos[i] = filterInfos[i]; +} + +FilterConfiguration::~FilterConfiguration() +{ + MemoryHelper::free(currentSamples2); + MemoryHelper::free(currentSamples); + + for(size_t i=0; ifilter->~IFilter(); + MemoryHelper::free(filterInfos[i]->filter); + if(filterInfos[i]->inChannels != NULL) + MemoryHelper::free(filterInfos[i]->inChannels); + if(filterInfos[i]->outChannels != NULL) + MemoryHelper::free(filterInfos[i]->outChannels); + MemoryHelper::free(filterInfos[i]); + } + MemoryHelper::free(filterInfos); +} + +#pragma AVRT_CODE_BEGIN +void FilterConfiguration::process(float* input, unsigned frameCount) +{ +#define DEINTERLEAVE_MACRO(ccount) \ + {\ + for (size_t c=0; c= stereo output, upmix to stereo as the Windows audio system would do automatically if no APO was present + if(realChannelCount == 1 && outputChannelCount >= 2) + memcpy(allSamples[1], allSamples[0], frameCount * sizeof(float)); + + for(size_t i=0; iinChannelCount; j++) + currentSamples[j] = allSamples[filterInfo->inChannels[j]]; + if(filterInfo->inPlace) + { + for(size_t j=0; joutChannelCount; j++) + currentSamples2[j] = allSamples[filterInfo->outChannels[j]]; + } + else + { + for(size_t j=0; joutChannelCount; j++) + currentSamples2[j] = allSamples2[filterInfo->outChannels[j]]; + } + + filterInfo->filter->process(currentSamples2, currentSamples, frameCount); + + if(!filterInfo->inPlace) + { + for(size_t j=0; joutChannelCount; j++) + swap(allSamples[filterInfo->outChannels[j]], allSamples2[filterInfo->outChannels[j]]); + swap(currentSamples, currentSamples2); + } + } +} + +void FilterConfiguration::write(float* output, unsigned frameCount) +{ +#define INTERLEAVE_MACRO(ccount) \ + for (size_t c=0; c +#include + +#include "IFilter.h" + +class FilterEngine; + +struct FilterInfo +{ + IFilter* filter; + bool inPlace; + size_t* inChannels; + size_t inChannelCount; + size_t* outChannels; + size_t outChannelCount; +}; + +#pragma AVRT_VTABLES_BEGIN +class FilterConfiguration +{ +public: + FilterConfiguration(FilterEngine* engine, const std::vector& filterInfos, unsigned allChannelCount); + ~FilterConfiguration(); + + void process(float* input, unsigned frameCount); + void write(float* output, unsigned frameCount); + float** getOutputSamples() {return allSamples;} + bool isEmpty(); + +private: + unsigned realChannelCount; + unsigned outputChannelCount; + unsigned allChannelCount; + float** allSamples; + float** allSamples2; + float** currentSamples; + float** currentSamples2; + FilterInfo** filterInfos; + unsigned filterCount; +}; +#pragma AVRT_VTABLES_END diff --git a/FilterEngine.cpp b/FilterEngine.cpp new file mode 100644 index 0000000..4626a69 --- /dev/null +++ b/FilterEngine.cpp @@ -0,0 +1,667 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers/RegistryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "helpers/MemoryHelper.h" +#include "FilterEngine.h" +#include "filters/ExpressionFilterFactory.h" +#include "filters/DeviceFilterFactory.h" +#include "filters/StageFilterFactory.h" +#include "filters/IfFilterFactory.h" +#include "filters/ChannelFilterFactory.h" +#include "filters/BiQuadFilterFactory.h" +#include "filters/IIRFilterFactory.h" +#include "filters/PreampFilterFactory.h" +#include "filters/DelayFilterFactory.h" +#include "filters/CopyFilterFactory.h" +#include "filters/IncludeFilterFactory.h" + +using namespace std; +using namespace mup; + +FilterEngine::FilterEngine() + :parser(0) +{ + lfx = false; + capture = false; + inputChannelCount = 0; + lastInputWasSilent = false; + lastInputSize = -1; + threadHandle = NULL; + currentConfig = NULL; + nextConfig = NULL; + previousConfig = NULL; + transitionCounter = 0; + InitializeCriticalSection(&loadSection); + loadSemaphore = CreateSemaphore(NULL, 1, 1, NULL); + parser.EnableAutoCreateVar(true); + + factories.push_back(new DeviceFilterFactory()); + factories.push_back(new IfFilterFactory()); + factories.push_back(new ExpressionFilterFactory()); + factories.push_back(new IncludeFilterFactory()); + factories.push_back(new StageFilterFactory()); + factories.push_back(new ChannelFilterFactory()); + factories.push_back(new IIRFilterFactory()); + factories.push_back(new BiQuadFilterFactory()); + factories.push_back(new PreampFilterFactory()); + factories.push_back(new DelayFilterFactory()); + factories.push_back(new CopyFilterFactory()); + + channelNameToPosMap[L"L"] = SPEAKER_FRONT_LEFT; + channelNameToPosMap[L"R"] = SPEAKER_FRONT_RIGHT; + channelNameToPosMap[L"C"] = SPEAKER_FRONT_CENTER; + channelNameToPosMap[L"SUB"] = SPEAKER_LOW_FREQUENCY; + channelNameToPosMap[L"RL"] = SPEAKER_BACK_LEFT; + channelNameToPosMap[L"RR"] = SPEAKER_BACK_RIGHT; + channelNameToPosMap[L"RC"] = SPEAKER_BACK_CENTER; + channelNameToPosMap[L"SL"] = SPEAKER_SIDE_LEFT; + channelNameToPosMap[L"SR"] = SPEAKER_SIDE_RIGHT; + + for(hash_map::iterator it=channelNameToPosMap.begin(); it!=channelNameToPosMap.end(); it++) + channelPosToNameMap[it->second] = it->first; +} + +FilterEngine::~FilterEngine() +{ + // Make sure notification thread is terminated before cleaning up, otherwise deleted memory might be accessed in loadConfig + if(threadHandle != NULL) + { + SetEvent(shutdownEvent); + if(WaitForSingleObject(threadHandle, INFINITE) == WAIT_OBJECT_0) + { + TraceF(L"Successfully terminated directory change notification thread"); + } + CloseHandle(shutdownEvent); + CloseHandle(threadHandle); + threadHandle = NULL; + } + + cleanupConfigurations(); + + for(vector::iterator it = factories.begin(); it != factories.end(); it++) + delete *it; + + CloseHandle(loadSemaphore); + DeleteCriticalSection(&loadSection); +} + +void FilterEngine::setLfx(bool lfx) +{ + this->lfx = lfx; +} + +void FilterEngine::setDeviceInfo(bool capture, const wstring& deviceName, const wstring& connectionName, const wstring& deviceGuid) +{ + this->capture = capture; + this->deviceName = deviceName; + this->connectionName = connectionName; + this->deviceGuid = deviceGuid; +} + +void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount) +{ + EnterCriticalSection(&loadSection); + + cleanupConfigurations(); + + this->sampleRate = sampleRate; + this->inputChannelCount = inputChannelCount; + this->realChannelCount = realChannelCount; + this->outputChannelCount = outputChannelCount; + this->maxFrameCount = maxFrameCount; + this->transitionCounter = 0; + this->transitionLength = (unsigned)(sampleRate / 100); + + unsigned deviceChannelCount; + if(capture) + deviceChannelCount = inputChannelCount; + else + deviceChannelCount = outputChannelCount; + + if(channelMask == 0) + { + switch(deviceChannelCount) + { + case 1: + channelMask = KSAUDIO_SPEAKER_MONO; + break; + case 2: + channelMask = KSAUDIO_SPEAKER_STEREO; + break; + case 4: + channelMask = KSAUDIO_SPEAKER_QUAD; + break; + case 6: + channelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; + break; + case 8: + channelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + break; + } + } + this->channelMask = channelMask; + + wstringstream channelStream; + unsigned c=0; + for(int i=0; i<31; i++) + { + int channelPos = 1< 0) + channelStream << L" "; + if(channelPosToNameMap.find(channelPos) != channelPosToNameMap.end()) + channelStream << channelPosToNameMap[channelPos]; + else + channelStream << c; + } + } + TraceF(L"%d channels for this device: %s", deviceChannelCount, channelStream.str().c_str()); + + try + { + configPath = RegistryHelper::readValue(APP_REGPATH, L"ConfigPath"); + } + catch(RegistryException e) + { + LogF(L"Can't read config path because of: %s", e.getMessage().c_str()); + LeaveCriticalSection(&loadSection); + return; + } + + parser.ClearConst(); + parser.ClearFun(); + parser.ClearInfixOprt(); + parser.ClearOprt(); + parser.ClearPostfixOprt(); + parser.AddPackage(PackageCommon::Instance()); + parser.AddPackage(PackageNonCmplx::Instance()); + parser.AddPackage(PackageStr::Instance()); + parser.AddPackage(PackageMatrix::Instance()); + + for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + factory->initialize(this); + } + + if(configPath != L"") + { + loadConfig(); + + if(threadHandle == NULL) + { + shutdownEvent = CreateEventW(NULL, true, false, NULL); + threadHandle = CreateThread(NULL, 0, notificationThread, this, 0, NULL); + if(threadHandle == INVALID_HANDLE_VALUE) + threadHandle = NULL; + else + TraceF(L"Successfully created directory change notification thread %d for %s and its subtree", GetThreadId(threadHandle), configPath.c_str()); + } + } + LeaveCriticalSection(&loadSection); +} + +void FilterEngine::loadConfig() +{ + EnterCriticalSection(&loadSection); + timer.start(); + if(previousConfig != NULL) + { + previousConfig->~FilterConfiguration(); + MemoryHelper::free(previousConfig); + previousConfig = NULL; + } + + allChannelNames.clear(); + unsigned c=1; + for(int i=0; i<31; i++) + { + int channelPos = 1<::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + vector newFilters = factory->startOfConfiguration(); + if(!newFilters.empty()) + addFilters(newFilters); + } + + loadConfig(configPath + L"\\config.txt"); + + for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + vector newFilters = factory->endOfConfiguration(); + if(!newFilters.empty()) + addFilters(newFilters); + } + + void* mem = MemoryHelper::alloc(sizeof(FilterConfiguration)); + FilterConfiguration* config = new (mem) FilterConfiguration(this, filterInfos, (unsigned)allChannelNames.size()); + + filterInfos.clear(); + + double loadTime = timer.stop(); + TraceF(L"Finished loading configuration after %lf milliseconds", loadTime * 1000.0); + + if(currentConfig == NULL) + currentConfig = config; + else + nextConfig = config; + + LeaveCriticalSection(&loadSection); +} + +void FilterEngine::loadConfig(const wstring& path) +{ + TraceF(L"Loading configuration from %s", path.c_str()); + + HANDLE hFile = INVALID_HANDLE_VALUE; + while(hFile == INVALID_HANDLE_VALUE) + { + hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(hFile == INVALID_HANDLE_VALUE) + { + DWORD error = GetLastError(); + if(error != ERROR_SHARING_VIOLATION) + { + LogF(L"Error while reading configuration file: %s", StringHelper::getSystemErrorString(error).c_str()); + return; + } + + // file is being written, so wait + Sleep(1); + } + } + + stringstream inputStream; + + char buf[8192]; + unsigned long bytesRead = -1; + while(ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL) && bytesRead != 0) + { + inputStream.write(buf, bytesRead); + } + + CloseHandle(hFile); + + inputStream.seekg(0); + + vector savedChannelNames = currentChannelNames; + + for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + vector newFilters = factory->startOfFile(path); + if(!newFilters.empty()) + addFilters(newFilters); + } + + while(!inputStream.eof()) + { + string encodedLine; + getline(inputStream, encodedLine); + if(encodedLine.size() > 0 && encodedLine[encodedLine.size()-1] == '\r') + encodedLine.resize(encodedLine.size()-1); + + wstring line = StringHelper::toWString(encodedLine, CP_UTF8); + if(line.find(L'\uFFFD') != -1) + line = StringHelper::toWString(encodedLine, CP_ACP); + + size_t pos = line.find(L':'); + if(pos != -1) + { + wstring key = line.substr(0, pos); + wstring value = line.substr(pos + 1); + + // allow to use indentation + key = StringHelper::trim(key); + + for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + + vector newFilters; + try + { + newFilters = factory->createFilter(path, key, value); + } + catch(exception e) + { + LogF(L"%S", e.what()); + } + + if(key == L"") + break; + if(!newFilters.empty()) + { + addFilters(newFilters); + break; + } + } + } + } + + for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) + { + IFilterFactory* factory = *it; + vector newFilters = factory->endOfFile(path); + if(!newFilters.empty()) + addFilters(newFilters); + } + + // restore channels selected in outer configuration file + currentChannelNames = savedChannelNames; +} + +void FilterEngine::watchRegistryKey(const std::wstring& key) +{ + watchRegistryKeys.insert(key); +} + +#pragma AVRT_CODE_BEGIN +void FilterEngine::process(float *output, float *input, unsigned frameCount) +{ + bool inputSilent = true; + + if(lastInputSize != frameCount) + { + if(lastInputSize != -1) + LogF(L"Input size changed from %d to %d", lastInputSize, frameCount); + lastInputSize = frameCount; + } + + for (unsigned i = 0; i < frameCount * realChannelCount; i++) + { + if(input[i] != 0) + { + inputSilent = false; + break; + } + } + + if(inputSilent) + { + if(lastInputWasSilent) + { + //Avoid processing cost if silence would be output anyway + if(input != output) + memset(output, 0, frameCount * outputChannelCount * sizeof(float)); + + return; + } + else + lastInputWasSilent = true; + } + else + lastInputWasSilent = false; + + if(currentConfig->isEmpty() && nextConfig == NULL) + { + // avoid (de-)interleaving cost if no processing will happen anyway + if(realChannelCount == outputChannelCount) + { + if(input != output) + memcpy(output, input, outputChannelCount * frameCount * sizeof(float)); + + return; + } + } + + currentConfig->process(input, frameCount); + + if(nextConfig != NULL) + { + nextConfig->process(input, frameCount); + float** currentSamples = currentConfig->getOutputSamples(); + float** nextSamples = nextConfig->getOutputSamples(); + + for(unsigned f=0; f= transitionLength) + factor = 1.0f; + + for(unsigned c=0; cwrite(output, frameCount); + + if(nextConfig != NULL && transitionCounter >= transitionLength) + { + previousConfig = currentConfig; + currentConfig = nextConfig; + nextConfig = NULL; + transitionCounter = 0; + ReleaseSemaphore(loadSemaphore, 1, NULL); + } +} +#pragma AVRT_CODE_END + +void FilterEngine::addFilters(vector filters) +{ + for(vector::iterator it = filters.begin(); it != filters.end(); it++) + { + IFilter* filter = *it; + FilterInfo* filterInfo = (FilterInfo*)MemoryHelper::alloc(sizeof(FilterInfo)); + filterInfo->filter = filter; + filterInfo->inPlace = filter->getInPlace(); + vector savedChannelNames = currentChannelNames; + bool allChannels = filter->getAllChannels(); + if(allChannels) + currentChannelNames = allChannelNames; + + if(lastChannelNames == currentChannelNames) + { + filterInfo->inChannelCount = 0; + filterInfo->inChannels = NULL; + } + else + { + filterInfo->inChannelCount = currentChannelNames.size(); + filterInfo->inChannels = (size_t*)MemoryHelper::alloc(filterInfo->inChannelCount * sizeof(size_t)); + + size_t c = 0; + for(vector::iterator it2 = currentChannelNames.begin(); it2 != currentChannelNames.end(); it2++) + { + vector::iterator pos = find(allChannelNames.begin(), allChannelNames.end(), *it2); + filterInfo->inChannels[c++] = pos - allChannelNames.begin(); + } + } + + lastChannelNames = currentChannelNames; + + vector newChannelNames = filter->initialize(sampleRate, maxFrameCount, currentChannelNames); + + if(filterInfo->inPlace && lastInPlace && lastNewChannelNames == newChannelNames) + { + filterInfo->outChannelCount = 0; + filterInfo->outChannels = NULL; + } + else + { + filterInfo->outChannelCount = newChannelNames.size(); + filterInfo->outChannels = (size_t*)MemoryHelper::alloc(filterInfo->outChannelCount * sizeof(size_t)); + + size_t c = 0; + for(vector::iterator it2 = newChannelNames.begin(); it2 != newChannelNames.end(); it2++) + { + vector::iterator pos = find(allChannelNames.begin(), allChannelNames.end(), *it2); + if(pos == allChannelNames.end()) + { + filterInfo->outChannels[c++] = allChannelNames.size(); + allChannelNames.push_back(*it2); + } + else + { + filterInfo->outChannels[c++] = pos - allChannelNames.begin(); + } + } + } + + lastNewChannelNames = newChannelNames; + lastInPlace = filterInfo->inPlace; + if(!lastInPlace) + swap(lastChannelNames, lastNewChannelNames); + + filterInfos.push_back(filterInfo); + + if(filter->getSelectChannels()) + currentChannelNames = newChannelNames; + else + currentChannelNames = savedChannelNames; + } +} + +void FilterEngine::cleanupConfigurations() +{ + if(currentConfig != NULL) + { + currentConfig->~FilterConfiguration(); + MemoryHelper::free(currentConfig); + currentConfig = NULL; + } + + if(nextConfig != NULL) + { + nextConfig->~FilterConfiguration(); + MemoryHelper::free(nextConfig); + nextConfig = NULL; + } + + if(previousConfig != NULL) + { + previousConfig->~FilterConfiguration(); + MemoryHelper::free(previousConfig); + previousConfig = NULL; + } +} + +unsigned long __stdcall FilterEngine::notificationThread(void* parameter) +{ + FilterEngine* engine = (FilterEngine*)parameter; + + HANDLE notificationHandle = FindFirstChangeNotificationW(engine->configPath.c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE); + if(notificationHandle == INVALID_HANDLE_VALUE) + notificationHandle = NULL; + + HANDLE registryEvent = CreateEventW(NULL, true, false, NULL); + + HANDLE handles[3] = {engine->shutdownEvent, notificationHandle, registryEvent}; + while(true) + { + vector keyHandles; + for(auto it = engine->watchRegistryKeys.begin(); it != engine->watchRegistryKeys.end(); it++) + { + try + { + HKEY keyHandle = RegistryHelper::openKey(*it, KEY_NOTIFY | KEY_WOW64_64KEY); + keyHandles.push_back(keyHandle); + RegNotifyChangeKeyValue(keyHandle, false, REG_NOTIFY_CHANGE_LAST_SET, registryEvent, true); + } + catch(RegistryException e) + { + LogFStatic(L"%s", e.getMessage().c_str()); + } + } + + DWORD which = WaitForMultipleObjects(3, handles, false, INFINITE); + + for(auto it = keyHandles.begin(); it != keyHandles.end(); it++) + { + RegCloseKey(*it); + } + + if(which == WAIT_OBJECT_0) + { + //Shutdown + break; + } + else + { + if(which == WAIT_OBJECT_0 + 1) + { + FindNextChangeNotification(notificationHandle); + //Wait for second event within 10 milliseconds to avoid loading twice + WaitForMultipleObjects(1, ¬ificationHandle, false, 10); + } + + HANDLE handles[2] = {engine->shutdownEvent, engine->loadSemaphore}; + DWORD which = WaitForMultipleObjects(2, handles, false, INFINITE); + if(which == WAIT_OBJECT_0) + { + //Shutdown + break; + } + + engine->loadConfig(); + FindNextChangeNotification(notificationHandle); + ResetEvent(registryEvent); + } + } + + FindCloseChangeNotification(notificationHandle); + CloseHandle(registryEvent); + + return 0; +} \ No newline at end of file diff --git a/FilterEngine.h b/FilterEngine.h new file mode 100644 index 0000000..63c9905 --- /dev/null +++ b/FilterEngine.h @@ -0,0 +1,111 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "IFilterFactory.h" +#include "FilterConfiguration.h" +#include "helpers/PrecisionTimer.h" +#include "helpers/MemoryHelper.h" + +#pragma AVRT_VTABLES_BEGIN +class FilterEngine +{ +public: + FilterEngine(); + ~FilterEngine(); + + void setLfx(bool lfx); + void setDeviceInfo(bool capture, const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid); + void initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount); + void loadConfig(); + void loadConfig(const std::wstring& path); + void watchRegistryKey(const std::wstring& key); + void process(float *output, float *input, unsigned frameCount); + + bool isLfx() const {return lfx;} + bool isCapture() const {return capture;} + std::wstring getDeviceName() const {return deviceName;} + std::wstring getConnectionName() const {return connectionName;} + std::wstring getDeviceGuid() const {return deviceGuid;} + unsigned getInputChannelCount() const {return inputChannelCount;} + unsigned getRealChannelCount() const {return realChannelCount;} + unsigned getOutputChannelCount() const {return outputChannelCount;} + unsigned getChannelMask() const {return channelMask;} + float getSampleRate() const {return sampleRate;} + unsigned getMaxFrameCount() const {return maxFrameCount;} + mup::ParserX* getParser() {return &parser;} + +private: + void addFilters(std::vector filters); + void cleanupConfigurations(); + static unsigned long __stdcall notificationThread(void* parameter); + + std::vector factories; + + bool lfx; + bool capture; + std::wstring deviceName; + std::wstring connectionName; + std::wstring deviceGuid; + std::wstring configPath; + float sampleRate; + // number of input channels that originally existed before child APO processing + unsigned inputChannelCount; + // number of input channels in process (including channels coming from child APO output) + unsigned realChannelCount; + unsigned outputChannelCount; + unsigned channelMask; + unsigned maxFrameCount; + + // only used during loading + std::vector filterInfos; + std::vector currentChannelNames; + std::vector lastChannelNames; + std::vector lastNewChannelNames; + std::vector allChannelNames; + bool lastInPlace; + mup::ParserX parser; + + FilterConfiguration* currentConfig; + FilterConfiguration* nextConfig; + FilterConfiguration* previousConfig; + + unsigned transitionCounter; + unsigned transitionLength; + HANDLE loadSemaphore; + CRITICAL_SECTION loadSection; + PrecisionTimer timer; + void* threadHandle; + void* shutdownEvent; + stdext::hash_set watchRegistryKeys; + bool lastInputWasSilent; + unsigned lastInputSize; + stdext::hash_map channelNameToPosMap; + stdext::hash_map channelPosToNameMap; +}; +#pragma AVRT_VTABLES_END diff --git a/IFilter.cpp b/IFilter.cpp new file mode 100644 index 0000000..fb551e6 --- /dev/null +++ b/IFilter.cpp @@ -0,0 +1,65 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/LogHelper.h" +#include "IFilter.h" + +using namespace std; + +int IFilter::getChannelIndex(std::wstring word, const std::vector& channelNames, bool allowNew) +{ + int channelIndex = -1; + + if(iswdigit(word[0])) + { + channelIndex = wcstol(word.c_str(), NULL, 10) - 1; + + if(channelIndex < 0 || channelIndex >= (int)channelNames.size()) + { + LogF(L"Channel number %s out of range (1 - %d)", word.c_str(), channelNames.size()); + channelIndex = -1; + } + } + else + { + vector::const_iterator pos = find(channelNames.begin(), channelNames.end(), word); + + if(pos == channelNames.end()) + { + //Special handling to accept "wrong", but unambiguous positions + if(word == L"SL") + pos = find(channelNames.begin(), channelNames.end(), L"RL"); + else if(word == L"SR") + pos = find(channelNames.begin(), channelNames.end(), L"RR"); + else if(word == L"RL") + pos = find(channelNames.begin(), channelNames.end(), L"SL"); + else if(word == L"RR") + pos = find(channelNames.begin(), channelNames.end(), L"SR"); + } + + if(pos != channelNames.end()) + channelIndex = (int)(pos - channelNames.begin()); + else if(!allowNew) + LogF(L"Invalid channel position %s", word.c_str()); + } + + return channelIndex; +} diff --git a/IFilter.h b/IFilter.h new file mode 100644 index 0000000..fc65ca8 --- /dev/null +++ b/IFilter.h @@ -0,0 +1,46 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "helpers/MemoryHelper.h" + +#pragma AVRT_VTABLES_BEGIN +class IFilter +{ +public: + virtual ~IFilter() {} + + // request to get all channel names instead of selection + virtual bool getAllChannels() {return false;} + // request that output and input do not point to the same memory locations + virtual bool getInPlace() {return true;} + // request that the channelNames returned by initialize become the new selection + virtual bool getSelectChannels() {return false;} + // return value is the channelNames vector, which may contain additional or fewer channel names + virtual std::vector initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames) = 0; + virtual void process(float** output, float** input, unsigned frameCount) = 0; + +protected: + int getChannelIndex(std::wstring word, const std::vector& channelNames, bool allowNew = false); +}; +#pragma AVRT_VTABLES_END diff --git a/IFilterFactory.h b/IFilterFactory.h new file mode 100644 index 0000000..f73eff9 --- /dev/null +++ b/IFilterFactory.h @@ -0,0 +1,44 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "helpers/MemoryHelper.h" +#include "IFilter.h" + +class FilterEngine; + +#pragma AVRT_VTABLES_BEGIN +class IFilterFactory +{ +public: + virtual ~IFilterFactory() {} + + virtual void initialize(FilterEngine* engine) {} + virtual std::vector startOfConfiguration() {return std::vector();} + virtual std::vector startOfFile(const std::wstring& configPath) {return std::vector();} + // command and parameter may be altered by the factory + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters) = 0; + virtual std::vector endOfFile(const std::wstring& configPath) {return std::vector();} + virtual std::vector endOfConfiguration() {return std::vector();} +}; +#pragma AVRT_VTABLES_END diff --git a/ParametricEQ.cpp b/ParametricEQ.cpp deleted file mode 100644 index 0d7c46c..0000000 --- a/ParametricEQ.cpp +++ /dev/null @@ -1,736 +0,0 @@ -/* - This file is part of EqualizerAPO, a system-wide equalizer. - Copyright (C) 2012 Jonas Thedering - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#include "StringHelper.h" -#include "RegistryHelper.h" -#include "LogHelper.h" -#include "ParametricEQ.h" - -using namespace std; -using namespace stdext; - -static wregex regexType(L"^\\s*ON\\s+([A-Za-z]+)"); -static wregex regexFreq(L"\\s+Fc\\s*([0-9.\u00A0]+)\\s*H\\s*z"); -static wregex regexGain(L"\\s+Gain\\s*([-+0-9.]+)\\s*dB"); -static wregex regexQ(L"\\s+Q\\s*([0-9.]+)"); -static wregex regexBW(L"\\s+BW\\s+Oct\\s*([0-9.]+)"); -static wregex regexSlope(L"^\\s*([0-9.]+)\\s*dB"); - -ChannelData::ChannelData() -{ - preamp = 1.0f; - filterCount = 0; -} - -ParametricEQ::ParametricEQ() -{ - channelCount = 0; - channelData = NULL; - lastInputWasSilent = false; - threadHandle = NULL; - - channelNameToPosMap[L"L"] = SPEAKER_FRONT_LEFT; - channelNameToPosMap[L"R"] = SPEAKER_FRONT_RIGHT; - channelNameToPosMap[L"C"] = SPEAKER_FRONT_CENTER; - channelNameToPosMap[L"SUB"] = SPEAKER_LOW_FREQUENCY; - channelNameToPosMap[L"RL"] = SPEAKER_BACK_LEFT; - channelNameToPosMap[L"RR"] = SPEAKER_BACK_RIGHT; - channelNameToPosMap[L"RC"] = SPEAKER_BACK_CENTER; - channelNameToPosMap[L"SL"] = SPEAKER_SIDE_LEFT; - channelNameToPosMap[L"SR"] = SPEAKER_SIDE_RIGHT; - - for(hash_map::iterator it=channelNameToPosMap.begin(); it!=channelNameToPosMap.end(); it++) - channelPosToNameMap[it->second] = it->first; - - filterNameToTypeMap[L"PK"] = BiQuad::PEAKING; - filterNameToTypeMap[L"PEQ"] = BiQuad::PEAKING; - filterNameToTypeMap[L"Modal"] = BiQuad::PEAKING; - filterNameToTypeMap[L"LP"] = BiQuad::LOW_PASS; - filterNameToTypeMap[L"HP"] = BiQuad::HIGH_PASS; - filterNameToTypeMap[L"LPQ"] = BiQuad::LOW_PASS; - filterNameToTypeMap[L"HPQ"] = BiQuad::HIGH_PASS; - filterNameToTypeMap[L"BP"] = BiQuad::BAND_PASS; - filterNameToTypeMap[L"LS"] = BiQuad::LOW_SHELF; - filterNameToTypeMap[L"HS"] = BiQuad::HIGH_SHELF; - filterNameToTypeMap[L"LSC"] = BiQuad::LOW_SHELF; - filterNameToTypeMap[L"HSC"] = BiQuad::HIGH_SHELF; - filterNameToTypeMap[L"NO"] = BiQuad::NOTCH; - filterNameToTypeMap[L"AP"] = BiQuad::ALL_PASS; - - filterTypeToDescriptionMap[BiQuad::PEAKING] = L"peaking"; - filterTypeToDescriptionMap[BiQuad::LOW_PASS] = L"low-pass"; - filterTypeToDescriptionMap[BiQuad::HIGH_PASS] = L"high-pass"; - filterTypeToDescriptionMap[BiQuad::BAND_PASS] = L"band-pass"; - filterTypeToDescriptionMap[BiQuad::LOW_SHELF] = L"low-shelf"; - filterTypeToDescriptionMap[BiQuad::HIGH_SHELF] = L"high-shelf"; - filterTypeToDescriptionMap[BiQuad::NOTCH] = L"notch"; - filterTypeToDescriptionMap[BiQuad::ALL_PASS] = L"all-pass"; -} - -ParametricEQ::~ParametricEQ() -{ - // Make sure notification thread is terminated before cleaning up, otherwise deleted memory might be accessed in loadConfig - if(threadHandle != NULL) - { - SetEvent(shutdownEvent); - if(WaitForSingleObject(threadHandle, INFINITE) == WAIT_OBJECT_0) - { - TraceF(L"Successfully terminated directory change notification thread"); - } - CloseHandle(shutdownEvent); - CloseHandle(threadHandle); - threadHandle = NULL; - } - - if(channelData != NULL) - { - delete[] channelData; - channelData = NULL; - } -} - -void ParametricEQ::setDeviceInfo(const wstring& deviceName, const wstring& connectionName, const wstring& deviceGuid) -{ - this->deviceName = deviceName; - this->connectionName = connectionName; - this->deviceGuid = deviceGuid; -} - -void ParametricEQ::initialize(float sampleRate, unsigned channelCount, unsigned channelMask) -{ - this->sampleRate = sampleRate; - this->channelCount = channelCount; - channelData = new ChannelData[channelCount]; - - if(channelMask == 0) - { - switch(channelCount) - { - case 1: - channelMask = KSAUDIO_SPEAKER_MONO; - break; - case 2: - channelMask = KSAUDIO_SPEAKER_STEREO; - break; - case 4: - channelMask = KSAUDIO_SPEAKER_QUAD; - break; - case 6: - channelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; - break; - case 8: - channelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; - break; - } - } - this->channelMask = channelMask; - - wstringstream channelNames; - unsigned c=0; - for(int i=0; i<31; i++) - { - int channelPos = 1< 0) - channelNames << L" "; - if(channelPosToNameMap.find(channelPos) != channelPosToNameMap.end()) - channelNames << channelPosToNameMap[channelPos]; - else - channelNames << c; - } - } - TraceF(L"%d channels for this device: %s", channelCount, channelNames.str().c_str()); - - try - { - configPath = RegistryHelper::readValue(APP_REGPATH, L"ConfigPath"); - } - catch(RegistryException e) - { - LogF(L"Can't read config path because of: %s", e.getMessage().c_str()); - return; - } - if(configPath != L"") - { - loadConfig(); - - if(threadHandle == NULL) - { - shutdownEvent = CreateEventW(NULL, true, false, NULL); - threadHandle = CreateThread(NULL, 0, notificationThread, this, 0, NULL); - if(threadHandle == INVALID_HANDLE_VALUE) - threadHandle = NULL; - else - { - TraceF(L"Successfully created directory change notification thread for %s and its subtree", configPath.c_str()); - } - } - } -} - -void ParametricEQ::loadConfig() -{ - for(unsigned c=0; c selectedChannels(channelCount, true); - loadConfig(configPath + L"\\config.txt", selectedChannels); - - unsigned loadFilterCount = 0; - for(unsigned c=0; c 0) - channelFilterCounts << L" "; - if(channelPosToNameMap.find(channelPos) != channelPosToNameMap.end()) - channelFilterCounts << channelPosToNameMap[channelPos]; - else - channelFilterCounts << c; - - channelFilterCounts << ":" << channelData[c-1].loadFilterCount; - } - } - - TraceF(L"%d filters loaded: %s", loadFilterCount, channelFilterCounts.str().c_str()); -} - -void ParametricEQ::loadConfig(const wstring& path, vector selectedChannels) -{ - TraceF(L"Loading configuration from %s", path.c_str()); - - ifstream inputStream(path); - if(!inputStream.good()) - { - LogF(L"Config file not found at %s!", path.c_str()); - return; - } - - boolean deviceMatches = true; - wstring deviceString = StringHelper::toLowerCase(deviceName + L" " + connectionName + L" " + deviceGuid); - - while(!inputStream.eof()) - { - string encodedLine; - getline(inputStream, encodedLine); - - wstring line = StringHelper::toWString(encodedLine, CP_UTF8); - if(line.find(L'\uFFFD') != -1) - line = StringHelper::toWString(encodedLine, CP_ACP); - - size_t pos = line.find(L':'); - if(pos != -1) - { - wstring key = line.substr(0, pos); - wstring value = line.substr(pos + 1); - - if(key == L"Device") - { - value = value + L";"; - - vector> fullList; - vector currentList; - wstring currentWord; - - for(unsigned i=0; i 0) - { - currentList.push_back(currentWord); - currentWord.clear(); - } - if(c == L';' && currentList.size() > 0) - { - fullList.push_back(currentList); - currentList.clear(); - } - } - else - { - currentWord += c; - } - } - - boolean matches = false; - - for(unsigned i=0; i(channelCount, false); - - value = value + L" "; - - wstring currentWord; - for(unsigned i=0; i 0) - { - int channelNr = -1; - - if(currentWord == L"ALL") - { - selectedChannels = vector(channelCount, true); - } - else if(iswdigit(currentWord[0])) - { - channelNr = wcstol(currentWord.c_str(), NULL, 10) - 1; - } - else - { - int channelPos = -1; - if(channelNameToPosMap.find(currentWord) != channelNameToPosMap.end()) - channelPos = channelNameToPosMap[currentWord]; - else - LogF(L"Invalid channel position %s", currentWord.c_str()); - - if(channelPos != -1) - channelNr = getChannelNumber(channelPos); - } - - if(channelNr != -1 && channelNr < (int)channelCount) - { - selectedChannels[channelNr] = true; - } - - currentWord.clear(); - } - } - else - { - currentWord += c; - } - } - - wstringstream channelNumbers; - for(unsigned c=0; c 0) - channelNumbers << L", "; - - channelNumbers << c+1; - } - } - - TraceF(L"Selecting channel(s) number %s", channelNumbers.str().c_str()); - } - else if(key.find(L"Filter") == 0) - { - //Conversion to period as decimal mark, if needed - value = StringHelper::replaceCharacters(value, L",", L"."); - - wsmatch match; - wstring typeString; - - bool found = regex_search(value, match, regexType); - if(found) - { - typeString = match.str(1); - if(filterNameToTypeMap.find(typeString) != filterNameToTypeMap.end()) - { - BiQuad::Type type = filterNameToTypeMap[typeString]; - wstring typeDescription = filterTypeToDescriptionMap[type]; - value = match.suffix().str(); - - wstringstream stream; - stream << L"Adding " << typeDescription << L" filter"; - - float freq = 0; - float gain = 0; - float bandwidthOrQOrS = 0; - bool isBandwidth = false; - bool error = false; - - found = regex_search(value, match, regexFreq); - if(found) - { - wstring freqString = match.str(1); - freq = getFreq(freqString); - stream << " with frequency " << freq << " Hz"; - } - else - { - LogF(L"No frequency given in filter string %s%s", typeString.c_str(), value.c_str()); - error = true; - } - - found = regex_search(value, match, regexGain); - if(found) - { - if(type == BiQuad::LOW_PASS || type == BiQuad::HIGH_PASS || type == BiQuad::NOTCH || type == BiQuad::ALL_PASS) - TraceF(L"Ignoring gain for filter of type %s", typeDescription.c_str()); - else - { - wstring gainString = match.str(1); - gain = (float)wcstod(gainString.c_str(), NULL); - if(type == BiQuad::PEAKING) - stream << ", gain " << gain << " dB"; - else - stream << " and gain " << gain << " dB"; - } - } - else if(type == BiQuad::PEAKING || type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) - { - LogF(L"No gain given in filter string %s%s", typeString.c_str(), value.c_str()); - error = true; - } - - found = regex_search(value, match, regexQ); - if(found) - { - if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) - TraceF(L"Ignoring Q for filter of type %s", typeDescription.c_str()); - else - { - wstring qString = match.str(1); - bandwidthOrQOrS = (float)wcstod(qString.c_str(), NULL); - stream << " and Q " << bandwidthOrQOrS; - } - } - - found = regex_search(value, match, regexBW); - if(found) - { - if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) - TraceF(L"Ignoring bandwidth for filter of type %s", typeDescription.c_str()); - else - { - wstring bwString = match.str(1); - bandwidthOrQOrS = (float)wcstod(bwString.c_str(), NULL); - isBandwidth = true; - stream << " and bandwidth " << bandwidthOrQOrS << " octaves"; - } - } - - found = regex_search(value, match, regexSlope); - if(found) - { - if(!(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF)) - TraceF(L"Ignoring slope for filter of type %s", typeDescription.c_str()); - else - { - wstring slopeString = match.str(1); - bandwidthOrQOrS = (float)wcstod(slopeString.c_str(), NULL); - stream << " and slope " << bandwidthOrQOrS << " dB"; - } - } - - if(bandwidthOrQOrS == 0) - { - if(type == BiQuad::PEAKING || type == BiQuad::ALL_PASS) - { - LogF(L"No Q or bandwidth given in filter string %s%s", typeString.c_str(), value.c_str()); - error = true; - } - else if(type == BiQuad::LOW_PASS || type == BiQuad::HIGH_PASS || type == BiQuad::BAND_PASS) - { - bandwidthOrQOrS = (float)M_SQRT1_2; - } - else if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) - { - bandwidthOrQOrS = 0.9f; // found out by experimentation with RoomEQWizard - } - else if(type == BiQuad::NOTCH) - { - bandwidthOrQOrS = 30.0f; // found out by experimentation with RoomEQWizard - } - } - else if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) - { - // Maximum S is 1 for 12 dB - bandwidthOrQOrS /= 12.0f; - if(typeString[typeString.length()-1] != L'C') - { - // frequency adjustment for DCX2496 - float centerFreqFactor = pow(10.0f, abs(gain) / 80.0f / bandwidthOrQOrS); - if(type == BiQuad::LOW_SHELF) - freq *= centerFreqFactor; - else - freq /= centerFreqFactor; - } - } - - if(!error) - { - TraceF(L"%s", stream.str().c_str()); - - for(unsigned c=0; c 0 && iswspace(value[0])) - value = value.substr(1); - - wstring includePath; - if(PathIsRelativeW(value.c_str())) - { - wchar_t filePath[MAX_PATH]; - path._Copy_s(filePath, sizeof(filePath), MAX_PATH); - if(path.size() < MAX_PATH) - filePath[path.size()] = L'\0'; - else - filePath[MAX_PATH - 1] = L'\0'; - PathRemoveFileSpecW(filePath); - PathAppendW(filePath, value.c_str()); - includePath = filePath; - } - else - includePath = value; - - loadConfig(includePath, selectedChannels); - } - } - } -} - -void ParametricEQ::process(float *output, float *input, unsigned frameCount) -{ - bool inputSilent = true; - - for (unsigned i = 0; i < frameCount * channelCount; i++) - { - if(input[i] != 0) - { - inputSilent = false; - break; - } - } - - if(inputSilent) - { - if(lastInputWasSilent) - { - //Avoid processing cost if silence would be output anyway - if(input != output) - memset(output, 0, frameCount * channelCount * sizeof(float)); - - return; - } - else - lastInputWasSilent = true; - } - else - lastInputWasSilent = false; - - //Avoid denormals - for (unsigned c=0; c= 5) - { - if(s[s.length() - 4] == L'.') - { - //Interpret as thousands separator because of Room EQ Wizard - result *= 1000.0f; - } - } - - return result; - } - else - return -1.0f; -} - -unsigned ParametricEQ::getChannelNumber(unsigned position) -{ - //Special handling to accept "wrong", but unambiguous positions - if(channelMask == KSAUDIO_SPEAKER_5POINT1_SURROUND) - { - if(position == SPEAKER_BACK_LEFT) - return 4; - else if(position == SPEAKER_BACK_RIGHT) - return 5; - } - else if(channelMask == KSAUDIO_SPEAKER_5POINT1) - { - if(position == SPEAKER_SIDE_LEFT) - return 4; - else if(position == SPEAKER_SIDE_RIGHT) - return 5; - } - - if((channelMask & position) == 0) - return -1; - - int channelNr = 0; - for(unsigned i=1; iconfigPath.c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE); - if(notificationHandle == INVALID_HANDLE_VALUE) - notificationHandle = NULL; - - HANDLE handles[2] = {peq->shutdownEvent, notificationHandle}; - while(true) - { - DWORD which = WaitForMultipleObjects(2, handles, false, INFINITE); - - if(which == WAIT_OBJECT_0) - { - //Shutdown - break; - } - else - { - FindNextChangeNotification(notificationHandle); - //Wait for second event within 100 milliseconds to avoid loading twice - WaitForMultipleObjects(1, ¬ificationHandle, false, 100); - peq->loadConfig(); - FindNextChangeNotification(notificationHandle); - } - } - - FindCloseChangeNotification(notificationHandle); - - return 0; -} diff --git a/ParametricEQ.h b/ParametricEQ.h deleted file mode 100644 index 82f9691..0000000 --- a/ParametricEQ.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - This file is part of EqualizerAPO, a system-wide equalizer. - Copyright (C) 2012 Jonas Thedering - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include -#include -#include - -#include "BiQuad.h" - -struct ChannelData -{ - ChannelData(); - - float preamp; - unsigned filterCount; - BiQuad filters[100]; - - // used while loading instead of overwriting immediately - float loadPreamp; - unsigned loadFilterCount; -}; - -class ParametricEQ -{ -public: - ParametricEQ(); - ~ParametricEQ(); - - void setDeviceInfo(const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid); - void initialize(float sampleRate, unsigned channelCount, unsigned channelMask); - void loadConfig(); - void process(float *output, float *input, unsigned frameCount); - -private: - void loadConfig(const std::wstring& path, std::vector selectedChannels); - float getFreq(const std::wstring& freqString); - unsigned getChannelNumber(unsigned position); - static unsigned long __stdcall notificationThread(void* parameter); - - std::wstring deviceName; - std::wstring connectionName; - std::wstring deviceGuid; - std::wstring configPath; - float sampleRate; - unsigned channelCount; - unsigned channelMask; - ChannelData* channelData; - bool lastInputWasSilent; - void* threadHandle; - void* shutdownEvent; - stdext::hash_map channelNameToPosMap; - stdext::hash_map channelPosToNameMap; - stdext::hash_map filterNameToTypeMap; - stdext::hash_map filterTypeToDescriptionMap; -}; diff --git a/Setup/Configuration reference (online).url b/Setup/Configuration reference (online).url new file mode 100644 index 0000000..bce18e3 --- /dev/null +++ b/Setup/Configuration reference (online).url @@ -0,0 +1,6 @@ +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,2 +[InternetShortcut] +URL=http://sourceforge.net/p/equalizerapo/wiki/Configuration%20reference +IDList= +HotKey=0 diff --git a/Setup/Setup.nsi b/Setup/Setup.nsi index a05be2e..f723bbc 100644 --- a/Setup/Setup.nsi +++ b/Setup/Setup.nsi @@ -118,6 +118,7 @@ Section "Install" SecInstall File "${LIBPATH}\msvcr100.dll" File "Configuration tutorial (online).url" + File "Configuration reference (online).url" CreateDirectory "$INSTDIR\config" @@ -126,6 +127,8 @@ Section "Install" SecInstall File /oname=config\example.txt "config\example.txt" File /oname=config\demo.txt "config\demo.txt" File /oname=config\multichannel.txt "config\multichannel.txt" + File /oname=config\iir_lowpass.txt "config\iir_lowpass.txt" + File /oname=config\selective_delay.txt "config\selective_delay.txt" SetOverwrite on ;Grant write access to the config directory for all users @@ -141,6 +144,7 @@ Section "Install" SecInstall ;Create shortcuts CreateDirectory "$SMPROGRAMS\$StartMenuFolder" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration tutorial (online).lnk" "$INSTDIR\Configuration tutorial (online).url" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration reference (online).lnk" "$INSTDIR\Configuration reference (online).url" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configurator.lnk" "$INSTDIR\Configurator.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Benchmark.lnk" "$INSTDIR\Benchmark.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" @@ -184,6 +188,7 @@ Section "-un.Uninstall" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder RMDir /r "$SMPROGRAMS\$StartMenuFolder" + Delete "$INSTDIR\Configuration reference (online).url" Delete "$INSTDIR\Configuration tutorial (online).url" Delete /REBOOTOK "$INSTDIR\msvcr100.dll" diff --git a/Setup/config/iir_lowpass.txt b/Setup/config/iir_lowpass.txt new file mode 100644 index 0000000..f76a164 --- /dev/null +++ b/Setup/config/iir_lowpass.txt @@ -0,0 +1,18 @@ +# This shows how to create a lowpass biquad filter using the IIR filter type +# The result should be equivalent to the command +# Filter: ON LP Fc 3000 Hz + +Do: `freq = 3000` +Do: `Q = 1/sqrt(2)` +Do: `omega = 2 * pi * freq / sampleRate` +Do: `sn = sin(omega)` +Do: `cs = cos(omega)` +Do: `alpha = sin(omega) / (2 * Q)` +Do: `a0 = (1 - cs) / 2` +Do: `a1 = 1 - cs` +Do: `a2 = (1 - cs) / 2` +Do: `b0 = 1 + alpha` +Do: `b1 = -2 * cs` +Do: `b2 = 1 - alpha` + +Filter: ON IIR Order 2 Coefficients `a0` `a1` `a2` `b0` `b1` `b2` diff --git a/Setup/config/selective_delay.txt b/Setup/config/selective_delay.txt new file mode 100644 index 0000000..cf01740 --- /dev/null +++ b/Setup/config/selective_delay.txt @@ -0,0 +1,11 @@ +# Copy channels to virtual channels to filter them while keeping the originals +Copy: L2=L R2=R +# Select channels for filtering +Channel: L2 R2 +# Limit frequency range for delay +Filter: ON HP Fc 200 Hz +Filter: ON LP Fc 2000 Hz +# Apply delay +Delay: 500 ms +# Add the delayed signal back to the original channels +Copy: L=0.5*L+0.5*L2 R=0.5*R+0.5*R2 diff --git a/StringHelper.cpp b/StringHelper.cpp deleted file mode 100644 index 4a86989..0000000 --- a/StringHelper.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - This file is part of EqualizerAPO, a system-wide equalizer. - Copyright (C) 2013 Jonas Thedering - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include "StringHelper.h" - -using namespace std; - -wstring StringHelper::replaceCharacters(wstring s, wstring chars, wstring replacement) -{ - wstring result; - result.reserve(s.length()); - - for(unsigned i=0; i:\"/\\|?*", L"_"); -} - -wstring StringHelper::toWString(string s, unsigned codepage) -{ - int length = MultiByteToWideChar(codepage, 0, s.c_str(), -1, NULL, 0); - wchar_t* charBuf = new wchar_t[length]; - MultiByteToWideChar(codepage, 0, s.c_str(), -1, charBuf, length); - wstring result = charBuf; - delete charBuf; - - return result; -} - -wstring StringHelper::toLowerCase(wstring s) -{ - wchar_t* charBuf = new wchar_t[s.length() + 1]; - memcpy(charBuf, s.c_str(), (s.length() + 1) * sizeof(wchar_t)); - errno_t err = _wcslwr_s(charBuf, s.length() + 1); - - wstring result = charBuf; - delete charBuf; - - if(err == 0) - return result; - else - return s; -} diff --git a/Wiki/Configuration reference.txt b/Wiki/Configuration reference.txt new file mode 100644 index 0000000..5f094f8 --- /dev/null +++ b/Wiki/Configuration reference.txt @@ -0,0 +1,370 @@ +This page describes the configuration file format and the commands supported by Equalizer APO. This information is meant for advanced users. To get an introduction to using Equalizer APO or if you need troubleshooting go to the [user documentation](https://sourceforge.net/p/equalizerapo/wiki/). + +**Table of contents:** + +[TOC] + +# Configuration file format + +The configuration files of Equalizer APO are organized as lines of the following format: + + Command: Parameters + +All lines not conforming to this format are silently ignored, like the comment lines that start with # in the example below. +Lines that contain any command name not supported are also silently ignored. + +Example: + + :::perl + Device: High Definition Audio Device Speakers; Benchmark + #All lines below will only be applied to the specified device and the benchmark application + Preamp: -6 db + Include: example.txt + Filter 1: ON PK Fc 50 Hz Gain -3.0 dB Q 10.00 + Filter 2: ON PEQ Fc 100 Hz Gain 1.0 dB BW Oct 0.167 + + Channel: L + #Additional preamp for left channel + Preamp: -5 dB + #Filters only for left channel + Include: demo.txt + Filter 1: ON LS Fc 300 Hz Gain 5.0 dB + + Channel: 2 C + #Filters for second(right) and center channel + Filter 1: ON HP Fc 30 Hz + Filter 2: ON LPQ Fc 10000 Hz Q 0.400 + + Device: Microphone + #From here, the lines only apply to microphone devices + Filter: ON NO Fc 50 Hz + +
+# Filtering commands + +These commands directly change the audio on the selected channels. + +## Preamp +**Syntax:** +Preamp: <Negative number> dB + +**Description:** +Sets a preamplification value in decibels. This is useful when you are using filters with positive gain, to make sure that no clipping occurs. Since version 0.8, when multiple preamps apply to the same channel, the resulting preamp is the sum in dB. + +**Example:** + + :::perl + Preamp: -6.5 dB + +
+## Filter +**Syntax:** +Filter <n>: ON <Type> Fc <Frequency> Hz Gain <Gain value> dB Q <Q value> +Filter <n>: ON <Type> Fc <Frequency> Hz Gain <Gain value> dB BW Oct <Bandwidth value> + +**Description:** +Adds a filter of the specified type with the specified frequency, gain and Q / bandwidth. The supported filter types and their parameters are listed below. The first parameter variant (with Q) is the filter text format used by Room EQ Wizard for equalizer type "Generic" while the second variant (with bandwidth) is used for equalizer type "FBQ2496". The filter number (n) is not interpreted and can be omitted. + +The following table lists the filter types supported by Equalizer APO since version 0.8.1. The columns Fc, Gain and Q/BW show the parameters supported by the filter type. An 'X' means that the parameter is required while an 'O' denotes an optional parameter. The filter types supported cover all filters of the "Generic" and the "FBQ2496" equalizer type. Other equalizer types may also be compatible if their filter text format is. There is one exception however: The band-pass filter supported by Equalizer APO is a real band-pass filter that does not support gain, like low/high-pass filters, but unlike the DCX2496's "BP" filter, which is actually a peaking filter. + +Type | Description | Fc | Gain | Q/BW | Example for filter parameters +- | - | - | - | - | - +PK
Modal
PEQ | Peaking filter
(Parametric EQ) | X | X | X | ON PK Fc 50.0 Hz Gain -10.0 dB Q 2.50
ON Modal Fc 100 Hz Gain 3.0 dB Q 5.41 T60 target 100 ms
ON PEQ Fc 100 Hz Gain 1.0 dB BW Oct 0.167 +LP
LPQ | Low-pass filter | X | | O | ON LP Fc 8000 Hz
ON LPQ Fc 10000 Hz Q 0.400 +HP
HPQ | High-pass filter | X | | O | ON HP Fc 30.0 Hz
ON HPQ Fc 20.0 Hz Q 0.500 +BP | Band-pass filter
(not from DCX2496) | X | | O | ON BP Fc 1000 Hz Q 0.100 +LS
LSC x dB | Low-shelf filter
(with center freq.,
x dB per oct. (LSC)) | X | X | | ON LS Fc 300 Hz Gain 5.0 dB
ON LSC 10.8 dB Fc 300 Hz Gain 5.0 dB +HS
HSC x dB | High-shelf filter
(with center freq.,
x dB per oct. (HSC)) | X | X | | ON HS Fc 1000 Hz Gain -3.0 dB
ON HSC 6 dB Fc 100 Hz Gain -6.0 dB +LS 6dB
LS 12dB | Low-shelf filter
(6 / 12 dB per octave
with corner freq.) | X | X | | ON LS 6dB Fc 50.0 Hz Gain 7.2 dB
ON LS 12dB Fc 2000 Hz Gain -5.0 dB +HS 6dB
HS 12dB | High-shelf filter
(6 / 12 dB per octave
with corner freq.) | X | X | | ON HS 6dB Fc 12000 Hz Gain 10.0 dB
ON HS 12dB Fc 500 Hz Gain 5.0 dB +NO | Notch filter | X | | O | ON NO Fc 800 Hz +AP | All-pass filter | X | | X | ON AP Fc 900 Hz Q 0.707 + +
+## Filter with custom coefficients (since version 0.9) +**Syntax:** +Filter <n>: ON IIR Order <m> Coefficients <b0> <b1> ... <bm> <a0> <a1> ... <am> + +**Description:** +Adds a generic IIR filter with the given order and coefficients. The number of coefficients must be 2*(order+1). +The transfer function of the filter is + + + +As the filter coefficients are normally dependent on the sample rate, the If command or inline expressions should be used to supply the correct coefficients for the current sample rate. + +Although it is possible to use the IIR filter type to implement the same BiQuad filters that are supported by the other filter types, this is not advisable because the execution time will be higher. + +**Example:** + + :::perl + # A lowpass biquad filter with Fc 3000 Hz for the sample rate 48 kHz + Filter: ON IIR Order 2 Coefficients 0.0380602 0.0761205 0.0380602 1.2706 -1.84776 0.729402 + +
+## Delay (since version 0.9) +**Syntax:** +Delay: <t> ms +Delay: <n> samples + +**Description:** +Delays the audio on the selected channels by t milliseconds or n samples. Milliseconds should be preferred because they will give equal delay independent of the sample rate. + +**Example:** + + :::perl + # Delays the audio by 50.5 ms independent of sample rate + Delay: 50.5 ms + # Delays the audio by 480 samples (10 ms at 48 kHz) + Delay: 480 samples + +
+## Copy (since version 0.9) +**Syntax:** +Copy: <Target channel>=<Factor>*<Source channel>+... +Copy: <Target channel>=<Source channel>+... +Copy: <Target channel>=<Constant value>+... + +**Description:** +Replaces the audio on the target channel by the sum of the given source channels with optional factors. To add instead of replace the audio on the target channel, the target channel itself can also be a source channel. The factor can also be specified in dB by appending dB. Multiple channel assignments can be specified on a single line by separating them with spaces, therefore a single assignment must not contain spaces. Instead of channel and factor, a constant value can be specified. To avoid ambiguity with numerical channel indices, the constant value must contain a decimal point. + +**Example:** + + :::perl + # Adds the audio on channel R multiplied by 0.5 to channel L + Copy: L=L+0.5*R + # Replaces the audio on channel L by the audio on channel R + # plus the audio on channel C attenuated by 6 dB + Copy: L=R+-6dB*C + # Replaces the audio on the first channel by the audio previously on channel R + # Also sets the audio on channel R to the constant value 0.5 + Copy: 1=R R=0.5 + # Attention: Sets the audio on channel L to the audio on the second channel + # (not to constant value 2, because no decimal point is present) + Copy: L=2 + +
+# Control commands +These command do not directly affect the audio but control which commands are executed or how they affect the audio. + +## Include +**Syntax:** +Include: <File name> + +**Description:** +Loads the given file as a configuration file. Instead of directly replacing config.txt, it can be better to load the actual filter definition from a separate file so that you can e.g. set a preamp before. + +**Example:** + + :::perl + Include: example.txt + +
+## Device (since version 0.7) +**Syntax:** +Device: <Device pattern 1>; <Device pattern 2>; ... + +**Description:** +Matches the given pattern to the connection name, device name and GUID of the current output device. If the pattern does not match, all following commands except Device commands are ignored. The pattern consists of words separated by space that must all be found in the string "***Device_name*** ***Connection_name*** ***GUID***". Multiple patterns of which one must match can be specified by separating the patterns with ';'. The special pattern "all" matches always. The benchmark application uses the device name "Benchmark" and connection name "File output". The easiest way to generate a Device command is to use the button in the Configurator application. + +**Example:** + + :::perl + # Matches the device "High Definition Audio Device" with connection "Speakers" + # and also the device "Benchmark" used by the benchmark application + Device: High Definition Audio Device Speakers; Benchmark + +
+## Channel (since version 0.8) +**Syntax:** +Channel: <Channel position 1> <Channel position 2> ... + +**Description:** +Selects the channels to which the following Filter and Preamp commands will be applied. Channel positions can be given by identifier (acronym of 1 up to 3 characters) or by number (counted from 1). The supported channel configurations are listed below. If a channel configuration is not supported, channels can only be selected by number. Multiple channels can be specified by separating with space. The special position "all" selects all channels. + +**Channel configurations:** +The following table lists the channel configurations that are supported by Equalizer APO. The channel identifiers in the column titles resemble the channels in Start -> Control Panel -> Sound -> Configure. If a channel position is supported for a configuration, the cell in the corresponding row contains the channel number. + +Configuration | Left
L | Right
R | Center
C | Subwoofer
SUB | Rear left
RL | Rear right
RR | Rear center
RC | Side left
SL | Side right
SR +- | - | - | - | - | - | - | - | - | - +Mono | | | 1 | | | | | | +Stereo | 1 | 2 | | | | | | | +Quadraphonic | 1 | 2 | | | 3 | 4 | | | +Surround | 1 | 2 | 3 | | | | 4 | | +5.1 Surround | 1 | 2 | 3 | 4 | | | | 5 | 6 +5.1 Surround | 1 | 2 | 3 | 4 | 5 | 6 | | | +7.1 Surround | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 + +
+ +Attention:
+ +Although it might be tempting to assign low frequency filters only to the subwoofer channel, this might not lead to the expected results. Many audio systems apply bass redirection after Equalizer APO has processed the signal, so filters applied to the subwoofer channel won't be effective for redirected sound. Furthermore, as crossover filters only gradually fade in, the low frequency audio might be played over multiple speakers, not just the subwoofer, reducing the effectivity of subwoofer-only filters. Therefore, to avoid these problems, it's recommended to apply low frequency filters to all channels. +
+
+**Example:** + + :::perl + # Selects the left channel and the rear left channel + Channel: L RL + # Selects the first, second and center channel + Channel: 1 2 C + +
+## Stage (since version 0.9) +**Syntax:** +Stage: <stage 1> <stage 2> + +**Description:** +Specifies on which stage(s) the following filtering commands will be executed. For output devices, there are two stages: pre-mix and post-mix as illustrated in the following (simplified) diagram: + +For input devices, there is only the capture stage. Initially, the selected stages are post-mix and capture, so that the filtering commands are executed exactly once for output and input devices. The post-mix stage is preferred to the pre-mix stage because the filtering happens per-application in the pre-mix stage and is therefore more cpu-intensive. However, for specific purposes such as upmixing the pre-mix stage has to be used as it can do filtering based on the number of input channels. + +**Example:** + + :::perl + Stage: pre-mix + # do upmixing + If: inputChannelCount == 2 + # note that there may be audio on SL, SR from another APO + Copy: SL=SL+L SR=SR+R + EndIf: + # ... + Stage: post-mix + # do room correction + # ... + +
+# Expression commands (since version 0.9) +These commands use expressions to alter the processing behaviour based on runtime variables. Expressions are a tiny language embedded in the configuration file language with a syntax that is closer to scripting languages. The language consists of constants, variables, operators and functions. + +**Constants:** + +Name | Description +- | - +e, pi | Mathematical constants +inputChannelCount | Number of channels that are input to the current APO stage +outputChannelCount | Number of channels that are output from the current APO stage +sampleRate | Sample rate (in Hz) of the audio in process +deviceName | Name of the audio device +connectionName | Name of the connection on the audio device +deviceGuid | GUID of the audio device endpoint +stage | Current APO stage (can be pre-mix, post-mix or capture) + +**Variables:** +User-defined variables that are valid while loading the configuration can be defined using the assignment operator (=). The variables will not be retained between configuration reloads, they are just meant to carry temporary values that will be used later in the configuration file. + +**Operators:** + +Name | Description +- | - ++, -, *, /, ^ | Arithmetic operators +=, +=, -=, *=, /= | Assignment operators +and, or, xor | Logical operators +==, !=, <, >, <=, >= | Comparison operators +&, |, <<, >> | Bit-wise / bit-shift operators ++ | String concatenation (at least one argument must be a string) +(float), (int) | Type conversion (from numerical type) +condition ? then : else | Conditional operator (ternary if-then-else) +{1,2} | Array creation +array\[0\] | Array access + +**Functions:** + +Name | Description +- | - +abs | Absolute value +sin, cos, tan,
sinh, cosh, tanh | Trigonometric functions +ln, log, log10, exp | Logarithmic / exponential functions +sqrt | Square root +min, max, sum | Minimum / maximum / sum of arguments +str2dbl | Conversion from string to float value +strlen | Length of string +tolower, toupper | Conversion to lower / upper case +sizeof | Length of array +regexSearch | Searches first match in string (second argument) to regular expression (first argument). Result is empty if not matching. If matching, result is array with whole match as first value and capturing groups as further values. +regexReplace | Replaces all matches in string (second argument) to regular expression (first argument) with string (third argument). Result is string with replacements. +readRegString | Reads value (second argument) from specified key (first argument) in the registry. Value must be of type string (REG_SZ). Automatically monitors the registry key for changes and initiates a configuration reload on any value change. +readRegDWORD | Reads value (second argument) from specified key (first argument) in the registry. Value must be of type integer (REG_DWORD). Automatically monitors the registry key for changes and initiates a configuration reload on any value change. + +The types supported in expressions are string, boolean, int, float and matrix/array. In error messages, these types are abbreviated to 's', 'b', 'i', 'f' and 'm'. String constants are specified in quotes ("). Backslashes (\\) and quotes need to be escaped by prefixing them with a backslash. Boolean constants are specified as true or false. Integer and float constants are specified without thousands separators and using point (.) as the decimal separator in case of float. Array constants are specified using the {} operator. + +Multiple expressions can be combined into one expression by separating them with semicolon (;). The result will be the value of the last expression. + +**Examples:** + + :::perl + # User-defined variables + Eval: a=0; b=pi + # Comparison + Eval: a > b ? "a is larger" : "b is larger" + # Trigonometric functions and exponentiation operator + Eval: sin(a)^2 + cos(a)^2 == 1 + # Matching device name with regular expression + Eval: a=regexSearch("High Definition .*", deviceName); sizeof(a) > 0 ? "found" : "not found" + # Reading configuration path from registry + Eval: readRegString("HKEY_LOCAL_MACHINE\\SOFTWARE\\EqualizerAPO", "ConfigPath") + +
+## If / ElseIf / Else / EndIf +**Syntax:** +If: <expression> +ElseIf: <expression> +Else: +EndIf: + +**Description:** +Conditionally executes the commands between If and EndIf. This resembles the if/else conditional statements found in most programming languages. + +When the command If is encountered, its expression is evaluated and converted to a boolean value. If the result is true, the following commands are executed, otherwise they are skipped. + +When an ElseIf command is encountered after an If or ElseIf command which evaluated to true, the following commands until EndIf are skipped. If the previous If or ElseIf command evaluated to false, the expression of the ElseIf command is evaluated and the following commands are executed if the expression evaluated to true. + +The Else command behaves as an ElseIf command whose expression always evaluates to true. + +The EndIf command ends the conditional execution. + +Conditional execution can be nested, so between If and EndIf can be another If statement. ElseIf and Else always refer to the latest If which was not ended by EndIf. To improve the readability, lines can be indented by inserting space or tab characters at the beginning. + +For technical reasons, If statements can not be used to conditionally execute Device statements as Device statements have a higher priority. + +**Example:** + + :::perl + # Executes statements according to the current sample rate and channel count + If: sampleRate == 44100 + ... + ElseIf: sampleRate == 48000 + ... + If: inputChannelCount == 1 + ... + ElseIf: inputChannelCount == 2 + ... + ElseIf: inputChannelCount == 6 + ... + EndIf: + Else: + ... + EndIf: + +
+## Eval and inline expressions +**Syntax:** +Eval: <expression> +<Command>: ... \`<expression>\` ... + +**Description:** +The Eval command just evaluates the expression without using its result any further. It is mainly useful to define variables or for testing purposes. + +Inline expressions are used to embed the result of an expression into the parameter string of a command. The result of the expression is converted into a string and replaces the inline expression from the first backtick(`) to the second backtick, inclusive. + +For technical reasons, inline expressions cannot be used in Device and If/ElseIf commands, but they can be used in Eval commands (to evaluate a calculated expression). + +**Example:** + + :::perl + # Specify gain linearly + Eval: linGain = 0.5 + Filter: ON PK Fc 1000 Hz Gain `20*log10(linGain)` dB Q 10.0 + +
diff --git a/Wiki/Documentation.txt b/Wiki/Documentation.txt index 716992b..52c3c49 100644 --- a/Wiki/Documentation.txt +++ b/Wiki/Documentation.txt @@ -1,4 +1,4 @@ -Welcome to the Wiki of Equalizer APO. This is the documentation for users of Equalizer APO. Developers might also be interested in reading the [developer documentation](https://sourceforge.net/p/equalizerapo/wiki/Developer%20documentation/). To begin using Equalizer APO, you should read the tutorials. +Welcome to the Wiki of Equalizer APO. This is the documentation for users of Equalizer APO. Developers might also be interested in reading the [developer documentation](https://sourceforge.net/p/equalizerapo/wiki/Developer%20documentation/). To begin using Equalizer APO, you should read the tutorials. After that, you can look at more detailed information in the [configuration reference](https://sourceforge.net/p/equalizerapo/wiki/Configuration%20reference/). **Table of contents:** @@ -36,90 +36,7 @@ A detailed explanation of the usage of Room EQ Wizard is out of the scope of thi Congratulations, you have now created your first configuration for Equalizer APO. To learn more about the usage of RoomEQWizard, you can look into its [help file](http://www.hometheatershack.com/roomeq/wizardhelpv5/help_en-GB/html/index.html). The process can even be automated to some extent, as is explained in this [forum thread](/p/equalizerapo/discussion/general/thread/3ba39cad/). # Configuration file format - -The configuration files of Equalizer APO are organized as lines of the following format: - - Command: Parameters - -All lines not conforming to this format are silently ignored, like the comment lines that start with # in the example below. - -Example: - - Device: High Definition Audio Device Speakers; Benchmark - #All lines below will only be applied to the specified device and the benchmark application - Preamp: -6 db - Include: example.txt - Filter 1: ON PK Fc 50 Hz Gain -3.0 dB Q 10.00 - Filter 2: ON PEQ Fc 100 Hz Gain 1.0 dB BW Oct 0.167 - - Channel: L - #Additional preamp for left channel - Preamp: -5 dB - #Filters only for left channel - Include: demo.txt - Filter 1: ON LS Fc 300 Hz Gain 5.0 dB - - Channel: 2 C - #Filters for second(right) and center channel - Filter 1: ON HP Fc 30 Hz - Filter 2: ON LPQ Fc 10000 Hz Q 0.400 - - Device: Microphone - #From here, the lines only apply to microphone devices - Filter: ON NO Fc 50 Hz - -The commands currently defined are: - -Command | Parameters | Description --|-|- -Preamp | ***Negative number*** dB | Sets a preamplification value in decibels. This is useful when you are using filters with positive gain, to make sure that no clipping occurs. Since version 0.8, when multiple preamps apply to the same channel, the resulting preamp is the sum in dB. -Include | ***File name*** | Loads the given file as a configuration file. Instead of directly replacing config.txt, it can be better to load the actual filter definition from a separate file so that you can e.g. set a preamp before. -Filter ***n*** | ON ***Type*** Fc ***Frequency*** Hz Gain ***Gain value*** dB Q ***Q value*** | Adds a filter of the given type with the given frequency, gain and Q. The supported filter types and their parameters are listed below. This is the filter text format used by Room EQ Wizard for equalizer type "Generic". The filter number (n) is not interpreted and can be omitted. -Filter ***n*** | ON ***Type*** Fc ***Frequency*** Hz Gain ***Gain value*** dB BW Oct ***Bandwidth value*** | Adds a filter of the given type with the given frequency, gain and bandwidth. The supported filter types and their parameters are listed below. This is the filter text format used by Room EQ Wizard for equalizer type "FBQ2496". The filter number (n) is not interpreted and can be omitted. -Device | ***Device identification pattern*** | Matches the given pattern to the connection name, device name and GUID of the current output device. If the pattern does not match, all following commands except Device commands are ignored. The pattern consists of words separated by space that must all be found in the string "***Device_name*** ***Connection_name*** ***GUID***". Multiple patterns of which one must match can be specified by separating the patterns with ';'. The special pattern "all" matches always. The benchmark application uses the device name "Benchmark" and connection name "File output". The easiest way to generate a Device command is to use the button in the Configurator application. (added in version 0.7) -Channel | *** Channel positions *** | Selects the channels to which the following Filter and Preamp commands will be applied. Channel positions can be given by identifier (acronym of 1 up to 3 characters) or by number (counted from 1). The supported channel configurations are listed below. If a channel configuration is not supported, channels can only be selected by number. Multiple channels can be specified by separating with space. The special position "all" selects all channels. (added in version 0.8) - -Lines that contain any command name not in this list are silently ignored. - - -# Filter types - -The following table lists the filter types supported by Equalizer APO since version 0.8.1. The columns Fc, Gain and Q/BW show the parameters supported by the filter type. An 'X' means that the parameter is required while an 'O' denotes an optional parameter. The filter types supported cover all filters of the "Generic" and the "FBQ2496" equalizer type. Other equalizer types may also be compatible if their filter text format is. There is one exception however: The band-pass filter supported by Equalizer APO is a real band-pass filter that does not support gain, like low/high-pass filters, but unlike the DCX2496's "BP" filter, which is actually a peaking filter. - -Type | Description | Fc | Gain | Q/BW | Example for filter parameters -- | - | - | - | - | - -PK
Modal
PEQ | Peaking filter
(Parametric EQ) | X | X | X | ON PK Fc 50.0 Hz Gain -10.0 dB Q 2.50
ON Modal Fc 100 Hz Gain 3.0 dB Q 5.41 T60 target 100 ms
ON PEQ Fc 100 Hz Gain 1.0 dB BW Oct 0.167 -LP
LPQ | Low-pass filter | X | | O | ON LP Fc 8000 Hz
ON LPQ Fc 10000 Hz Q 0.400 -HP
HPQ | High-pass filter | X | | O | ON HP Fc 30.0 Hz
ON HPQ Fc 20.0 Hz Q 0.500 -BP | Band-pass filter
(not from DCX2496) | X | | O | ON BP Fc 1000 Hz Q 0.100 -LS
LSC x dB | Low-shelf filter
(with center freq.,
x dB per oct. (LSC)) | X | X | | ON LS Fc 300 Hz Gain 5.0 dB
ON LSC 10.8 dB Fc 300 Hz Gain 5.0 dB -HS
HSC x dB | High-shelf filter
(with center freq.,
x dB per oct. (HSC)) | X | X | | ON HS Fc 1000 Hz Gain -3.0 dB
ON HSC 6 dB Fc 100 Hz Gain -6.0 dB -LS 6dB
LS 12dB | Low-shelf filter
(6 / 12 dB per octave
with corner freq.) | X | X | | ON LS 6dB Fc 50.0 Hz Gain 7.2 dB
ON LS 12dB Fc 2000 Hz Gain -5.0 dB -HS 6dB
HS 12dB | High-shelf filter
(6 / 12 dB per octave
with corner freq.) | X | X | | ON HS 6dB Fc 12000 Hz Gain 10.0 dB
ON HS 12dB Fc 500 Hz Gain 5.0 dB -NO | Notch filter | X | | O | ON NO Fc 800 Hz -AP | All-pass filter | X | | X | ON AP Fc 900 Hz Q 0.707 - - -# Channel configurations - -The following table lists the channel configurations that are supported by Equalizer APO. The channel identifiers in the column titles resemble the channels in Start -> Control Panel -> Sound -> Configure. If a channel position is supported for a configuration, the cell in the corresponding row contains the channel number. - -Configuration | Left
L | Right
R | Center
C | Subwoofer
SUB | Rear left
RL | Rear right
RR | Rear center
RC | Side left
SL | Side right
SR -- | - | - | - | - | - | - | - | - | - -Mono | | | 1 | | | | | | -Stereo | 1 | 2 | | | | | | | -Quadraphonic | 1 | 2 | | | 3 | 4 | | | -Surround | 1 | 2 | 3 | | | | 4 | | -5.1 Surround | 1 | 2 | 3 | 4 | | | | 5 | 6 -5.1 Surround | 1 | 2 | 3 | 4 | 5 | 6 | | | -7.1 Surround | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 - -
- -Attention:
- -Although it might be tempting to assign low frequency filters only to the subwoofer channel, this might not lead to the expected results. Many audio systems apply bass redirection after Equalizer APO has processed the signal, so filters applied to the subwoofer channel won't be effective for redirected sound. Furthermore, as crossover filters only gradually fade in, the low frequency audio might be played over multiple speakers, not just the subwoofer, reducing the effectivity of subwoofer-only filters. Therefore, to avoid these problems, it's recommended to apply low frequency filters to all channels. -
+This information has been moved to the [configuration reference](https://sourceforge.net/p/equalizerapo/wiki/Configuration%20reference/). # Troubleshooting diff --git a/Wiki/Stages.odg b/Wiki/Stages.odg new file mode 100644 index 0000000000000000000000000000000000000000..e70a3dfd4c67c9a3f1e12ef9f0cbeef12bdb7496 GIT binary patch literal 22653 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 literal 0 HcmV?d00001 diff --git a/Wiki/Stages.png b/Wiki/Stages.png new file mode 100644 index 0000000000000000000000000000000000000000..6aeae2f863e98e9670f1889a73a7819ef60fcae3 GIT binary patch literal 23495 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

t#=9m#uYo( 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�V%RCaYYZ2 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Application + + + + + + + APO + + + + + + + + + + + + + + + Application + + + + + + + APO + + + + + + + Application + + + + + + + APO + + + + + + + APO + + + + + + + Outputdevice + + + + + + + 2 + + + + + + + 2 + + + + + + + 6 + + + + + + + 6 + + + + + + + 6 + + + + + + + 6 + + + + + + + 6 + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pre-mixstage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + post-mixstage + + + + + + + number of channels + + + + + + + \ No newline at end of file diff --git a/Wiki/Transfer function.odf b/Wiki/Transfer function.odf new file mode 100644 index 0000000000000000000000000000000000000000..af91818b9a42af9a977692b348d0e918d1df1210 GIT binary patch literal 9275 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 @@ -6,18 +24,18 @@ using namespace std; -BiQuad::BiQuad(Type type, float dbGain, float freq, float srate, float bandwidthOrQOrS, bool isBandwidth) +BiQuad::BiQuad(Type type, double dbGain, double freq, double srate, double bandwidthOrQOrS, bool isBandwidth) { - float A; + double A; if(type == PEAKING || type == LOW_SHELF || type == HIGH_SHELF) A = pow(10, dbGain / 40); else A = pow(10, dbGain / 20); - float omega = 2 * (float)M_PI * freq / srate; - float sn = sin(omega); - float cs = cos(omega); - float alpha; - float beta = -1; + double omega = 2 * M_PI * freq / srate; + double sn = sin(omega); + double cs = cos(omega); + double alpha; + double beta = -1; if (type == LOW_SHELF || type == HIGH_SHELF) // S { @@ -25,11 +43,11 @@ BiQuad::BiQuad(Type type, float dbGain, float freq, float srate, float bandwidth beta = 2 * sqrt(A) * alpha; } else if(isBandwidth) // BW - alpha = sn * sinh((float)M_LN2/2 * bandwidthOrQOrS * omega / sn); + alpha = sn * sinh(M_LN2/2 * bandwidthOrQOrS * omega / sn); else // Q alpha = sn / (2 * bandwidthOrQOrS); - - float b0, b1, b2, a0, a1, a2; + + double b0, b1, b2, a0, a1, a2; switch(type) { @@ -99,11 +117,11 @@ BiQuad::BiQuad(Type type, float dbGain, float freq, float srate, float bandwidth break; } - this->a0 = b0 /a0; - this->a[0] = b1 /a0; - this->a[1] = b2 /a0; - this->a[2] = -a1 /a0; - this->a[3] = -a2 /a0; + this->a0 = float(b0 / a0); + this->a[0] = float(b1 / a0); + this->a[1] = float(b2 / a0); + this->a[2] = float(a1 / a0); + this->a[3] = float(a2 / a0); x1 = 0; x2 = 0; diff --git a/BiQuad.h b/filters/BiQuad.h similarity index 81% rename from BiQuad.h rename to filters/BiQuad.h index 3951fe7..a9fdf04 100644 --- a/BiQuad.h +++ b/filters/BiQuad.h @@ -17,6 +17,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#pragma once + #include #define IS_DENORMAL(f) (((*(unsigned int *)&(f))&0x7f800000) == 0) @@ -30,7 +32,7 @@ public: }; BiQuad() {} - BiQuad(Type type, float dbGain, float freq, float srate, float bandwidthOrQOrS, bool isBandwidth); + BiQuad(Type type, double dbGain, double freq, double srate, double bandwidthOrQOrS, bool isBandwidth); __forceinline void removeDenormals() @@ -48,8 +50,8 @@ public: __forceinline float process(float sample) { - float result = a0 * sample + a[0] * x1 + a[1] * x2 + - a[2] * y1 + a[3] * y2; + // changed order of additions leads to better pipelining + float result = a0 * sample + a[1] * x2 + a[0] * x1 - a[3] * y2 - a[2] * y1; x2 = x1; x1 = sample; @@ -57,7 +59,6 @@ public: y2 = y1; y1 = result; - //Input for next stage return result; } diff --git a/filters/BiQuadFilter.cpp b/filters/BiQuadFilter.cpp new file mode 100644 index 0000000..ab83b26 --- /dev/null +++ b/filters/BiQuadFilter.cpp @@ -0,0 +1,69 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/MemoryHelper.h" +#include "BiQuadFilter.h" + +using namespace std; + +BiQuadFilter::BiQuadFilter(BiQuad::Type type, double dbGain, double freq, double bandwidthOrQOrS, bool isBandwidth) + :type(type), dbGain(dbGain), freq(freq), bandwidthOrQOrS(bandwidthOrQOrS), isBandwidth(isBandwidth) +{ +} + +BiQuadFilter::~BiQuadFilter() +{ + if(biquads != NULL) + { + MemoryHelper::free(biquads); + biquads = NULL; + } +} + +vector BiQuadFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + this->channelCount = channelNames.size(); + biquads = (BiQuad*)MemoryHelper::alloc(channelCount * sizeof(BiQuad)); + + for(unsigned i=0; i initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + BiQuad::Type type; + double dbGain; + double freq; + double bandwidthOrQOrS; + bool isBandwidth; + + size_t channelCount; + BiQuad* biquads; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/BiQuadFilterFactory.cpp b/filters/BiQuadFilterFactory.cpp new file mode 100644 index 0000000..25ce8cf --- /dev/null +++ b/filters/BiQuadFilterFactory.cpp @@ -0,0 +1,248 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2014 Jonas Thedering + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define _USE_MATH_DEFINES +#include +#include +#include + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "BiQuadFilter.h" +#include "BiQuadFilterFactory.h" + +using namespace std; + +static wregex regexType(L"^\\s*ON\\s+([A-Za-z]+)"); +static wregex regexFreq(L"\\s+Fc\\s*([-+0-9.eE\u00A0]+)\\s*H\\s*z"); +static wregex regexGain(L"\\s+Gain\\s*([-+0-9.eE]+)\\s*dB"); +static wregex regexQ(L"\\s+Q\\s*([-+0-9.eE]+)"); +static wregex regexBW(L"\\s+BW\\s+Oct\\s*([-+0-9.eE]+)"); +static wregex regexSlope(L"^\\s*([-+0-9.eE]+)\\s*dB"); + +BiQuadFilterFactory::BiQuadFilterFactory() +{ + filterNameToTypeMap[L"PK"] = BiQuad::PEAKING; + filterNameToTypeMap[L"PEQ"] = BiQuad::PEAKING; + filterNameToTypeMap[L"Modal"] = BiQuad::PEAKING; + filterNameToTypeMap[L"LP"] = BiQuad::LOW_PASS; + filterNameToTypeMap[L"HP"] = BiQuad::HIGH_PASS; + filterNameToTypeMap[L"LPQ"] = BiQuad::LOW_PASS; + filterNameToTypeMap[L"HPQ"] = BiQuad::HIGH_PASS; + filterNameToTypeMap[L"BP"] = BiQuad::BAND_PASS; + filterNameToTypeMap[L"LS"] = BiQuad::LOW_SHELF; + filterNameToTypeMap[L"HS"] = BiQuad::HIGH_SHELF; + filterNameToTypeMap[L"LSC"] = BiQuad::LOW_SHELF; + filterNameToTypeMap[L"HSC"] = BiQuad::HIGH_SHELF; + filterNameToTypeMap[L"NO"] = BiQuad::NOTCH; + filterNameToTypeMap[L"AP"] = BiQuad::ALL_PASS; + + filterTypeToDescriptionMap[BiQuad::PEAKING] = L"peaking"; + filterTypeToDescriptionMap[BiQuad::LOW_PASS] = L"low-pass"; + filterTypeToDescriptionMap[BiQuad::HIGH_PASS] = L"high-pass"; + filterTypeToDescriptionMap[BiQuad::BAND_PASS] = L"band-pass"; + filterTypeToDescriptionMap[BiQuad::LOW_SHELF] = L"low-shelf"; + filterTypeToDescriptionMap[BiQuad::HIGH_SHELF] = L"high-shelf"; + filterTypeToDescriptionMap[BiQuad::NOTCH] = L"notch"; + filterTypeToDescriptionMap[BiQuad::ALL_PASS] = L"all-pass"; +} + +vector BiQuadFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + BiQuadFilter* filter = NULL; + + if(command.find(L"Filter") == 0) + { + //Conversion to period as decimal mark, if needed + parameters = StringHelper::replaceCharacters(parameters, L",", L"."); + + wsmatch match; + wstring typeString; + + bool found = regex_search(parameters, match, regexType); + if(found) + { + typeString = match.str(1); + if(filterNameToTypeMap.find(typeString) != filterNameToTypeMap.end()) + { + BiQuad::Type type = filterNameToTypeMap[typeString]; + wstring typeDescription = filterTypeToDescriptionMap[type]; + parameters = match.suffix().str(); + + wstringstream stream; + stream << L"Adding " << typeDescription << L" filter"; + + double freq = 0; + double gain = 0; + double bandwidthOrQOrS = 0; + bool isBandwidth = false; + bool error = false; + + found = regex_search(parameters, match, regexFreq); + if(found) + { + wstring freqString = match.str(1); + freq = getFreq(freqString); + stream << " with frequency " << freq << " Hz"; + } + else + { + LogF(L"No frequency given in filter string %s%s", typeString.c_str(), parameters.c_str()); + error = true; + } + + found = regex_search(parameters, match, regexGain); + if(found) + { + if(type == BiQuad::LOW_PASS || type == BiQuad::HIGH_PASS || type == BiQuad::NOTCH || type == BiQuad::ALL_PASS) + TraceF(L"Ignoring gain for filter of type %s", typeDescription.c_str()); + else + { + wstring gainString = match.str(1); + gain = wcstod(gainString.c_str(), NULL); + if(type == BiQuad::PEAKING) + stream << ", gain " << gain << " dB"; + else + stream << " and gain " << gain << " dB"; + } + } + else if(type == BiQuad::PEAKING || type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) + { + LogF(L"No gain given in filter string %s%s", typeString.c_str(), parameters.c_str()); + error = true; + } + + found = regex_search(parameters, match, regexQ); + if(found) + { + if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) + TraceF(L"Ignoring Q for filter of type %s", typeDescription.c_str()); + else + { + wstring qString = match.str(1); + bandwidthOrQOrS = wcstod(qString.c_str(), NULL); + stream << " and Q " << bandwidthOrQOrS; + } + } + + found = regex_search(parameters, match, regexBW); + if(found) + { + if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) + TraceF(L"Ignoring bandwidth for filter of type %s", typeDescription.c_str()); + else + { + wstring bwString = match.str(1); + bandwidthOrQOrS = wcstod(bwString.c_str(), NULL); + isBandwidth = true; + stream << " and bandwidth " << bandwidthOrQOrS << " octaves"; + } + } + + found = regex_search(parameters, match, regexSlope); + if(found) + { + if(!(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF)) + TraceF(L"Ignoring slope for filter of type %s", typeDescription.c_str()); + else + { + wstring slopeString = match.str(1); + bandwidthOrQOrS = wcstod(slopeString.c_str(), NULL); + stream << " and slope " << bandwidthOrQOrS << " dB"; + } + } + + if(bandwidthOrQOrS == 0) + { + if(type == BiQuad::PEAKING || type == BiQuad::ALL_PASS) + { + LogF(L"No Q or bandwidth given in filter string %s%s", typeString.c_str(), parameters.c_str()); + error = true; + } + else if(type == BiQuad::LOW_PASS || type == BiQuad::HIGH_PASS || type == BiQuad::BAND_PASS) + { + bandwidthOrQOrS = M_SQRT1_2; + } + else if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) + { + bandwidthOrQOrS = 0.9; // found out by experimentation with RoomEQWizard + } + else if(type == BiQuad::NOTCH) + { + bandwidthOrQOrS = 30.0; // found out by experimentation with RoomEQWizard + } + } + else if(type == BiQuad::LOW_SHELF || type == BiQuad::HIGH_SHELF) + { + // Maximum S is 1 for 12 dB + bandwidthOrQOrS /= 12.0; + if(typeString[typeString.length()-1] != L'C') + { + // frequency adjustment for DCX2496 + double centerFreqFactor = pow(10.0, abs(gain) / 80.0 / bandwidthOrQOrS); + if(type == BiQuad::LOW_SHELF) + freq *= centerFreqFactor; + else + freq /= centerFreqFactor; + } + } + + if(!error) + { + TraceF(L"%s", stream.str().c_str()); + + void* mem = MemoryHelper::alloc(sizeof(BiQuadFilter)); + filter = new(mem) BiQuadFilter(type, gain, freq, bandwidthOrQOrS, isBandwidth); + } + } + else if(typeString != L"None") + { + LogF(L"Invalid filter type %s", typeString.c_str()); + } + } + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} + +double BiQuadFilterFactory::getFreq(const wstring& freqString) +{ + double result; + // remove thousand's separator for locales utilizing non-breaking space + wstring s = StringHelper::replaceCharacters(freqString, L"\u00A0", L""); + int matched = swscanf_s(s.c_str(), L"%lf", &result); + if(matched == 1) + { + if(s.length() >= 5 && s.find_first_of(L"eE") == wstring::npos) + { + if(s[s.length() - 4] == L'.') + { + //Interpret as thousands separator because of Room EQ Wizard + result *= 1000.0; + } + } + + return result; + } + else + return -1.0; +} \ No newline at end of file diff --git a/filters/BiQuadFilterFactory.h b/filters/BiQuadFilterFactory.h new file mode 100644 index 0000000..538f73e --- /dev/null +++ b/filters/BiQuadFilterFactory.h @@ -0,0 +1,40 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "IFilterFactory.h" +#include "IFilter.h" +#include "BiQuad.h" + +class BiQuadFilterFactory : public IFilterFactory +{ +public: + BiQuadFilterFactory(); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + +private: + double getFreq(const std::wstring& freqString); + + stdext::hash_map filterNameToTypeMap; + stdext::hash_map filterTypeToDescriptionMap; +}; diff --git a/filters/ChannelFilter.cpp b/filters/ChannelFilter.cpp new file mode 100644 index 0000000..ec24bdc --- /dev/null +++ b/filters/ChannelFilter.cpp @@ -0,0 +1,88 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include + +#include "helpers/MemoryHelper.h" +#include "helpers/LogHelper.h" +#include "ChannelFilter.h" + +using namespace std; + +ChannelFilter::ChannelFilter(const vector& words) + :words(words) +{ +} + +ChannelFilter::~ChannelFilter() +{ +} + +vector ChannelFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + size_t channelCount = channelNames.size(); + vector selectedChannels = vector(channelCount, false); + + for(vector::iterator it = words.begin(); it != words.end(); it++) + { + wstring currentWord = *it; + int channelNr = -1; + + if(currentWord == L"ALL") + selectedChannels = vector(channelCount, true); + else + channelNr = getChannelIndex(currentWord, channelNames); + + if(channelNr != -1 && channelNr < (int)channelCount) + { + selectedChannels[channelNr] = true; + } + } + + vector selectedChannelNames; + for(unsigned i=0; i 0) + channelNumbers << L", "; + + channelNumbers << c+1; + } + } + + TraceF(L"Selecting channel(s) number %s", channelNumbers.str().c_str()); + + return selectedChannelNames; +} + +#pragma AVRT_CODE_BEGIN +void ChannelFilter::process(float** output, float** input, unsigned frameCount) +{ + // nothing to do +} +#pragma AVRT_CODE_END diff --git a/filters/ChannelFilter.h b/filters/ChannelFilter.h new file mode 100644 index 0000000..8abe6e5 --- /dev/null +++ b/filters/ChannelFilter.h @@ -0,0 +1,41 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "IFilter.h" + +#pragma AVRT_VTABLES_BEGIN +class ChannelFilter : public IFilter +{ +public: + ChannelFilter(const std::vector& words); + virtual ~ChannelFilter(); + virtual bool getAllChannels() {return true;} + virtual bool getSelectChannels() {return true;} + virtual std::vector initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + std::vector words; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/ChannelFilterFactory.cpp b/filters/ChannelFilterFactory.cpp new file mode 100644 index 0000000..5683fc8 --- /dev/null +++ b/filters/ChannelFilterFactory.cpp @@ -0,0 +1,64 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2014 Jonas Thedering + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "ChannelFilter.h" +#include "ChannelFilterFactory.h" + +using namespace std; + +vector ChannelFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + ChannelFilter* filter = NULL; + + if(command == L"Channel") + { + wstring value = parameters + L" "; + + vector words; + wstring currentWord; + for(unsigned i=0; i 0) + { + words.push_back(currentWord); + + currentWord.clear(); + } + } + else + { + currentWord += c; + } + } + + void* mem = MemoryHelper::alloc(sizeof(ChannelFilter)); + filter = new(mem) ChannelFilter(words); + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} \ No newline at end of file diff --git a/filters/ChannelFilterFactory.h b/filters/ChannelFilterFactory.h new file mode 100644 index 0000000..af5d14a --- /dev/null +++ b/filters/ChannelFilterFactory.h @@ -0,0 +1,31 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class ChannelFilterFactory : public IFilterFactory +{ +public: + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/CopyFilter.cpp b/filters/CopyFilter.cpp new file mode 100644 index 0000000..f2c5b2a --- /dev/null +++ b/filters/CopyFilter.cpp @@ -0,0 +1,167 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include + +#include "helpers/LogHelper.h" +#include "helpers/MemoryHelper.h" +#include "CopyFilter.h" + +using namespace std; + +CopyFilter::CopyFilter(const vector& assignments) +{ + this->assignments = assignments; + + internalAssignments = NULL; + assignmentCount = 0; +} + +CopyFilter::~CopyFilter() +{ + cleanup(); +} + +vector CopyFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + cleanup(); + + assignmentCount = (unsigned)assignments.size(); + internalAssignments = (InternalAssignment*)MemoryHelper::alloc(assignmentCount * sizeof(Assignment)); + + vector outChannelNames; + + for(unsigned i=0; i::const_iterator it = find(outChannelNames.begin(), outChannelNames.end(), channelName); + ia.targetChannel = (int)(it-outChannelNames.begin()); + if(it == outChannelNames.end()) + outChannelNames.push_back(channelName); + + ia.sourceCount = (unsigned)a.sourceSum.size(); + ia.sourceSum = (InternalAssignment::InternalSummand*)MemoryHelper::alloc(ia.sourceCount * sizeof(InternalAssignment::InternalSummand)); + + for(unsigned j=0; j 0) + stream << ", "; + stream << L"to channel " << outChannelNames[ia.targetChannel].c_str() << " "; + for(unsigned j=0; j 0) + stream << ", "; + if(is.channel != -1) + stream << L"from channel " << channelNames[is.channel].c_str() << L" with factor " << is.factor; + else + stream << L"value " << is.factor; + } + } + TraceF(L"%s", stream.str().c_str()); + + return outChannelNames; +} + +#pragma AVRT_CODE_BEGIN +void CopyFilter::process(float** output, float** input, unsigned frameCount) +{ + for(unsigned i=0; i +#include + +#include "IFilter.h" + +struct Assignment +{ + std::wstring targetChannel; + + struct Summand + { + double factor; + bool isDecibel; + std::wstring channel; + }; + + std::vector sourceSum; +}; + +#pragma AVRT_VTABLES_BEGIN +class CopyFilter : public IFilter +{ +public: + CopyFilter(const std::vector& assignments); + virtual ~CopyFilter(); + virtual bool getAllChannels() {return true;} + virtual bool getInPlace() {return false;} + virtual std::vector initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + void cleanup(); + + std::vector assignments; + + struct InternalAssignment + { + int targetChannel; + + struct InternalSummand + { + float factor; + int channel; + }; + + InternalSummand* sourceSum; + unsigned sourceCount; + }; + + InternalAssignment* internalAssignments; + unsigned assignmentCount; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/CopyFilterFactory.cpp b/filters/CopyFilterFactory.cpp new file mode 100644 index 0000000..5c874d8 --- /dev/null +++ b/filters/CopyFilterFactory.cpp @@ -0,0 +1,96 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2014 Jonas Thedering + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "CopyFilter.h" +#include "CopyFilterFactory.h" + +using namespace std; + +vector CopyFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + CopyFilter* filter = NULL; + + if(command == L"Copy") + { + vector assignments; + + vector assignmentStrings = StringHelper::split(parameters, L' '); + for(vector::iterator it=assignmentStrings.begin(); it!=assignmentStrings.end(); it++) + { + Assignment assignment; + + vector parts = StringHelper::split(*it, L'='); + if(parts.size() == 2) + { + wstring target = parts[0]; + wstring source = parts[1]; + + assignment.targetChannel = target; + + vector summands = StringHelper::split(source, '+'); + for(vector::iterator it2=summands.begin(); it2!=summands.end(); it2++) + { + vector factors = StringHelper::split(*it2, '*'); + wstring factor; + wstring channel; + if(factors.size() == 2) + { + factor = factors[0]; + channel = factors[1]; + } + else if(factors.size() == 1) + { + if(factors[0] == L"0" || factors[0].find(L'.') != wstring::npos) + factor = factors[0]; + else + channel = factors[0]; + } + + Assignment::Summand summand; + if(factor == L"") + { + summand.factor = 1.0; + summand.isDecibel = false; + } + else + { + summand.factor = wcstod(factor.c_str(), NULL); + summand.isDecibel = factor.size() > 2 && StringHelper::toLowerCase(factor.substr(factor.size()-2)) == L"db"; + } + + summand.channel = channel; + assignment.sourceSum.push_back(summand); + } + } + + if(assignment.targetChannel != L"" && !assignment.sourceSum.empty()) + assignments.push_back(assignment); + } + + void* mem = MemoryHelper::alloc(sizeof(CopyFilter)); + filter = new(mem) CopyFilter(assignments); + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} \ No newline at end of file diff --git a/filters/CopyFilterFactory.h b/filters/CopyFilterFactory.h new file mode 100644 index 0000000..33e7cb0 --- /dev/null +++ b/filters/CopyFilterFactory.h @@ -0,0 +1,31 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class CopyFilterFactory : public IFilterFactory +{ +public: + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/DelayFilter.cpp b/filters/DelayFilter.cpp new file mode 100644 index 0000000..55540a4 --- /dev/null +++ b/filters/DelayFilter.cpp @@ -0,0 +1,112 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/MemoryHelper.h" +#include "DelayFilter.h" + +using namespace std; + +DelayFilter::DelayFilter(double delay, bool isMs) + :delay(delay), isMs(isMs) +{ + buffers = NULL; +} + +DelayFilter::~DelayFilter() +{ + cleanup(); +} + +vector DelayFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + cleanup(); + + channelCount = (unsigned)channelNames.size(); + + if(isMs) + bufferLength = (unsigned)(sampleRate * delay / 1000.0 + 0.5); + else + bufferLength = (unsigned)(delay + 0.5); + + buffers = (float**)MemoryHelper::alloc(sizeof(float*) * channelCount); + + for(unsigned i=0; i initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + void cleanup(); + + double delay; + bool isMs; + unsigned bufferLength; + unsigned channelCount; + float** buffers; + unsigned bufferOffset; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/DelayFilterFactory.cpp b/filters/DelayFilterFactory.cpp new file mode 100644 index 0000000..153e27b --- /dev/null +++ b/filters/DelayFilterFactory.cpp @@ -0,0 +1,64 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "DelayFilter.h" +#include "DelayFilterFactory.h" + +using namespace std; + +vector DelayFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + DelayFilter* filter = NULL; + + if(command == L"Delay") + { + //Conversion to period as decimal mark, if needed + wstring value = StringHelper::replaceCharacters(parameters, L",", L"."); + + double delay = 0; + wstring unit; + wstringstream stream(value); + stream >> delay >> unit; + + if(delay > 0) + { + if(StringHelper::toLowerCase(unit) == L"ms") + { + TraceF(L"Delaying by %g ms", delay); + void* mem = MemoryHelper::alloc(sizeof(DelayFilter)); + filter = new(mem) DelayFilter(delay, true); + } + else if(StringHelper::toLowerCase(unit) == L"samples") + { + TraceF(L"Delaying by %g samples", delay); + void* mem = MemoryHelper::alloc(sizeof(DelayFilter)); + filter = new(mem) DelayFilter(delay, false); + } + } + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} \ No newline at end of file diff --git a/filters/DelayFilterFactory.h b/filters/DelayFilterFactory.h new file mode 100644 index 0000000..304f1ea --- /dev/null +++ b/filters/DelayFilterFactory.h @@ -0,0 +1,31 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class DelayFilterFactory : public IFilterFactory +{ +public: + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/DeviceFilterFactory.cpp b/filters/DeviceFilterFactory.cpp new file mode 100644 index 0000000..e58bb0d --- /dev/null +++ b/filters/DeviceFilterFactory.cpp @@ -0,0 +1,116 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "FilterEngine.h" +#include "DeviceFilterFactory.h" + +using namespace std; + +void DeviceFilterFactory::initialize(FilterEngine* engine) +{ + deviceString = engine->getDeviceName() + L" " + engine->getConnectionName() + L" " + engine->getDeviceGuid(); + + mup::ParserX* parser = engine->getParser(); + parser->DefineConst(L"deviceName", engine->getDeviceName()); + parser->DefineConst(L"connectionName", engine->getConnectionName()); + parser->DefineConst(L"deviceGuid", engine->getDeviceGuid()); +} + +vector DeviceFilterFactory::startOfConfiguration() +{ + deviceMatches = true; + + return vector(); +} + +vector DeviceFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + if(command == L"Device") + { + wstring value = StringHelper::trim(parameters) + L";"; + + vector> fullList; + vector currentList; + wstring currentWord; + + for(unsigned i=0; i 0) + { + currentList.push_back(currentWord); + currentWord.clear(); + } + if(c == L';' && currentList.size() > 0) + { + fullList.push_back(currentList); + currentList.clear(); + } + } + else + { + currentWord += c; + } + } + + bool matches = false; + + for(unsigned i=0; i(); +} + +std::vector DeviceFilterFactory::endOfFile(const std::wstring& configPath) +{ + // in outer file, the device must have matched, otherwise the inner file would not have been included + deviceMatches = true; + + return vector(); +} diff --git a/filters/DeviceFilterFactory.h b/filters/DeviceFilterFactory.h new file mode 100644 index 0000000..1189f98 --- /dev/null +++ b/filters/DeviceFilterFactory.h @@ -0,0 +1,38 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class DeviceFilterFactory : public IFilterFactory +{ +public: + virtual void initialize(FilterEngine* engine); + virtual std::vector startOfConfiguration(); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + virtual std::vector endOfFile(const std::wstring& configPath); + +private: + bool deviceMatches; + std::wstring deviceString; +}; diff --git a/filters/ExpressionFilterFactory.cpp b/filters/ExpressionFilterFactory.cpp new file mode 100644 index 0000000..0dfdf50 --- /dev/null +++ b/filters/ExpressionFilterFactory.cpp @@ -0,0 +1,152 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "parser/RegexFunctions.h" +#include "parser/RegistryFunctions.h" +#include "parser/StringOperators.h" +#include "FilterEngine.h" +#include "ExpressionFilterFactory.h" + +using namespace std; +using namespace mup; + +void ExpressionFilterFactory::initialize(FilterEngine* engine) +{ + parser = engine->getParser(); + parser->DefineConst(L"inputChannelCount", (int)engine->getInputChannelCount()); + parser->DefineConst(L"outputChannelCount", (int)engine->getOutputChannelCount()); + parser->DefineConst(L"sampleRate", engine->getSampleRate()); + + parser->DefineFun(new ReadRegStringFunction(engine)); + parser->DefineFun(new ReadRegDWORDFunction(engine)); + parser->DefineFun(new RegexSearchFunction()); + parser->DefineFun(new RegexReplaceFunction()); + + parser->RemoveOprt(L"+"); + parser->DefineOprt(new AddOperator()); +} + +vector ExpressionFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + if(command.length() > 0 && command[0] == L'#') + return vector(); + + bool inExpression = false; + bool lastWasBackslash = false; + + wstring output; + wstring expression; + for(unsigned i=0; iSetExpr(expression); + Value result = parser->Eval(); + wstring resultString; + if(result.GetType() == L's') + resultString = result.GetString(); + else + resultString = result.ToString().c_str(); + output += resultString; + TraceF(L"Inline expression %s evaluated to %s", expression.c_str(), resultString.c_str()); + } + catch(ParserError e) + { + LogF(L"Error while evaluating inline expression %s: %s", expression.c_str(), e.GetMsg().c_str()); + } + expression.clear(); + } + } + lastWasBackslash = false; + } + else if(c == L'\\') + { + if(i >= parameters.size()-1 || parameters[i+1] != L'`') + { + if(inExpression) + expression += c; + else + output += c; + } + lastWasBackslash = true; + } + else + { + if(inExpression) + expression += c; + else + output += c; + lastWasBackslash = false; + } + } + + parameters = output; + + if(command == L"Eval") + { + try + { + expression = StringHelper::trim(parameters); + parser->SetExpr(expression); + Value result = parser->Eval(); + wstring resultString; + if(result.GetType() == L's') + resultString = result.GetString(); + else + resultString = result.ToString().c_str(); + TraceF(L"Expression %s evaluated to %s", expression.c_str(), resultString.c_str()); + } + catch(ParserError e) + { + LogF(L"Error while evaluating expression %s: %s", expression.c_str(), e.GetMsg().c_str()); + } + + // command has been handled + command = L""; + } + + return vector(); +} diff --git a/filters/ExpressionFilterFactory.h b/filters/ExpressionFilterFactory.h new file mode 100644 index 0000000..9f75a5b --- /dev/null +++ b/filters/ExpressionFilterFactory.h @@ -0,0 +1,36 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class ExpressionFilterFactory : public IFilterFactory +{ +public: + virtual void initialize(FilterEngine* engine); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + +private: + mup::ParserX* parser; +}; diff --git a/filters/IIRFilter.cpp b/filters/IIRFilter.cpp new file mode 100644 index 0000000..0a70c7d --- /dev/null +++ b/filters/IIRFilter.cpp @@ -0,0 +1,123 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/MemoryHelper.h" +#include "IIRFilter.h" + +using namespace std; + +#define IS_DENORMAL(f) (((*(unsigned int *)&(f))&0x7f800000) == 0) + +IIRFilter::IIRFilter(const vector& coefficients) +{ + order = (unsigned)coefficients.size() / 2 - 1; + a = (float*)MemoryHelper::alloc(order * sizeof(float)); + b = (float*)MemoryHelper::alloc(order * sizeof(float)); + x = NULL; + y = NULL; + + double a0 = coefficients[order+1]; + b0 = float(coefficients[0] / a0); + for(unsigned i=0; i IIRFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + channelCount = (unsigned)channelNames.size(); + + if(x != NULL) + MemoryHelper::free(x); + if(y != NULL) + MemoryHelper::free(y); + + x = (float*)MemoryHelper::alloc(order * channelCount * sizeof(float)); + y = (float*)MemoryHelper::alloc(order * channelCount * sizeof(float)); + memset(x, 0, order * channelCount * sizeof(float)); + memset(y, 0, order * channelCount * sizeof(float)); + + return channelNames; +} + +#pragma AVRT_CODE_BEGIN +void IIRFilter::process(float** output, float** input, unsigned frameCount) +{ + for(unsigned i=0; i0; k--) + { + sum += b[k] * xo[k]; + } + + sum += b[0] * xo[0]; + + for(unsigned k=order-1; k>0; k--) + { + sum += a[k] * yo[k]; + } + + sum += a[0] * yo[0]; + + for(unsigned k=order-1; k>0; k--) + { + xo[k] = xo[k-1]; + yo[k] = yo[k-1]; + } + + xo[0] = sample; + yo[0] = sum; + + outputChannel[j] = sum; + } + } + + for(unsigned i=0; i& coefficients); + virtual ~IIRFilter(); + virtual bool getInPlace() {return true;} + virtual std::vector initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + unsigned order; + float b0; + float* a; + float* b; + unsigned channelCount; + float* x; + float* y; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/IIRFilterFactory.cpp b/filters/IIRFilterFactory.cpp new file mode 100644 index 0000000..96aa82a --- /dev/null +++ b/filters/IIRFilterFactory.cpp @@ -0,0 +1,108 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2014 Jonas Thedering + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define _USE_MATH_DEFINES +#include +#include +#include + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "IIRFilter.h" +#include "IIRFilterFactory.h" + +using namespace std; + +static wregex regexType(L"^\\s*ON\\s+([A-Za-z]+)"); +static wregex regexOrder(L"\\s*Order\\s+([0-9]+)"); +static wregex regexCoefficients(L"\\s+Coefficients((?: [-+0-9.eE]+)+)"); + +IIRFilterFactory::IIRFilterFactory() +{ +} + +vector IIRFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + IIRFilter* filter = NULL; + + if(command.find(L"Filter") == 0) + { + wsmatch match; + wstring typeString; + + bool found = regex_search(parameters, match, regexType); + if(found) + { + typeString = match.str(1); + if(typeString == L"IIR") + { + bool found = regex_search(parameters, match, regexOrder); + if(found) + { + wstring orderString = match.str(1); + unsigned order = wcstol(orderString.c_str(), NULL, 10); + + if(order<1) + { + LogF(L"Order %d not supported, must at least be 1", order); + } + else + { + found = regex_search(parameters, match, regexCoefficients); + + if(found) + { + wstring coefficientsString = match.str(1); + vector coefficientStrings = StringHelper::split(coefficientsString, L' '); + if(coefficientStrings.size() != (order + 1) * 2) + { + LogF(L"Invalid number of coefficients. Expected %d coefficients instead of %d", (order + 1) * 2, coefficientStrings.size()); + } + else + { + vector coefficients; + for(auto it = coefficientStrings.begin(); it!=coefficientStrings.end(); it++) + { + coefficients.push_back(wcstod(it->c_str(), NULL)); + } + + wstringstream stream; + stream << L"Adding IIR filter of order " << order << " with coefficients"; + for(unsigned i=0; i<=order; i++) + stream << L" b" << i << L"=" << coefficients[i]; + for(unsigned i=0; i<=order; i++) + stream << L" a" << i << L"=" << coefficients[i + order + 1]; + + TraceF(L"%s", stream.str().c_str()); + + void* mem = MemoryHelper::alloc(sizeof(IIRFilter)); + filter = new(mem) IIRFilter(coefficients); + } + } + } + } + } + } + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} diff --git a/filters/IIRFilterFactory.h b/filters/IIRFilterFactory.h new file mode 100644 index 0000000..8cf8cbc --- /dev/null +++ b/filters/IIRFilterFactory.h @@ -0,0 +1,33 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class IIRFilterFactory : public IFilterFactory +{ +public: + IIRFilterFactory(); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/IfFilterFactory.cpp b/filters/IfFilterFactory.cpp new file mode 100644 index 0000000..b1e74af --- /dev/null +++ b/filters/IfFilterFactory.cpp @@ -0,0 +1,211 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "parser/RegexFunctions.h" +#include "parser/RegistryFunctions.h" +#include "FilterEngine.h" +#include "IfFilterFactory.h" + +using namespace std; +using namespace mup; + +void IfFilterFactory::initialize(FilterEngine* engine) +{ + parser = engine->getParser(); +} + +vector IfFilterFactory::startOfConfiguration() +{ + trueCount = 0; + falseCount = 0; + while(!trueCountStack.empty()) + trueCountStack.pop(); + + return vector(); +} + +std::vector IfFilterFactory::startOfFile(const std::wstring& configPath) +{ + trueCountStack.push(trueCount); + trueCount = 0; + if(falseCount != 0) + { + LogF(L"File was included inside If that evaluated to false!"); + falseCount = 0; + } + + return vector(); +} + +vector IfFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + wstring expression = StringHelper::trim(parameters); + + if(command == L"If") + { + if(falseCount == 0) + { + try + { + parser->SetExpr(expression); + Value result = parser->Eval(); + bool isTrue = toBoolean(result); + if(result.GetType() == L'b') + TraceF(L"If(%s) evaluated to %s", expression.c_str(), result.ToString().c_str()); + else + TraceF(L"If(%s) evaluated to %s (%s)", expression.c_str(), result.ToString().c_str(), isTrue ? L"true" : L"false"); + + if(isTrue) + trueCount++; + else + falseCount++; + } + catch(ParserError e) + { + LogF(L"Error while evaluating If(%s): %s", expression.c_str(), e.GetMsg().c_str()); + falseCount++; + } + } + else + { + falseCount++; + } + } + else if(command == L"ElseIf") + { + if(falseCount == 1) + { + try + { + parser->SetExpr(expression); + Value result = parser->Eval(); + bool isTrue = toBoolean(result); + if(result.GetType() == L'b') + TraceF(L"ElseIf(%s) evaluated to %s", expression.c_str(), result.ToString().c_str()); + else + TraceF(L"ElseIf(%s) evaluated to %s (%s)", expression.c_str(), result.ToString().c_str(), isTrue ? L"true" : L"false"); + + if(isTrue) + { + falseCount--; + trueCount++; + } + } + catch(ParserError e) + { + LogF(L"Error while evaluating ElseIf(%s): %s", expression.c_str(), e.GetMsg().c_str()); + } + } + else if(falseCount == 0 && trueCount == 0) + { + LogF(L"ElseIf without If!"); + } + } + else if(command == L"Else") + { + if(falseCount == 1) + { + falseCount--; + trueCount++; + } + else if(falseCount == 0 && trueCount == 0) + { + LogF(L"Else without If!"); + } + } + else if(command == L"EndIf") + { + if(falseCount == 0) + { + if(trueCount == 0) + LogF(L"EndIf without If!"); + else + trueCount--; + } + else + { + falseCount--; + } + } + + if(falseCount > 0) + // skip line for further factories + command = L""; + + return vector(); +} + +std::vector IfFilterFactory::endOfFile(const std::wstring& configPath) +{ + if(trueCount != 0 || falseCount != 0) + { + LogF(L"If was not closed by EndIf!"); + falseCount = 0; + } + trueCount = trueCountStack.top(); + trueCountStack.pop(); + + return vector(); +} + +bool IfFilterFactory::toBoolean(const Value& value) +{ + bool result = false; + + wchar_t type = value.GetType(); + switch(type) + { + case 'i': + { + int i = value.GetInteger(); + result = (i != 0); + } + break; + case 'f': + { + double f = value.GetFloat(); + result = (f != 0.0 && f == f); + } + break; + case 'b': + { + result = value.GetBool(); + } + break; + case 's': + { + wstring s = value.GetString(); + result = s.length() > 0 && s != L"false" && s != L"0"; + } + break; + case 'm': + { + Matrix m = value.GetArray(); + result = m.GetRows() > 0 && m.GetCols() > 0; + } + break; + default: + result = false; + break; + } + + return result; +} diff --git a/filters/IfFilterFactory.h b/filters/IfFilterFactory.h new file mode 100644 index 0000000..40b6ee3 --- /dev/null +++ b/filters/IfFilterFactory.h @@ -0,0 +1,45 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class IfFilterFactory : public IFilterFactory +{ +public: + virtual void initialize(FilterEngine* engine); + virtual std::vector startOfConfiguration(); + virtual std::vector startOfFile(const std::wstring& configPath); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + virtual std::vector endOfFile(const std::wstring& configPath); + +private: + mup::ParserX* parser; + + unsigned trueCount; + unsigned falseCount; + std::stack trueCountStack; + + bool toBoolean(const mup::Value& value); +}; diff --git a/filters/IncludeFilterFactory.cpp b/filters/IncludeFilterFactory.cpp new file mode 100644 index 0000000..d6855b1 --- /dev/null +++ b/filters/IncludeFilterFactory.cpp @@ -0,0 +1,63 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "FilterEngine.h" +#include "IncludeFilterFactory.h" + +using namespace std; + +void IncludeFilterFactory::initialize(FilterEngine* engine) +{ + this->engine = engine; +} + +vector IncludeFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + if(command == L"Include") + { + wstring value = parameters; + while(value.length() > 0 && iswspace(value[0])) + value = value.substr(1); + + wstring includePath; + if(PathIsRelativeW(value.c_str())) + { + wchar_t filePath[MAX_PATH]; + configPath._Copy_s(filePath, sizeof(filePath)/sizeof(wchar_t), MAX_PATH); + if(configPath.size() < MAX_PATH) + filePath[configPath.size()] = L'\0'; + else + filePath[MAX_PATH - 1] = L'\0'; + PathRemoveFileSpecW(filePath); + PathAppendW(filePath, value.c_str()); + includePath = filePath; + } + else + includePath = value; + + engine->loadConfig(includePath); + command = L""; + } + + return vector(); +} \ No newline at end of file diff --git a/filters/IncludeFilterFactory.h b/filters/IncludeFilterFactory.h new file mode 100644 index 0000000..5e81378 --- /dev/null +++ b/filters/IncludeFilterFactory.h @@ -0,0 +1,35 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class IncludeFilterFactory : public IFilterFactory +{ +public: + virtual void initialize(FilterEngine* engine); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + +private: + FilterEngine* engine; +}; diff --git a/filters/PreampFilter.cpp b/filters/PreampFilter.cpp new file mode 100644 index 0000000..f88e5bd --- /dev/null +++ b/filters/PreampFilter.cpp @@ -0,0 +1,51 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define _USE_MATH_DEFINES +#include + +#include "PreampFilter.h" + +using namespace std; + +PreampFilter::PreampFilter(double dbGain) + :dbGain(dbGain) +{ + gain = (float)pow(10.0, dbGain / 20.0); +} + +vector PreampFilter::initialize(float sampleRate, unsigned maxFrameCount, vector channelNames) +{ + this->channelCount = channelNames.size(); + + return channelNames; +} + +#pragma AVRT_CODE_BEGIN +void PreampFilter::process(float** output, float** input, unsigned frameCount) +{ + for(size_t i=0; i initialize(float sampleRate, unsigned maxFrameCount, std::vector channelNames); + virtual void process(float** output, float** input, unsigned frameCount); + +private: + float gain; + double dbGain; + size_t channelCount; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/PreampFilterFactory.cpp b/filters/PreampFilterFactory.cpp new file mode 100644 index 0000000..b6f5a97 --- /dev/null +++ b/filters/PreampFilterFactory.cpp @@ -0,0 +1,51 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "PreampFilter.h" +#include "PreampFilterFactory.h" + +using namespace std; + +vector PreampFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + PreampFilter* filter = NULL; + + if(command == L"Preamp") + { + //Conversion to period as decimal mark, if needed + wstring value = StringHelper::replaceCharacters(parameters, L",", L"."); + + double preamp_dB; + int matched = swscanf_s(value.c_str(), L" %lf dB", &preamp_dB); + if(matched == 1) + { + TraceF(L"Adjusting preamp by %g dB", preamp_dB); + + void* mem = MemoryHelper::alloc(sizeof(PreampFilter)); + filter = new(mem) PreampFilter(preamp_dB); + } + } + + if(filter == NULL) + return vector(0); + return vector(1, filter); +} \ No newline at end of file diff --git a/filters/PreampFilterFactory.h b/filters/PreampFilterFactory.h new file mode 100644 index 0000000..8022b32 --- /dev/null +++ b/filters/PreampFilterFactory.h @@ -0,0 +1,31 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class PreampFilterFactory : public IFilterFactory +{ +public: + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/StageFilterFactory.cpp b/filters/StageFilterFactory.cpp new file mode 100644 index 0000000..d9b9955 --- /dev/null +++ b/filters/StageFilterFactory.cpp @@ -0,0 +1,112 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "helpers/LogHelper.h" +#include "helpers/StringHelper.h" +#include "FilterEngine.h" +#include "StageFilterFactory.h" + +using namespace std; + +void StageFilterFactory::initialize(FilterEngine* engine) +{ + lfx = engine->isLfx(); + capture = engine->isCapture(); + + engine->getParser()->DefineConst(L"stage", engine->isCapture() ? L"capture" : engine->isLfx() ? L"pre-mix" : L"post-mix"); +} + +vector StageFilterFactory::startOfConfiguration() +{ + stageMatches = capture || !lfx; + while(!stageMatchesStack.empty()) + stageMatchesStack.pop(); + + return vector(); +} + +std::vector StageFilterFactory::startOfFile(const std::wstring& configPath) +{ + stageMatchesStack.push(stageMatches); + + return vector(); +} + +vector StageFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + if(command == L"Stage") + { + stageMatches = false; + + wstring stageString = StringHelper::toLowerCase(StringHelper::trim(parameters)); + vector parts = StringHelper::split(stageString, L' '); + wstring matchingPart; + for(auto it=parts.begin(); it!=parts.end(); it++) + { + const wstring& part = *it; + if(part == L"pre-mix") + { + if(!capture && lfx) + { + stageMatches = true; + matchingPart = part; + } + } + else if(part == L"post-mix") + { + if(!capture && !lfx) + { + stageMatches = true; + matchingPart = part; + } + } + else if(part == L"capture") + { + if(capture) + { + stageMatches = true; + matchingPart = part; + } + } + else + { + LogF(L"Unknown stage \"%s\"! Only pre-mix, post-mix and capture are supported.", part.c_str()); + } + } + + if(stageMatches) + TraceF(L"Matching stage \"%s\"", matchingPart.c_str()); + else + TraceF(L"Not matching stage set \"%s\"", stageString.c_str()); + } + + if(!stageMatches) + // skip line for further factories + command = L""; + + return vector(); +} + +std::vector StageFilterFactory::endOfFile(const std::wstring& configPath) +{ + stageMatches = stageMatchesStack.top(); + stageMatchesStack.pop(); + + return vector(); +} diff --git a/filters/StageFilterFactory.h b/filters/StageFilterFactory.h new file mode 100644 index 0000000..7cc7a8f --- /dev/null +++ b/filters/StageFilterFactory.h @@ -0,0 +1,42 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class StageFilterFactory : public IFilterFactory +{ +public: + virtual void initialize(FilterEngine* engine); + virtual std::vector startOfConfiguration(); + virtual std::vector startOfFile(const std::wstring& configPath); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); + virtual std::vector endOfFile(const std::wstring& configPath); + +private: + bool lfx; + bool capture; + + bool stageMatches; + std::stack stageMatchesStack; +}; diff --git a/LogHelper.cpp b/helpers/LogHelper.cpp similarity index 87% rename from LogHelper.cpp rename to helpers/LogHelper.cpp index a768c9a..09e0664 100644 --- a/LogHelper.cpp +++ b/helpers/LogHelper.cpp @@ -33,7 +33,7 @@ FILE* LogHelper::presetFP = NULL; bool LogHelper::compact = false; bool LogHelper::useConsoleColors = false; -void LogHelper::log(const char* file, int line, void* caller, bool trace, const wchar_t* format, ...) +void LogHelper::log(const char* file, int line, const void* caller, bool trace, const wchar_t* format, ...) { if(!initialized) { @@ -85,8 +85,9 @@ void LogHelper::log(const char* file, int line, void* caller, bool trace, const { SYSTEMTIME ___st; GetLocalTime(&___st); - fwprintf(fp, L"%04d-%02d-%02d %02d:%02d:%02d.%03d %08X (%S:%d): ", - ___st.wYear, ___st.wMonth, ___st.wDay, ___st.wHour, ___st.wMinute, ___st.wSecond, ___st.wMilliseconds, caller, file, line); + DWORD threadId = GetCurrentThreadId(); + fwprintf(fp, L"%04d-%02d-%02d %02d:%02d:%02d.%03d %d %08X (%S:%d): ", + ___st.wYear, ___st.wMonth, ___st.wDay, ___st.wHour, ___st.wMinute, ___st.wSecond, ___st.wMilliseconds, threadId, caller, file, line); } if(trace) @@ -124,4 +125,4 @@ void LogHelper::set(FILE* fp, bool enableTrace, bool compact, bool useConsoleCol LogHelper::enableTrace = enableTrace; LogHelper::compact = compact; LogHelper::useConsoleColors = useConsoleColors; -} +} \ No newline at end of file diff --git a/LogHelper.h b/helpers/LogHelper.h similarity index 91% rename from LogHelper.h rename to helpers/LogHelper.h index 7c3d146..3a22411 100644 --- a/LogHelper.h +++ b/helpers/LogHelper.h @@ -30,7 +30,7 @@ class LogHelper { public: - static void log(const char* file, int line, void* caller, bool trace, const wchar_t* format, ...); + static void log(const char* file, int line, const void* caller, bool trace, const wchar_t* format, ...); static void reset(); static void set(FILE* fp, bool enableTrace, bool compact, bool useConsoleColors); diff --git a/helpers/MemoryHelper.cpp b/helpers/MemoryHelper.cpp new file mode 100644 index 0000000..5a4e0e9 --- /dev/null +++ b/helpers/MemoryHelper.cpp @@ -0,0 +1,112 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2013 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#define WIN32_LEAN_AND_MEAN +#include +#ifdef _DEBUG +#include +#include +#endif +#ifdef USE_WINDDK +// disable the declarations to use our own (see below) +#define AERT_Allocate _AERT_Allocate_disabled +#define AERT_Free _AERT_Free_disabled +#include +#undef AERT_Allocate +#undef AERT_Free +#endif + +#include "LogHelper.h" +#include "MemoryHelper.h" + +#ifdef USE_WINDDK +// someone forgot to add the __stdcall/WINAPI modifier to BaseAudioProcessingObject.h, so we can't use the existing declarations +extern "C" +{ + HRESULT __stdcall AERT_Allocate(size_t size, void **pMemory); + HRESULT __stdcall AERT_Free(void *pMemory); +} +#endif + +void* MemoryHelper::alloc(size_t size) +{ + void* memory; + bool alternative = false; + size += 16; +#ifdef USE_WINDDK + HRESULT hr = AERT_Allocate(size, &memory); + if(FAILED(hr)) + { + size += 16; + memory = malloc(size); + + alternative = true; + } +#else +#ifdef _DEBUG + memory = _malloc_dbg(size,_NORMAL_BLOCK, __FILE__, __LINE__); +#else + memory = malloc(size); +#endif +#endif + if(memory == NULL) + { + LogFStatic(L"Allocation of %d bytes failed.", size); + return NULL; + } + + size_t offset = 16 - ((size_t)memory) % 16; + if(alternative) + offset += 16; + void* ptr = ((char*)memory) + offset; + ((char*)ptr)[-1] = (char)offset; + + return ptr; +} + +void MemoryHelper::free(void* ptr) +{ + bool alternative = false; + char offset = ((char*)ptr)[-1]; + if(offset > 16) + alternative = true; + + void* memory = ((char*)ptr) - offset; + +#ifdef USE_WINDDK + if(alternative) + { + ::free(memory); + } + else + { + HRESULT hr = AERT_Free(memory); + if(FAILED(hr)) + { + LogFStatic(L"Memory release failed. Error code %X", hr); + } + } +#else +#ifdef _DEBUG + _free_dbg(memory,_NORMAL_BLOCK); +#else + ::free(memory); +#endif +#endif +} \ No newline at end of file diff --git a/StringHelper.h b/helpers/MemoryHelper.h similarity index 64% rename from StringHelper.h rename to helpers/MemoryHelper.h index d735896..5f053a6 100644 --- a/StringHelper.h +++ b/helpers/MemoryHelper.h @@ -19,15 +19,18 @@ #pragma once -#include -#include +#ifdef USE_WINDDK +#include +#else +#define AVRT_VTABLES_BEGIN +#define AVRT_VTABLES_END +#define AVRT_CODE_BEGIN +#define AVRT_CODE_END +#endif -class StringHelper +class MemoryHelper { public: - // replaces any occurrence of a character from chars in s with the replacement string - static std::wstring replaceCharacters(std::wstring s, std::wstring chars, std::wstring replacement); - static std::wstring replaceIllegalCharacters(std::wstring filename); - static std::wstring toWString(std::string s, unsigned codepage); - static std::wstring toLowerCase(std::wstring s); + static void* alloc(size_t size); + static void free(void* ptr); }; diff --git a/Benchmark/PerformanceCounter.h b/helpers/PrecisionTimer.h similarity index 100% rename from Benchmark/PerformanceCounter.h rename to helpers/PrecisionTimer.h diff --git a/RegistryHelper.cpp b/helpers/RegistryHelper.cpp similarity index 59% rename from RegistryHelper.cpp rename to helpers/RegistryHelper.cpp index b662fe7..5a37c3b 100644 --- a/RegistryHelper.cpp +++ b/helpers/RegistryHelper.cpp @@ -19,11 +19,10 @@ #include #include -#define WIN32_LEAN_AND_MEAN -#include #include #include +#include "StringHelper.h" #include "RegistryHelper.h" using namespace std; @@ -34,25 +33,22 @@ wstring RegistryHelper::readValue(wstring key, wstring valuename) { wstring result; - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); + LSTATUS status; DWORD type; DWORD bufSize; status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, &type, NULL, &bufSize); if(status != ERROR_SUCCESS) { RegCloseKey(keyHandle); - throw RegistryException(L"Error while reading registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } if(type != REG_SZ) { RegCloseKey(keyHandle); - throw RegistryException(L"Registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L" has wrong type"); + throw RegistryException(L"Registry value " + key + L"\\" + valuename + L" has wrong type"); } wchar_t* buf = new wchar_t[bufSize / sizeof(wchar_t) + 1]; @@ -63,7 +59,7 @@ wstring RegistryHelper::readValue(wstring key, wstring valuename) if(status != ERROR_SUCCESS) { delete buf; - throw RegistryException(L"Error while reading registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } //Remove zero-termination @@ -79,25 +75,22 @@ unsigned long RegistryHelper::readDWORDValue(wstring key, wstring valuename) { unsigned long result; - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); + LSTATUS status; DWORD type; DWORD bufSize; status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, &type, NULL, &bufSize); if(status != ERROR_SUCCESS) { RegCloseKey(keyHandle); - throw RegistryException(L"Error while reading registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } if(type != REG_DWORD) { RegCloseKey(keyHandle); - throw RegistryException(L"Registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L" has wrong type"); + throw RegistryException(L"Registry value " + key + L"\\" + valuename + L" has wrong type"); } BYTE* buf = new BYTE[bufSize]; @@ -108,7 +101,7 @@ unsigned long RegistryHelper::readDWORDValue(wstring key, wstring valuename) if(status != ERROR_SUCCESS) { delete buf; - throw RegistryException(L"Error while reading registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while reading registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } result = ((unsigned long*)buf)[0]; @@ -119,93 +112,81 @@ unsigned long RegistryHelper::readDWORDValue(wstring key, wstring valuename) void RegistryHelper::writeValue(wstring key, wstring valuename, wstring value) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_SET_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); - status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)((value.size()+1)*sizeof(wchar_t))); + LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)((value.size()+1)*sizeof(wchar_t))); RegCloseKey(keyHandle); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while writing to registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while writing to registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } void RegistryHelper::writeMultiValue(wstring key, wstring valuename, wstring value) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_SET_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); wchar_t* data = new wchar_t[value.size() + 2]; value._Copy_s(data, (value.size()+2)*sizeof(wchar_t), value.size()); data[value.size()] = L'\0'; data[value.size()+1] = L'\0'; - status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_MULTI_SZ, (const BYTE*)data, (DWORD)((value.size()+2)*sizeof(wchar_t))); + LSTATUS status = RegSetValueExW(keyHandle, valuename.c_str(), 0, REG_MULTI_SZ, (const BYTE*)data, (DWORD)((value.size()+2)*sizeof(wchar_t))); delete data; RegCloseKey(keyHandle); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while writing to registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while writing to registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } void RegistryHelper::deleteValue(wstring key, wstring valuename) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_SET_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_SET_VALUE | KEY_WOW64_64KEY); - status = RegDeleteValueW(keyHandle, valuename.c_str()); + LSTATUS status = RegDeleteValueW(keyHandle, valuename.c_str()); RegCloseKey(keyHandle); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while deleting registry value HKEY_LOCAL_MACHINE\\" + key + L"\\" + valuename + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while deleting registry value " + key + L"\\" + valuename + L": " + StringHelper::getSystemErrorString(status)); } void RegistryHelper::createKey(wstring key) { + HKEY rootKey; + wstring subKey = splitKey(key, &rootKey); + HKEY keyHandle; - LSTATUS status; - status = RegCreateKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, NULL, 0, KEY_SET_VALUE | KEY_WOW64_64KEY, NULL, &keyHandle, NULL); + LSTATUS status = RegCreateKeyExW(rootKey, subKey.c_str(), 0, NULL, 0, KEY_SET_VALUE | KEY_WOW64_64KEY, NULL, &keyHandle, NULL); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while creating registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while creating registry key " + key + L": " + StringHelper::getSystemErrorString(status)); RegCloseKey(keyHandle); } void RegistryHelper::deleteKey(wstring key) { - LSTATUS status; - status = RegDeleteKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), KEY_WOW64_64KEY, 0); + HKEY rootKey; + wstring subKey = splitKey(key, &rootKey); + + LSTATUS status = RegDeleteKeyExW(rootKey, subKey.c_str(), KEY_WOW64_64KEY, 0); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while deleting registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while deleting registry key " + key + L": " + StringHelper::getSystemErrorString(status)); } void RegistryHelper::makeWritable(wstring key) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, READ_CONTROL | WRITE_DAC | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, READ_CONTROL | WRITE_DAC | KEY_WOW64_64KEY); DWORD descriptorSize = 0; RegGetKeySecurity(keyHandle, DACL_SECURITY_INFORMATION, NULL, &descriptorSize); PSECURITY_DESCRIPTOR oldSd = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, descriptorSize); - status = RegGetKeySecurity(keyHandle, DACL_SECURITY_INFORMATION, oldSd, &descriptorSize); + LSTATUS status = RegGetKeySecurity(keyHandle, DACL_SECURITY_INFORMATION, oldSd, &descriptorSize); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while getting security information for registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while getting security information for registry key " + key + L": " + StringHelper::getSystemErrorString(status)); BOOL aclPresent, aclDefaulted; PACL oldAcl = NULL; @@ -242,7 +223,7 @@ void RegistryHelper::makeWritable(wstring key) status = RegSetKeySecurity(keyHandle, DACL_SECURITY_INFORMATION, sd); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while setting security information for registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while setting security information for registry key " + key + L": " + StringHelper::getSystemErrorString(status)); FreeSid(sid); LocalFree(acl); @@ -268,11 +249,7 @@ void RegistryHelper::takeOwnership(wstring key) if(!AdjustTokenPrivileges(tokenHandle, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) throw RegistryException(L"Error in AdjustTokenPrivileges while taking ownership"); - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, WRITE_OWNER | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, WRITE_OWNER | KEY_WOW64_64KEY); PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if(NULL == sd) @@ -290,9 +267,9 @@ void RegistryHelper::takeOwnership(wstring key) if(!SetSecurityDescriptorOwner(sd, sid, FALSE)) throw RegistryException(L"Error in SetSecurityDescriptorOwner while taking ownership"); - status = RegSetKeySecurity(keyHandle, OWNER_SECURITY_INFORMATION, sd); + LSTATUS status = RegSetKeySecurity(keyHandle, OWNER_SECURITY_INFORMATION, sd); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while setting security information for registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while setting security information for registry key " + key + L": " + StringHelper::getSystemErrorString(status)); tp.Privileges[0].Attributes = 0; @@ -307,8 +284,11 @@ bool RegistryHelper::keyExists(wstring key) { bool result; + HKEY rootKey; + wstring subKey = splitKey(key, &rootKey); + HKEY keyHandle; - result = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle) == ERROR_SUCCESS); + result = (RegOpenKeyExW(rootKey, subKey.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle) == ERROR_SUCCESS); if(result) RegCloseKey(keyHandle); @@ -318,15 +298,11 @@ bool RegistryHelper::keyExists(wstring key) bool RegistryHelper::valueExists(wstring key, wstring valuename) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); DWORD type; DWORD bufSize; - status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, &type, NULL, &bufSize); + LSTATUS status = RegQueryValueExW(keyHandle, valuename.c_str(), NULL, &type, NULL, &bufSize); RegCloseKey(keyHandle); return status == ERROR_SUCCESS; } @@ -335,17 +311,14 @@ vector RegistryHelper::enumSubKeys(wstring key) { vector result; - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_ENUMERATE_SUB_KEYS | KEY_WOW64_64KEY); wchar_t keyName[256]; DWORD keyLength = sizeof(keyName) / sizeof(wchar_t); int i=0; int itemCount=0; + LSTATUS status; while((status = RegEnumKeyExW(keyHandle, i++, keyName, &keyLength, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) { keyLength = sizeof(keyName) / sizeof(wchar_t); @@ -356,41 +329,42 @@ vector RegistryHelper::enumSubKeys(wstring key) RegCloseKey(keyHandle); if(status != ERROR_NO_MORE_ITEMS) - throw RegistryException(L"Error while enumerating sub keys of registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while enumerating sub keys of registry key " + key + L": " + StringHelper::getSystemErrorString(status)); return result; } -unsigned long RegistryHelper::valueCount(wstring key) +bool RegistryHelper::keyEmpty(wstring key) { - LSTATUS status; - HKEY keyHandle; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key.c_str(), 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &keyHandle); - if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while opening registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + HKEY keyHandle = openKey(key, KEY_QUERY_VALUE | KEY_WOW64_64KEY); + DWORD keyCount; DWORD valueCount; - status = RegQueryInfoKeyW(keyHandle, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount, NULL, NULL, NULL, NULL); + LSTATUS status = RegQueryInfoKeyW(keyHandle, NULL, NULL, NULL, &keyCount, NULL, NULL, &valueCount, NULL, NULL, NULL, NULL); RegCloseKey(keyHandle); if(status != ERROR_SUCCESS) - throw RegistryException(L"Error while reading info for registry key HKEY_LOCAL_MACHINE\\" + key + L": " + getSystemErrorString(status)); + throw RegistryException(L"Error while reading info for registry key " + key + L": " + StringHelper::getSystemErrorString(status)); - return valueCount; + return keyCount == 0 && valueCount == 0; } -void RegistryHelper::saveToFile(wstring key, wstring valuename, wstring filepath) +void RegistryHelper::saveToFile(wstring key, vector valuenames, wstring filepath) { - wstring value = readValue(key, valuename); - wofstream stream(filepath); if(!stream.good()) throw RegistryException(L"Error while opening file " + filepath + L" for writing"); stream << L"Windows Registry Editor Version 5.00\n" << endl; stream << L"[HKEY_LOCAL_MACHINE\\" << key << L"]" << endl; - stream << L"\"" << valuename << L"\"=\"" << value << L"\"" << endl; + for(vector::iterator it = valuenames.begin(); it != valuenames.end(); it++) + { + wstring& valuename = *it; + wstring value = readValue(key, valuename); + + stream << L"\"" << valuename << L"\"=\"" << value << L"\"" << endl; + } stream << endl; stream.close(); @@ -433,17 +407,41 @@ bool RegistryHelper::isWindowsVersionAtLeast(unsigned major, unsigned minor) return windowsVersion >= compareVersion; } -wstring RegistryHelper::getSystemErrorString(long status) +HKEY RegistryHelper::openKey(const wstring& key, REGSAM samDesired) { - wchar_t* buf; + HKEY rootKey; + wstring subKey = splitKey(key, &rootKey); - if(FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, (LPTSTR)&buf, 0, NULL) != 0) - { - wstring result(buf); - LocalFree(buf); + HKEY keyHandle; + LSTATUS status = RegOpenKeyExW(rootKey, subKey.c_str(), 0, samDesired, &keyHandle); + if(status != ERROR_SUCCESS) + throw RegistryException(L"Error while opening registry key " + key + L": " + StringHelper::getSystemErrorString(status)); - return result; - } + return keyHandle; +} + +wstring RegistryHelper::splitKey(const wstring& key, HKEY* rootKey) +{ + size_t pos = key.find(L'\\'); + if(pos == wstring::npos) + throw RegistryException(L"Key " + key + L" has invalid format"); + + wstring rootPart = key.substr(0, pos); + wstring pathPart = key.substr(pos+1); + + wstring p = StringHelper::toUpperCase(rootPart); + if(p == L"HKEY_CLASSES_ROOT") + *rootKey = HKEY_CLASSES_ROOT; + else if(p == L"HKEY_CURRENT_CONFIG") + *rootKey = HKEY_CURRENT_CONFIG; + else if(p == L"HKEY_CURRENT_USER") + *rootKey = HKEY_CURRENT_USER; + else if(p == L"HKEY_LOCAL_MACHINE") + *rootKey = HKEY_LOCAL_MACHINE; + else if(p == L"HKEY_USERS") + *rootKey = HKEY_USERS; else - return L""; -} \ No newline at end of file + throw RegistryException(L"Unknown root key " + rootPart); + + return pathPart; +} diff --git a/RegistryHelper.h b/helpers/RegistryHelper.h similarity index 81% rename from RegistryHelper.h rename to helpers/RegistryHelper.h index 0159b1a..4cf4d79 100644 --- a/RegistryHelper.h +++ b/helpers/RegistryHelper.h @@ -22,8 +22,10 @@ #include #include #include +#define WIN32_LEAN_AND_MEAN +#include -#define APP_REGPATH L"SOFTWARE\\EqualizerAPO" +#define APP_REGPATH L"HKEY_LOCAL_MACHINE\\SOFTWARE\\EqualizerAPO" class RegistryHelper { @@ -40,13 +42,15 @@ public: static std::vector enumSubKeys(std::wstring key); static bool keyExists(std::wstring key); static bool valueExists(std::wstring key, std::wstring valuename); - static unsigned long valueCount(std::wstring key); - static void saveToFile(std::wstring key, std::wstring valuename, std::wstring filepath); + static bool keyEmpty(std::wstring key); + static void saveToFile(std::wstring key, std::vector valuenames, std::wstring filepath); static std::wstring getGuidString(GUID guid); static bool isWindowsVersionAtLeast(unsigned major, unsigned minor); + static HKEY openKey(const std::wstring& key, REGSAM samDesired); private: - static std::wstring getSystemErrorString(long status); + static std::wstring splitKey(const std::wstring& key, HKEY* rootKey); + static unsigned long windowsVersion; }; diff --git a/helpers/StringHelper.cpp b/helpers/StringHelper.cpp new file mode 100644 index 0000000..da3188a --- /dev/null +++ b/helpers/StringHelper.cpp @@ -0,0 +1,146 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2013 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include "StringHelper.h" + +using namespace std; + +wstring StringHelper::replaceCharacters(const wstring& s, const wstring& chars, const wstring& replacement) +{ + wstring result; + result.reserve(s.length()); + + for(unsigned i=0; i:\"/\\|?*", L"_"); +} + +wstring StringHelper::toWString(const string& s, unsigned codepage) +{ + int length = MultiByteToWideChar(codepage, 0, s.c_str(), -1, NULL, 0); + wchar_t* charBuf = new wchar_t[length]; + MultiByteToWideChar(codepage, 0, s.c_str(), -1, charBuf, length); + wstring result = charBuf; + delete charBuf; + + return result; +} + +wstring StringHelper::toLowerCase(const wstring& s) +{ + wchar_t* charBuf = new wchar_t[s.length() + 1]; + memcpy(charBuf, s.c_str(), (s.length() + 1) * sizeof(wchar_t)); + errno_t err = _wcslwr_s(charBuf, s.length() + 1); + + wstring result = charBuf; + delete charBuf; + + if(err == 0) + return result; + else + return s; +} + +wstring StringHelper::toUpperCase(const wstring& s) +{ + wchar_t* charBuf = new wchar_t[s.length() + 1]; + memcpy(charBuf, s.c_str(), (s.length() + 1) * sizeof(wchar_t)); + errno_t err = _wcsupr_s(charBuf, s.length() + 1); + + wstring result = charBuf; + delete charBuf; + + if(err == 0) + return result; + else + return s; +} + +wstring StringHelper::trim(const wstring& s) +{ + int firstNonSpace = -1; + int lastNonSpace = -1; + + for(unsigned i=0;i StringHelper::split(const wstring& s, wchar_t splitChar, bool skipEmpty) +{ + vector result; + size_t prevPos = 0; + size_t pos = 0; + while((pos = s.find(splitChar, pos)) != wstring::npos) + { + wstring part = s.substr(prevPos, pos-prevPos); + if(part.length() > 0 || !skipEmpty) + result.push_back(part); + prevPos = ++pos; + } + + wstring part = s.substr(prevPos); + if(part.length() > 0 || !skipEmpty) + result.push_back(part); + + return result; +} + +wstring StringHelper::getSystemErrorString(long status) +{ + wchar_t* buf; + + if(FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, (LPTSTR)&buf, 0, NULL) != 0) + { + wstring result(buf); + LocalFree(buf); + + return result; + } + else + return L""; +} \ No newline at end of file diff --git a/helpers/StringHelper.h b/helpers/StringHelper.h new file mode 100644 index 0000000..f3fb5e1 --- /dev/null +++ b/helpers/StringHelper.h @@ -0,0 +1,37 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2013 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +class StringHelper +{ +public: + // replaces any occurrence of a character from chars in s with the replacement string + static std::wstring replaceCharacters(const std::wstring& s, const std::wstring& chars, const std::wstring& replacement); + static std::wstring replaceIllegalCharacters(const std::wstring& filename); + static std::wstring toWString(const std::string& s, unsigned codepage); + static std::wstring toLowerCase(const std::wstring& s); + static std::wstring toUpperCase(const std::wstring& s); + static std::wstring trim(const std::wstring& s); + static std::vector split(const std::wstring& s, wchar_t splitChar, bool skipEmpty = true); + static std::wstring getSystemErrorString(long status); +}; diff --git a/parser/RegexFunctions.cpp b/parser/RegexFunctions.cpp new file mode 100644 index 0000000..41ab865 --- /dev/null +++ b/parser/RegexFunctions.cpp @@ -0,0 +1,98 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "RegexFunctions.h" + +using namespace std; +using namespace mup; + +RegexSearchFunction::RegexSearchFunction() + :ICallback(cmFUNC, L"regexSearch", 2) +{ +} + +void RegexSearchFunction::Eval(ptr_val_type& ret, const ptr_val_type *arg, int argc) +{ + if(!arg[0]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[0]->GetType(), 's', 1)); + if(!arg[1]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[1]->GetType(), 's', 2)); + + wstring regexString = arg[0]->GetString(); + wstring string = arg[1]->GetString(); + + wregex regex(regexString); + wsmatch match; + bool found = regex_search(string, match, regex); + + vector result; + if(found) + { + for(unsigned i=0; iIsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[0]->GetType(), 's', 1)); + if(!arg[1]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[1]->GetType(), 's', 2)); + if(!arg[2]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[2]->GetType(), 's', 3)); + + wstring regexString = arg[0]->GetString(); + wstring string = arg[1]->GetString(); + wstring replacement = arg[2]->GetString(); + + wregex regex(regexString); + wstring result = regex_replace(string, regex, replacement); + + *ret = result; +} + +const char_type* RegexReplaceFunction::GetDesc() const +{ + return L"regexReplace - replaces all matches to regular expression. result is replaced string"; +} + +IToken* RegexReplaceFunction::Clone() const +{ + return new RegexReplaceFunction(*this); +} diff --git a/parser/RegexFunctions.h b/parser/RegexFunctions.h new file mode 100644 index 0000000..11bf792 --- /dev/null +++ b/parser/RegexFunctions.h @@ -0,0 +1,40 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +class RegexSearchFunction:public mup::ICallback +{ +public: + RegexSearchFunction(); + virtual void Eval(mup::ptr_val_type& ret, const mup::ptr_val_type *arg, int argc); + const mup::char_type* GetDesc() const; + virtual mup::IToken* Clone() const; +}; + +class RegexReplaceFunction:public mup::ICallback +{ +public: + RegexReplaceFunction(); + virtual void Eval(mup::ptr_val_type& ret, const mup::ptr_val_type *arg, int argc); + const mup::char_type* GetDesc() const; + virtual mup::IToken* Clone() const; +}; diff --git a/parser/RegistryFunctions.cpp b/parser/RegistryFunctions.cpp new file mode 100644 index 0000000..210f40a --- /dev/null +++ b/parser/RegistryFunctions.cpp @@ -0,0 +1,105 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2014 Jonas Thedering + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "RegistryFunctions.h" +#include "../helpers/RegistryHelper.h" +#include "../FilterEngine.h" + +using namespace std; +using namespace mup; + +ReadRegStringFunction::ReadRegStringFunction(FilterEngine* engine) + :ICallback(cmFUNC, L"readRegString", 2) +{ + this->engine = engine; +} + +void ReadRegStringFunction::Eval(ptr_val_type& ret, const ptr_val_type *arg, int argc) +{ + if(!arg[0]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[0]->GetType(), 's', 1)); + if(!arg[1]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[1]->GetType(), 's', 2)); + + wstring key = arg[0]->GetString(); + wstring valuename = arg[1]->GetString(); + + try + { + wstring value = RegistryHelper::readValue(key, valuename); + + *ret = value; + + engine->watchRegistryKey(key); + } + catch(RegistryException e) + { + throw ParserError(e.getMessage()); + } +} + +const char_type* ReadRegStringFunction::GetDesc() const +{ + return L"readRegString - read a string value from the registry"; +} + +IToken* ReadRegStringFunction::Clone() const +{ + return new ReadRegStringFunction(*this); +} + +ReadRegDWORDFunction::ReadRegDWORDFunction(FilterEngine* engine) + :ICallback(cmFUNC, L"readRegDWORD", 2) +{ + this->engine = engine; +} + +void ReadRegDWORDFunction::Eval(ptr_val_type& ret, const ptr_val_type *arg, int argc) +{ + if(!arg[0]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[0]->GetType(), 's', 1)); + if(!arg[1]->IsString()) + throw ParserError(ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg[1]->GetType(), 's', 2)); + + wstring key = arg[0]->GetString(); + wstring valuename = arg[1]->GetString(); + + try + { + unsigned long value = RegistryHelper::readDWORDValue(key, valuename); + + *ret = (long)value; + + engine->watchRegistryKey(key); + } + catch(RegistryException e) + { + throw ParserError(e.getMessage()); + } +} + +const char_type* ReadRegDWORDFunction::GetDesc() const +{ + return L"readRegDWORD - read a DWORD value from the registry"; +} + +IToken* ReadRegDWORDFunction::Clone() const +{ + return new ReadRegDWORDFunction(*this); +} diff --git a/parser/RegistryFunctions.h b/parser/RegistryFunctions.h new file mode 100644 index 0000000..dad73af --- /dev/null +++ b/parser/RegistryFunctions.h @@ -0,0 +1,48 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +class FilterEngine; + +class ReadRegStringFunction:public mup::ICallback +{ +public: + ReadRegStringFunction(FilterEngine* engine); + virtual void Eval(mup::ptr_val_type& ret, const mup::ptr_val_type *arg, int argc); + const mup::char_type* GetDesc() const; + virtual mup::IToken* Clone() const; + +private: + FilterEngine* engine; +}; + +class ReadRegDWORDFunction:public mup::ICallback +{ +public: + ReadRegDWORDFunction(FilterEngine* engine); + virtual void Eval(mup::ptr_val_type& ret, const mup::ptr_val_type *arg, int argc); + const mup::char_type* GetDesc() const; + virtual mup::IToken* Clone() const; + +private: + FilterEngine* engine; +}; diff --git a/parser/StringOperators.cpp b/parser/StringOperators.cpp new file mode 100644 index 0000000..471249b --- /dev/null +++ b/parser/StringOperators.cpp @@ -0,0 +1,56 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "StringOperators.h" + +using namespace std; +using namespace mup; + +void AddOperator::Eval(ptr_val_type& ret, const ptr_val_type *arg, int argc) +{ + if(!arg[0]->IsString() && !arg[1]->IsString()) + { + OprtAdd::Eval(ret, arg, argc); + return; + } + + wstring firstString; + if(arg[0]->IsString()) + firstString = arg[0]->GetString(); + else + firstString = arg[0]->ToString(); + + wstring secondString; + if(arg[1]->IsString()) + secondString = arg[1]->GetString(); + else + secondString = arg[1]->ToString(); + + *ret = firstString + secondString; +} + +const char_type* AddOperator::GetDesc() const +{ + return L"x+y - Concatenation of strings and addition of noncomplex values"; +} + +IToken* AddOperator::Clone() const +{ + return new AddOperator(*this); +} diff --git a/parser/StringOperators.h b/parser/StringOperators.h new file mode 100644 index 0000000..2c27015 --- /dev/null +++ b/parser/StringOperators.h @@ -0,0 +1,31 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2014 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +class AddOperator:public mup::OprtAdd +{ +public: + virtual void Eval(mup::ptr_val_type& ret, const mup::ptr_val_type *arg, int argc); + const mup::char_type* GetDesc() const; + virtual mup::IToken* Clone() const; +}; diff --git a/version.h b/version.h index bd1c320..3075d22 100644 --- a/version.h +++ b/version.h @@ -1,3 +1,3 @@ #define MAJOR 0 -#define MINOR 8 -#define REVISION 1 +#define MINOR 9 +#define REVISION 0