From fb5020e72fecd72be17c6b5a01244e843e9becde Mon Sep 17 00:00:00 2001 From: jthedering <51533355+jthedering@users.noreply.github.com> Date: Sun, 14 Apr 2013 19:15:57 +0000 Subject: [PATCH] Added: Support for independent filter sets per channel. The new command "Channel" allows to specify the channel(s) to which the following "Filter" and "Preamp" commands should apply. --- Benchmark/Benchmark.cpp | 7 +- EqualizerAPO.cpp | 33 +++++- ParametricEQ.cpp | 255 ++++++++++++++++++++++++++++++++-------- ParametricEQ.h | 67 +++++++++-- 4 files changed, 295 insertions(+), 67 deletions(-) diff --git a/Benchmark/Benchmark.cpp b/Benchmark/Benchmark.cpp index 78e094e..fb2e206 100644 --- a/Benchmark/Benchmark.cpp +++ b/Benchmark/Benchmark.cpp @@ -34,6 +34,7 @@ int main(int argc, char** argv) { unsigned sampleRate; unsigned channelCount; + unsigned channelMask; unsigned frameCount; float* buf; @@ -50,6 +51,7 @@ int main(int argc, char** argv) sampleRate = info.samplerate; channelCount = info.channels; + channelMask = 0; frameCount = (unsigned)info.frames; buf = new float[frameCount * channelCount]; @@ -65,11 +67,12 @@ int main(int argc, char** argv) { sampleRate = 44100; channelCount = 1; - frameCount = 8820000; + channelMask = 0; float sweepFrom = 0.1f; float sweepTo = 20000; float sweepDiff = sweepTo - sweepFrom; float length = 200; + frameCount = (unsigned)(length * sampleRate); printf("No input file given, so generating linear sine sweep from %g to %g Hz over %g seconds\n", sweepFrom, sweepTo, length); @@ -88,7 +91,7 @@ int main(int argc, char** argv) ParametricEQ peq; peq.setDeviceInfo(L"Benchmark", L"File output", L""); - peq.initialize((float)sampleRate, channelCount); + peq.initialize((float)sampleRate, channelCount, channelMask); PrecisionTimer timer; timer.start(); diff --git a/EqualizerAPO.cpp b/EqualizerAPO.cpp index 3bcbb3d..53a2fd2 100644 --- a/EqualizerAPO.cpp +++ b/EqualizerAPO.cpp @@ -195,6 +195,18 @@ HRESULT EqualizerAPO::IsInputFormatSupported(IAudioMediaType* pOutputFormat, TraceF(L"RequestedInputFormat = { %08X, %u, %u, %u, %f, %08X }", inFormat.guidFormatType.Data1, inFormat.dwSamplesPerFrame, inFormat.dwBytesPerSampleContainer, inFormat.dwValidBitsPerSample, inFormat.fFramesPerSecond, inFormat.dwChannelMask); + + UNCOMPRESSEDAUDIOFORMAT outFormat; + hr = pOutputFormat->GetUncompressedAudioFormat(&outFormat); + if(FAILED(hr)) + { + 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) { @@ -270,15 +282,30 @@ HRESULT EqualizerAPO::LockForProcess(UINT32 u32NumInputConnections, return hr; } - UNCOMPRESSEDAUDIOFORMAT uncompAudioFormat; - hr = ppOutputConnections[0]->pFormat->GetUncompressedAudioFormat(&uncompAudioFormat); + UNCOMPRESSEDAUDIOFORMAT inFormat; + hr = ppInputConnections[0]->pFormat->GetUncompressedAudioFormat(&inFormat); if(FAILED(hr)) { LogF(L"Error in GetUncompressedAudioFormat in LockForProcess"); return hr; } - peq.initialize(uncompAudioFormat.fFramesPerSecond, uncompAudioFormat.dwSamplesPerFrame); + 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); + peq.initialize(outFormat.fFramesPerSecond, outFormat.dwSamplesPerFrame, outFormat.dwChannelMask); return hr; } diff --git a/ParametricEQ.cpp b/ParametricEQ.cpp index 33098a6..e5fe93c 100644 --- a/ParametricEQ.cpp +++ b/ParametricEQ.cpp @@ -26,6 +26,8 @@ #define WIN32_LEAN_AND_MEAN #include #include +#include +#include #include "StringHelper.h" #include "RegistryHelper.h" @@ -52,20 +54,30 @@ BiQuad::BiQuad(float dbGain, float freq, float srate, float bandwidthOrQ, bool i a[1] = (1 - (alpha * A)) / temp; a[2] = - (-2 * cs) / temp; a[3] = - (1 - (alpha /A)) / temp; + + x1 = 0; + x2 = 0; + y1 = 0; + y2 = 0; +} + +ChannelData::ChannelData() +{ + preamp = 1.0f; + filterCount = 0; } ParametricEQ::ParametricEQ() { - filterCount = 0; channelCount = 0; - preamp = 1.0f; - memset(sampleData, 0, 1000 * sizeof(float)); + channelData = NULL; lastInputWasSilent = false; threadHandle = NULL; } ParametricEQ::~ParametricEQ() { + // Make sure notification thread is terminated before cleaning up, otherwise deleted memory might be accessed in loadConfig if(threadHandle != NULL) { SetEvent(shutdownEvent); @@ -77,6 +89,12 @@ ParametricEQ::~ParametricEQ() CloseHandle(threadHandle); threadHandle = NULL; } + + if(channelData != NULL) + { + delete[] channelData; + channelData = NULL; + } } void ParametricEQ::setDeviceInfo(const wstring& deviceName, const wstring& connectionName, const wstring& deviceGuid) @@ -86,13 +104,34 @@ void ParametricEQ::setDeviceInfo(const wstring& deviceName, const wstring& conne this->deviceGuid = deviceGuid; } -void ParametricEQ::initialize(float sampleRate, unsigned channelCount) +void ParametricEQ::initialize(float sampleRate, unsigned channelCount, unsigned channelMask) { this->sampleRate = sampleRate; this->channelCount = channelCount; - memset(sampleData, 0, 1000 * sizeof(float)); - filterCount = 0; - preamp = 1.0f; + 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; try { @@ -123,17 +162,27 @@ void ParametricEQ::initialize(float sampleRate, unsigned channelCount) void ParametricEQ::loadConfig() { - unsigned loadFilterCount = 0; - float loadPreamp = 1.0f; + for(unsigned c=0; c selectedChannels(channelCount, true); + loadConfig(configPath + L"\\config.txt", selectedChannels); + + unsigned loadFilterCount = 0; + for(unsigned c=0; c selectedChannels) { TraceF(L"Loading configuration from %s", path.c_str()); @@ -188,7 +237,7 @@ void ParametricEQ::loadConfig(wstring path, unsigned& loadFilterCount, float& lo } else { - currentWord += value[i]; + currentWord += c; } } @@ -222,31 +271,120 @@ void ParametricEQ::loadConfig(wstring path, unsigned& loadFilterCount, float& lo if(!deviceMatches) continue; - if(key.find(L"Filter") == 0) + if(key == L"Channel") + { + selectedChannels = vector(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(currentWord == L"l") + channelPos = SPEAKER_FRONT_LEFT; + else if(currentWord == L"r") + channelPos = SPEAKER_FRONT_RIGHT; + else if(currentWord == L"c") + channelPos = SPEAKER_FRONT_CENTER; + else if(currentWord == L"sub") + channelPos = SPEAKER_LOW_FREQUENCY; + else if(currentWord == L"rl") + channelPos = SPEAKER_BACK_LEFT; + else if(currentWord == L"rr") + channelPos = SPEAKER_BACK_RIGHT; + else if(currentWord == L"rc") + channelPos = SPEAKER_BACK_CENTER; + else if(currentWord == L"sl") + channelPos = SPEAKER_SIDE_LEFT; + else if(currentWord == L"sr") + channelPos = SPEAKER_SIDE_RIGHT; + 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 channelStream; + for(unsigned c=0; c 0) + channelStream << L", "; + channelStream << c+1; + } + } + + TraceF(L"Selecting channel(s) %s", channelStream.str().c_str()); + } + else if(key.find(L"Filter") == 0) { //Conversion to period as decimal mark, if needed value = StringHelper::replaceCharacters(value, L",", L'.'); - if(loadFilterCount < (sizeof(filters)/sizeof(BiQuad))) + wchar_t freqString[10]; + float freq, gain, bandwidth; + + int matched = swscanf_s(value.c_str(), L" ON PEQ Fc %9s Hz Gain %f dB BW Oct %f", &freqString, 10, &gain, &bandwidth); + if(matched == 3 && (freq = getFreq(freqString)) != -1.0f) { - wchar_t freqString[10]; - float freq, gain, bandwidth; - - int matched = swscanf_s(value.c_str(), L" ON PEQ Fc %9s Hz Gain %f dB BW Oct %f", &freqString, 10, &gain, &bandwidth); + for(unsigned c=0; c +#include + +#define IS_DENORMAL(f) (((*(unsigned int *)&(f))&0x7f800000) == 0) struct BiQuad { - BiQuad() {} - BiQuad(float dbGain, float freq, float srate, float bandwidthOrQ, bool isQ); - - __declspec(align(16)) float a[4]; + BiQuad() {} + BiQuad(float dbGain, float freq, float srate, float bandwidthOrQ, bool isQ); + + __forceinline + void removeDenormals() + { + if(IS_DENORMAL(x1)) + x1 = 0.0f; + if(IS_DENORMAL(x2)) + x2 = 0.0f; + if(IS_DENORMAL(y1)) + y1 = 0.0f; + if(IS_DENORMAL(y2)) + y2 = 0.0f; + } + + __forceinline + float process(float sample) + { + float result = a0 * sample + a[0] * x1 + a[1] * x2 + + a[2] * y1 + a[3] * y2; + + x2 = x1; + x1 = sample; + + y2 = y1; + y1 = result; + + //Input for next stage + return result; + } + + __declspec(align(16)) float a[4]; float a0; + + float x1, x2; + float y1, y2; +}; + +struct ChannelData +{ + ChannelData(); + + float preamp; + unsigned filterCount; + BiQuad filters[100]; + + // used while loading instead of overwriting immediately + float loadPreamp; + unsigned loadFilterCount; }; class ParametricEQ @@ -37,13 +85,14 @@ public: ~ParametricEQ(); void setDeviceInfo(const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid); - void initialize(float sampleRate, unsigned channelCount); + void initialize(float sampleRate, unsigned channelCount, unsigned channelMask); void loadConfig(); void process(float *output, float *input, unsigned frameCount); private: - void loadConfig(std::wstring path, unsigned& loadFilterCount, float& loadPreamp); + 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; @@ -52,10 +101,8 @@ private: std::wstring configPath; float sampleRate; unsigned channelCount; - float preamp; - BiQuad filters[100]; - unsigned filterCount; - float sampleData[2000]; + unsigned channelMask; + ChannelData* channelData; bool lastInputWasSilent; void* threadHandle; void* shutdownEvent;