Added: Loudness correction filter by Alexander Walch that adjusts shelving filters based on the system volume. Also includes a GUI that allows calibration using a test sound.

This commit is contained in:
jthedering committed 2017-07-17 19:22:59 +00:00
1 parent 534b63da5f
commit ce857887af
33 files changed
+2146 -14

No files matched your search

+8
View File
@@ -65,6 +65,14 @@ public:
return result;
}
__forceinline
void setCoefficients(double ain[], const double& a0in)
{
for (int i = 0; i < 4; i++)
a[i] = ain[i];
a0 = a0in;
}
double gainAt(double freq, double srate);
private:
@@ -0,0 +1,243 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 "stdafx.h"
#include "LoudnessCorrectionFilter.h"
#include "helpers/MemoryHelper.h"
#include "VolumeController.h"
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES
#endif
#include <math.h>
LoudnessCorrectionFilter::LoudnessCorrectionFilter(const FilterParameters& fParameters)
{
_parameters = fParameters;
if (_parameters.attenuation > 1.0)
{
_parameters.attenuation = 1.0;
}
if (_parameters.attenuation < 0.0)
{
_parameters.attenuation = 0.0;
}
InitializeCriticalSection(&_parameterUpdateSection);
}
LoudnessCorrectionFilter::~LoudnessCorrectionFilter()
{
if (_stopParameterUpdateThreadEvent)
{
SetEvent(_stopParameterUpdateThreadEvent);
}
WaitForSingleObject(_parameterUpdateThreadHandle, INFINITE);
DeleteCriticalSection(&_parameterUpdateSection);
CloseHandle(_stopParameterUpdateThreadEvent);
CloseHandle(_parameterUpdateThreadHandle);
CloseHandle(_parameterchangedEvent);
}
std::vector<std::wstring> LoudnessCorrectionFilter::initialize(float sampleRate, unsigned maxFrameCount, std::vector<std::wstring> channelNames)
{
this->_channelCount = channelNames.size();
_lowShelfBiquads.resize(_channelCount);
_highShelfBiquads.resize(_channelCount);
_sampleRate = sampleRate;
_attFactor = 1.0;
for (unsigned i = 0; i < _channelCount; i++)
{
_lowShelfBiquads[i] = BiQuad(BiQuad::LOW_SHELF, 0, 75, _sampleRate, 1, false);
_highShelfBiquads[i] = BiQuad(BiQuad::HIGH_SHELF, 0, 10000, _sampleRate, 1, false);
}
_stopParameterUpdateThreadEvent = CreateEvent(NULL, true, false, NULL);
_parameterchangedEvent = CreateEvent(NULL, true, false, NULL);
_parameterUpdateThreadHandle = CreateThread(NULL, 0, &parameterUpdateThread, this, 0, NULL);
return channelNames;
}
void LoudnessCorrectionFilter::getLShelfParamter(const float& volume, double& frequence, double& q, double& gain, double& preAmp)
{
frequence = 75;
q = 0.52;
float volDiff = _parameters.referenceLevel - _parameters.referenceOffset - volume;
if (volDiff > 0)
{
// old: gain=volDiff*0.55*_parameters.attenuation;
gain = volDiff * 0.55 / (1 - 0.55) * _parameters.attenuation;
preAmp = -gain;
}
else if (volDiff < 0)
{
preAmp = 0.0;
gain = volDiff * 0.55 * exp(volDiff / 90.0) * _parameters.attenuation;
}
else
{
gain = 0;
}
}
void LoudnessCorrectionFilter::getHShelfParamter(const float& volume, double& frequence, double& q, double& gain)
{
frequence = 10000;
q = 0.9;
float volDiff = _parameters.referenceLevel - _parameters.referenceOffset - volume;
if (volDiff > 0)
{
gain = volDiff * 0.225 * exp(-volDiff / 100.0) * _parameters.attenuation;
}
else if (volDiff < 0)
{
gain = volDiff * 0.175 * exp(volDiff / 80.0) * _parameters.attenuation;
}
else
{
gain = 0;
}
}
unsigned long __stdcall LoudnessCorrectionFilter::parameterUpdateThread(void* parameter)
{
LoudnessCorrectionFilter* lCorrection = (LoudnessCorrectionFilter*)parameter;
VolumeController VolumeController;
float volOld(lCorrection->_parameters.referenceLevel);
float vol(lCorrection->_parameters.referenceLevel);
double freqLS, qLS, gainLS, preAmp;
double freqHS, qHS, gainHS;
HRESULT res;
while (WaitForSingleObject(lCorrection->_stopParameterUpdateThreadEvent, 0) == WAIT_TIMEOUT)
{
if (WaitForSingleObject(lCorrection->_parameterchangedEvent, 0) == WAIT_TIMEOUT)
{
res = VolumeController.getVolume(vol);
if (res == S_OK)
{
if (vol != volOld)
{
lCorrection->getLShelfParamter(vol, freqLS, qLS, gainLS, preAmp);
lCorrection->_attFactor = exp(preAmp / 6 * log(2));
lCorrection->getHShelfParamter(vol + (float)preAmp, freqHS, qHS, gainHS);
lCorrection->upDateBiquadCoefficients(freqHS, qHS, gainHS, true);
lCorrection->upDateBiquadCoefficients(freqLS, qLS, gainLS, false);
volOld = vol;
lCorrection->_neutralUpDate = std::max<double>(std::abs(gainLS), std::abs(gainHS)) < 0.2 ? true : false;
SetEvent(lCorrection->_parameterchangedEvent);
}
}
}
Sleep(10);
// ==========================
}
return 0;
}
bool LoudnessCorrectionFilter::upDateNeutral()
{
return _neutralUpDate;
}
#pragma AVRT_CODE_BEGIN
void LoudnessCorrectionFilter::process(float** output, float** input, unsigned frameCount)
{
if (_parameters.state == false)
{
for (unsigned int j = 0; j < frameCount; j++)
{
for (unsigned i = 0; i < _channelCount; i++)
{
output[i][j] = input[i][j];
}
}
output = input;
return;
}
if (WaitForSingleObject(_parameterchangedEvent, 0) == WAIT_OBJECT_0)
{
for (unsigned i = 0; i < _channelCount; i++)
{
_lowShelfBiquads[i].setCoefficients(_aLS, _a0LS);
_highShelfBiquads[i].setCoefficients(_aHS, _a0HS);
}
_neutral = upDateNeutral();
ResetEvent(_parameterchangedEvent);
}
for (unsigned i = 0; i < _channelCount; i++)
{
float* inputChannel = input[i];
float* outputChannel = output[i];
for (unsigned j = 0; j < frameCount; j++)
{
_tempResult = _lowShelfBiquads[i].process(inputChannel[j]);
_lowShelfBiquads[i].removeDenormals();
_tempResult *= _attFactor;
outputChannel[j] = (float)_highShelfBiquads[i].process(_tempResult);
_highShelfBiquads[i].removeDenormals();
// if there is nearly no loudness correction necessary => set output=input to achive best quality
if (_neutral)
{
outputChannel[j] = inputChannel[j];
}
}
}
}
void LoudnessCorrectionFilter::upDateBiquadCoefficients(const double& freq, const double& bandwidthOrQOrS, const double& dbGain, bool highshelf)
{
double A;
A = pow(10, dbGain / 40);
double omega = 2 * M_PI * freq / _sampleRate;
double sn = sin(omega);
double cs = cos(omega);
double alpha;
double beta = -1;
alpha = sn / 2 * sqrt((A + 1 / A) * (1 / bandwidthOrQOrS - 1) + 2);
beta = 2 * sqrt(A) * alpha;
double a0;
TryEnterCriticalSection(&_parameterUpdateSection);
if (highshelf)
{
a0 = (A + 1) - (A - 1) * cs + beta;
_a0HS = (float)((A * ((A + 1) + (A - 1) * cs + beta)) / a0);
_aHS[0] = (float)((-2 * A * ((A - 1) + (A + 1) * cs)) / a0);
_aHS[1] = (float)((A * ((A + 1) + (A - 1) * cs - beta)) / a0);
_aHS[2] = (float)((2 * ((A - 1) - (A + 1) * cs)) / a0);
_aHS[3] = (float)(((A + 1) - (A - 1) * cs - beta) / a0);
}
else
{
a0 = (A + 1) + (A - 1) * cs + beta;
_a0LS = (float)((A * ((A + 1) - (A - 1) * cs + beta)) / a0);
_aLS[0] = (float)((2 * A * ((A - 1) - (A + 1) * cs)) / a0);
_aLS[1] = (float)((A * ((A + 1) - (A - 1) * cs - beta)) / a0);
_aLS[2] = (float)((-2 * ((A - 1) + (A + 1) * cs)) / a0);
_aLS[3] = (float)(((A + 1) + (A - 1) * cs - beta) / a0);
}
LeaveCriticalSection(&_parameterUpdateSection);
}
#pragma AVRT_CODE_END
@@ -0,0 +1,110 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 "ParameterArchive.h"
#include <IFilter.h>
#include <filters/BiQuad.h>
#include <regex>
#include <sstream>
#pragma AVRT_VTABLES_BEGIN
class LoudnessCorrectionFilter : public IFilter
{
public:
struct FilterParameters
{
bool state;
float referenceLevel;
float referenceOffset;
float attenuation;
std::vector<char> serialize()
{
ParameterArchive archive; // possibility to get string from name of variable: std::string name=NAME(on);
archive.add(state, L"State");
archive.add(referenceLevel, L"ReferenceLevel");
archive.add(referenceOffset, L"ReferenceOffset");
archive.add(attenuation, L"Attenuation");
return archive.getSerializedParameters();
}
template<typename T> bool deSerialize(const T& parameters)
{
ParameterArchive archive(parameters);
int error(0);
error += archive.get(state, std::wregex(L"\\s*State\\s+(0|1)"));
error += archive.get(referenceLevel, std::wregex(L"\\s*ReferenceLevel\\s+([-+0-9]+)"));
error += archive.get(referenceOffset, std::wregex(L"\\s*ReferenceOffset\\s+([-+0-9]+)"));
// attenuation is only an optional parameter
int errorAtt = archive.get(attenuation, std::wregex(L"\\s*Attenuation\\s+((1((\\.|,)0+)?)|(0((\\.|,)[0-9]+)?))"));
if (errorAtt > 0)
{
attenuation = 1.0;
}
return error == 0 ? false : true;
}
FilterParameters()
: _isInitialized(false) {};
template<typename T> FilterParameters(T input)
{
_isInitialized = !deSerialize<T>(input);
}
bool isInitialized()
{
return _isInitialized;
}
private:
bool _isInitialized;
};
LoudnessCorrectionFilter(const FilterParameters& fParameters);
virtual ~LoudnessCorrectionFilter();
virtual bool getInPlace() {return true;}
virtual std::vector<std::wstring> initialize(float sampleRate, unsigned maxFrameCount, std::vector<std::wstring> channelNames);
virtual void process(float** output, float** input, unsigned frameCount);
private:
void getLShelfParamter(const float& volume, double& frequence, double& q, double& gain, double& preAmp);
void getHShelfParamter(const float& volume, double& frequence, double& q, double& gain);
void upDateBiquadCoefficients(const double& freq, const double& bandwidthOrQOrS, const double& dbGain, bool highshelf);
bool upDateNeutral();
void* _parameterUpdateThreadHandle;
static unsigned long __stdcall parameterUpdateThread(void* parameter);
void* _stopParameterUpdateThreadEvent;
void* _parameterchangedEvent;
CRITICAL_SECTION _parameterUpdateSection;
FilterParameters _parameters;
size_t _channelCount;
float _sampleRate;
double _attFactor;
std::vector<BiQuad> _lowShelfBiquads;
std::vector<BiQuad> _highShelfBiquads;
bool _neutral;
bool _neutralUpDate;
double _tempResult;
double _aLS[4];
double _a0LS;
double _aHS[4];
double _a0HS;
};
#pragma AVRT_VTABLES_END
@@ -0,0 +1,54 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 "stdafx.h"
#define _USE_MATH_DEFINES
#include <cmath>
#include <regex>
#include <sstream>
#include "helpers/MemoryHelper.h"
#include "helpers/StringHelper.h"
#include "helpers/LogHelper.h"
#include "LoudnessCorrectionFilter.h"
#include "LoudnessCorrectionFilterFactory.h"
using namespace std;
LoudnessCorrectionFilterFactory::LoudnessCorrectionFilterFactory()
{
}
vector<IFilter*> LoudnessCorrectionFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters)
{
vector<IFilter*> allFilter(0);
if (command == L"LoudnessCorrection")
{
LoudnessCorrectionFilter::FilterParameters filterParameters(parameters);
if (filterParameters.isInitialized())
{
TraceF(L"Adding loudness correction filter");
void* mem = MemoryHelper::alloc(sizeof(LoudnessCorrectionFilter));
allFilter.push_back(new(mem) LoudnessCorrectionFilter(filterParameters));
}
}
return allFilter;
}
@@ -0,0 +1,33 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 <string>
#include <hash_map>
#include "IFilterFactory.h"
#include "IFilter.h"
class LoudnessCorrectionFilterFactory : public IFilterFactory
{
public:
LoudnessCorrectionFilterFactory();
virtual std::vector<IFilter*> createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters);
};
@@ -0,0 +1,169 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 <regex>
#include <string>
#include <vector>
#include <helpers/StringHelper.h>
template<typename type> struct to_WString_type_traits
{
static inline std::wstring cast_ToWString(type& input) {return std::wstring();}
};
template<> struct to_WString_type_traits<float>
{
static inline std::wstring cast_ToWString(const float& input) {return std::to_wstring((long double)input);}
};
template<> struct to_WString_type_traits<double>
{
static inline std::wstring cast_ToWString(const double& input) {return std::to_wstring((long double)input);}
};
template<> struct to_WString_type_traits<int>
{
static inline std::wstring cast_ToWString(const int& input) {return std::to_wstring((_Longlong)input);}
};
template<> struct to_WString_type_traits<bool>
{
static inline std::wstring cast_ToWString(const bool& input) {return std::to_wstring((_Longlong)input);}
};
template<> struct to_WString_type_traits<std::string>
{
static inline std::wstring cast_ToWString(const std::string& input) {return StringHelper::toWString(input, CP_ACP);}
};
template<> struct to_WString_type_traits<std::wstring>
{
static inline std::wstring cast_ToWString(const std::wstring& input) {return input;}
};
template<typename type> struct from_WString_type_traits
{
static inline type cast_fromWString(const std::wstring& input) {return (type)wcstod(input.c_str(), NULL);}
};
template<> struct from_WString_type_traits<float>
{
static inline float cast_fromWString(const std::wstring& input) {return (float)wcstod(input.c_str(), NULL);}
};
template<> struct from_WString_type_traits<double>
{
static inline double cast_fromWString(const std::wstring& input) {return wcstod(input.c_str(), NULL);}
};
template<> struct from_WString_type_traits<int>
{
static inline int cast_fromWString(const std::wstring& input) {return (int)wcstol(input.c_str(), NULL, 10);}
};
template<> struct from_WString_type_traits<bool>
{
static inline bool cast_fromWString(const std::wstring& input) {return wcstol(input.c_str(), NULL, 10) == 0 ? false : true;}
};
template<> struct from_WString_type_traits<std::wstring>
{
static inline std::wstring cast_fromWString(const std::wstring& input) {return input;}
};
template<typename type> struct constructor_traits
{
static inline std::wstring initFrom(type input) {return std::wstring();}
};
template<> struct constructor_traits<std::wstring>
{
static inline std::wstring initFrom(std::wstring input) {return input;}
};
template<> struct constructor_traits<std::vector<char> >
{
static inline std::wstring initFrom(std::vector<char> input)
{
input.push_back(0);
std::string tempParams(&input[0]);
return to_WString_type_traits<std::string>::cast_ToWString(tempParams);
}
};
class ParameterArchive
{
public:
ParameterArchive()
: _serializedParamters(std::wstring())
{
}
template<typename T> ParameterArchive(T input)
{
_serializedParamters = constructor_traits<T>::initFrom(input);
}
std::vector<char> getSerializedParameters()
{
size_t noBytes = _serializedParamters.length() * sizeof(std::wstring);
std::vector<char> outputParameter(noBytes);
CopyMemory(&outputParameter[0], reinterpret_cast<char*>(&_serializedParamters[0]), noBytes);
return outputParameter;
}
template<typename T, typename U> void add(const T& parameter, const U& name)
{
std::wstring nameStr = to_WString_type_traits<U>::cast_ToWString(name);
if (!nameStr.empty())
{
_serializedParamters += nameStr;
_serializedParamters += L" ";
}
_serializedParamters += to_WString_type_traits<T>::cast_ToWString(parameter);
_serializedParamters += L" ";
};
template<typename T> int get(T& parameter, const std::wregex& searchArgument)
{
std::wsmatch match;
bool found = regex_search(_serializedParamters, match, searchArgument);
if (!found)
{
return 1;
}
std::vector<std::wstring> splittedStrings = StringHelper::split(match.str(1), L' ');
if (splittedStrings.empty())
{
return 1;
}
parameter = from_WString_type_traits<T>::cast_fromWString(splittedStrings.back());
return 0;
};
bool find(const std::wregex& searchArgument)
{
std::wsmatch match;
return regex_search(_serializedParamters, match, searchArgument);
}
private:
std::wstring _serializedParamters;
};
@@ -0,0 +1,55 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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 "stdafx.h"
#include "VolumeController.h"
#include <mmdeviceapi.h>
VolumeController::VolumeController()
{
HRESULT hr;
CoInitialize(NULL);
IMMDeviceEnumerator* deviceEnumerator = NULL;
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator);
IMMDevice* defaultDevice = NULL;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &defaultDevice);
deviceEnumerator->Release();
deviceEnumerator = NULL;
_endpointVolume = NULL;
hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&_endpointVolume);
defaultDevice->Release();
defaultDevice = NULL;
float inkrement;
_endpointVolume->GetVolumeRange(&_minVol, &_maxVol, &inkrement);
}
HRESULT VolumeController::getVolume(float& currentVolume)
{
HRESULT res = _endpointVolume->GetMasterVolumeLevel(&currentVolume);
return res;
}
HRESULT VolumeController::setVolume(float volume)
{
volume = min(volume, _maxVol);
volume = max(volume, _minVol);
return _endpointVolume->SetMasterVolumeLevel(volume, NULL);
}
@@ -0,0 +1,36 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Alexander Walch
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
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <EndpointVolume.h>
class VolumeController
{
public:
VolumeController();
HRESULT getVolume(float& currentVolume);
HRESULT setVolume(float volume);
private:
IAudioEndpointVolume* _endpointVolume;
float _minVol;
float _maxVol;
};