/* 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 #define WIN32_LEAN_AND_MEAN #include #include #include #include #include "StringHelper.h" #include "RegistryHelper.h" #include "LogHelper.h" #include "ParametricEQ.h" using namespace std; BiQuad::BiQuad(float dbGain, float freq, float srate, float bandwidthOrQ, bool isQ) { float A = pow(10, dbGain / 40); float omega = 2 * (float)M_PI * freq / srate; float sn = sin(omega); float cs = cos(omega); float alpha; if(isQ) alpha = sn / (2 * bandwidthOrQ); else alpha = sn * sinh((float)M_LN2/2 * bandwidthOrQ * omega / sn); float temp = 1 + (alpha /A); a0 = (1 + (alpha * A)) / temp; a[0] = (-2 * cs) / temp; 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() { channelCount = 0; 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); if(WaitForSingleObject(threadHandle, INFINITE) == WAIT_OBJECT_0) { TraceF(L"Successfully terminated directory change notification thread", configPath.c_str()); } 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; try { configPath = RegistryHelper::readValue(APP_REGPATH, L"ConfigPath"); } catch(RegistryException e) { LogF(L"Can't read config path because of: %s", e.getMessage()); 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 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(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'.'); 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) { 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(freqString[freqString.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) { 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; }