From ce857887af10204a8780e889158d288b33468d8f Mon Sep 17 00:00:00 2001 From: jthedering <51533355+jthedering@users.noreply.github.com> Date: Mon, 17 Jul 2017 19:22:59 +0000 Subject: [PATCH] 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. --- Benchmark/Benchmark.vcxproj.filters | 6 +- Common.vcxproj | 10 + Common.vcxproj.filters | 27 ++ Editor/Editor.pro | 25 +- Editor/Editor.qrc | 1 + Editor/FilterTable.cpp | 8 + Editor/FilterTable.h | 2 + Editor/MainWindow.h | 2 +- Editor/guis/LoudnessCorrectionFilterGUI.cpp | 110 +++++ Editor/guis/LoudnessCorrectionFilterGUI.h | 55 +++ Editor/guis/LoudnessCorrectionFilterGUI.ui | 401 ++++++++++++++++++ .../LoudnessCorrectionFilterGUIDialog.cpp | 106 +++++ .../guis/LoudnessCorrectionFilterGUIDialog.h | 50 +++ .../guis/LoudnessCorrectionFilterGUIDialog.ui | 274 ++++++++++++ .../LoudnessCorrectionFilterGUIFactory.cpp | 93 ++++ .../guis/LoudnessCorrectionFilterGUIFactory.h | 47 ++ Editor/helpers/QtSndfileHandle.cpp | 66 +++ Editor/helpers/QtSndfileHandle.h | 31 ++ Editor/sounds/pinkNoise.flac | Bin 0 -> 86764 bytes Editor/translations/Editor_de.qm | Bin 26241 -> 34220 bytes Editor/translations/Editor_de.ts | 126 +++++- EqualizerAPO/EqualizerAPO.vcxproj | 2 +- EqualizerAPO/EqualizerAPO.vcxproj.filters | 2 +- FilterEngine.cpp | 2 + .../VoicemeeterClient.vcxproj.filters | 6 +- filters/BiQuad.h | 8 + .../LoudnessCorrectionFilter.cpp | 243 +++++++++++ .../LoudnessCorrectionFilter.h | 110 +++++ .../LoudnessCorrectionFilterFactory.cpp | 54 +++ .../LoudnessCorrectionFilterFactory.h | 33 ++ filters/loudnessCorrection/ParameterArchive.h | 169 ++++++++ .../loudnessCorrection/VolumeController.cpp | 55 +++ filters/loudnessCorrection/VolumeController.h | 36 ++ 33 files changed, 2146 insertions(+), 14 deletions(-) create mode 100644 Editor/guis/LoudnessCorrectionFilterGUI.cpp create mode 100644 Editor/guis/LoudnessCorrectionFilterGUI.h create mode 100644 Editor/guis/LoudnessCorrectionFilterGUI.ui create mode 100644 Editor/guis/LoudnessCorrectionFilterGUIDialog.cpp create mode 100644 Editor/guis/LoudnessCorrectionFilterGUIDialog.h create mode 100644 Editor/guis/LoudnessCorrectionFilterGUIDialog.ui create mode 100644 Editor/guis/LoudnessCorrectionFilterGUIFactory.cpp create mode 100644 Editor/guis/LoudnessCorrectionFilterGUIFactory.h create mode 100644 Editor/helpers/QtSndfileHandle.cpp create mode 100644 Editor/helpers/QtSndfileHandle.h create mode 100644 Editor/sounds/pinkNoise.flac create mode 100644 filters/loudnessCorrection/LoudnessCorrectionFilter.cpp create mode 100644 filters/loudnessCorrection/LoudnessCorrectionFilter.h create mode 100644 filters/loudnessCorrection/LoudnessCorrectionFilterFactory.cpp create mode 100644 filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h create mode 100644 filters/loudnessCorrection/ParameterArchive.h create mode 100644 filters/loudnessCorrection/VolumeController.cpp create mode 100644 filters/loudnessCorrection/VolumeController.h diff --git a/Benchmark/Benchmark.vcxproj.filters b/Benchmark/Benchmark.vcxproj.filters index 9eead87..d4a0a88 100644 --- a/Benchmark/Benchmark.vcxproj.filters +++ b/Benchmark/Benchmark.vcxproj.filters @@ -13,12 +13,12 @@ helpers - - - {8c70f87a-7d86-4826-bfa1-7a4c4ec5efec} + + + \ No newline at end of file diff --git a/Common.vcxproj b/Common.vcxproj index 0e43989..d5daff0 100644 --- a/Common.vcxproj +++ b/Common.vcxproj @@ -158,6 +158,10 @@ + + + + @@ -208,6 +212,9 @@ + + + @@ -234,6 +241,9 @@ Create + + + diff --git a/Common.vcxproj.filters b/Common.vcxproj.filters index 1d29d20..690105f 100644 --- a/Common.vcxproj.filters +++ b/Common.vcxproj.filters @@ -129,6 +129,18 @@ + + filters\loudnessCorrection + + + filters\loudnessCorrection + + + filters\loudnessCorrection + + + filters\loudnessCorrection + @@ -246,6 +258,15 @@ + + filters\loudnessCorrection + + + filters\loudnessCorrection + + + filters\loudnessCorrection + @@ -260,5 +281,11 @@ {4a6738bc-7359-4b18-bdb4-1f443f41e528} + + {c473f978-6002-42aa-adf5-3b5f10868d2d} + + + + \ No newline at end of file diff --git a/Editor/Editor.pro b/Editor/Editor.pro index 8f58c3f..0f76788 100644 --- a/Editor/Editor.pro +++ b/Editor/Editor.pro @@ -124,7 +124,14 @@ SOURCES += main.cpp\ guis/VSTPluginFilterGUIDialog.cpp \ ../filters/VSTPluginFilter.cpp \ ../filters/VSTPluginFilterFactory.cpp \ - ../helpers/VSTPluginInstance.cpp + ../helpers/VSTPluginInstance.cpp \ + guis/LoudnessCorrectionFilterGUI.cpp \ + guis/LoudnessCorrectionFilterGUIFactory.cpp \ + ../filters/loudnessCorrection/LoudnessCorrectionFilter.cpp \ + ../filters/loudnessCorrection/LoudnessCorrectionFilterFactory.cpp \ + ../filters/loudnessCorrection/VolumeController.cpp \ + guis/LoudnessCorrectionFilterGUIDialog.cpp \ + helpers/QtSndfileHandle.cpp HEADERS += \ ../helpers/LogHelper.h \ @@ -237,7 +244,15 @@ HEADERS += \ guis/VSTPluginFilterGUIDialog.h \ ../filters/VSTPluginFilter.h \ ../filters/VSTPluginFilterFactory.h \ - ../helpers/VSTPluginInstance.h + ../helpers/VSTPluginInstance.h \ + guis/LoudnessCorrectionFilterGUI.h \ + guis/LoudnessCorrectionFilterGUIFactory.h \ + ../filters/loudnessCorrection/LoudnessCorrectionFilter.h \ + ../filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h \ + ../filters/loudnessCorrection/ParameterArchive.h \ + ../filters/loudnessCorrection/VolumeController.h \ + guis/LoudnessCorrectionFilterGUIDialog.h \ + helpers/QtSndfileHandle.h FORMS += \ guis/PreampFilterGUI.ui \ @@ -257,10 +272,12 @@ FORMS += \ MainWindow.ui \ guis/StageFilterGUI.ui \ guis/VSTPluginFilterGUI.ui \ - guis/VSTPluginFilterGUIDialog.ui + guis/VSTPluginFilterGUIDialog.ui \ + guis/LoudnessCorrectionFilterGUI.ui \ + guis/LoudnessCorrectionFilterGUIDialog.ui INCLUDEPATH += $$PWD/.. "C:/Program Files/libsndfile/include" "C:/Program Files/fftw3" "C:/Program Files/muparserx_v3_0_1/parser" -LIBS += user32.lib advapi32.lib version.lib ole32.lib Shlwapi.lib authz.lib crypt32.lib dbghelp.lib libsndfile-1.lib libfftw3f-3.lib +LIBS += user32.lib advapi32.lib version.lib ole32.lib Shlwapi.lib authz.lib crypt32.lib dbghelp.lib winmm.lib libsndfile-1.lib libfftw3f-3.lib build_pass:CONFIG(debug, debug|release) { LIBS += muparserxd.lib diff --git a/Editor/Editor.qrc b/Editor/Editor.qrc index b8f9f64..1229442 100644 --- a/Editor/Editor.qrc +++ b/Editor/Editor.qrc @@ -23,5 +23,6 @@ icons/normalize_response.ico icons/reset_response.ico icons/resize_corner.ico + sounds/pinkNoise.flac diff --git a/Editor/FilterTable.cpp b/Editor/FilterTable.cpp index 7e1065f..eace1f3 100644 --- a/Editor/FilterTable.cpp +++ b/Editor/FilterTable.cpp @@ -49,6 +49,7 @@ #include "guis/GraphicEQFilterGUIFactory.h" #include "guis/ConvolutionFilterGUIFactory.h" #include "guis/VSTPluginFilterGUIFactory.h" +#include "guis/LoudnessCorrectionFilterGUIFactory.h" #include "Editor/helpers/DPIHelper.h" #include "helpers/StringHelper.h" #include "helpers/LogHelper.h" @@ -81,6 +82,7 @@ FilterTable::FilterTable(MainWindow* mainWindow, QWidget* parent) factories.append(new GraphicEQFilterGUIFactory); factories.append(new ConvolutionFilterGUIFactory); factories.append(new VSTPluginFilterGUIFactory); + factories.append(new LoudnessCorrectionFilterGUIFactory); QApplication::instance()->installEventFilter(this); } @@ -594,6 +596,12 @@ void FilterTable::setScrollOffsets(int x, int y) presetScrollY = y; } +void FilterTable::updateAnalysis() +{ + if (isVisible()) + mainWindow->startAnalysis(); +} + void FilterTable::mousePressEvent(QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) diff --git a/Editor/FilterTable.h b/Editor/FilterTable.h index d214ce6..e130c99 100644 --- a/Editor/FilterTable.h +++ b/Editor/FilterTable.h @@ -93,6 +93,8 @@ public: void savePreferences(); void setScrollOffsets(int x, int y); + void updateAnalysis(); + signals: void linesChanged(); diff --git a/Editor/MainWindow.h b/Editor/MainWindow.h index f93b9fc..75d7ba7 100644 --- a/Editor/MainWindow.h +++ b/Editor/MainWindow.h @@ -52,6 +52,7 @@ public: void save(FilterTable* filterTable, QString path); bool isEmpty(); bool shouldRestart(); + void startAnalysis(); protected: void closeEvent(QCloseEvent* event) override; @@ -93,7 +94,6 @@ private: FilterTable* addTab(QString title, QString tooltip, QString configPath, QList lines); void getDeviceAndChannelMask(std::shared_ptr* selectedDevice, int* channelMask); bool askForClose(int tabIndex); - void startAnalysis(); void loadPreferences(); void savePreferences(); void updateRecentFiles(); diff --git a/Editor/guis/LoudnessCorrectionFilterGUI.cpp b/Editor/guis/LoudnessCorrectionFilterGUI.cpp new file mode 100644 index 0000000..90fc7bd --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUI.cpp @@ -0,0 +1,110 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 "Editor/helpers/DPIHelper.h" +#include "LoudnessCorrectionFilterGUIDialog.h" +#include "LoudnessCorrectionFilterGUI.h" +#include "ui_LoudnessCorrectionFilterGUI.h" + +LoudnessCorrectionFilterGUI::LoudnessCorrectionFilterGUI(float refLevel, float refOffset, float att) + : IFilterGUI(), + ui(new Ui::LoudnessCorrectionFilterGUI) +{ + ui->setupUi(this); + + ui->refLevelDial->setFixedSize(DPIHelper::scale(QSize(100, 66))); + ui->refOffsetDial->setFixedSize(DPIHelper::scale(QSize(100, 66))); + ui->attDial->setFixedSize(DPIHelper::scale(QSize(100, 66))); + + ui->refLevelSpinBox->setValue((int)refLevel); + ui->refOffsetSpinBox->setValue((int)refOffset); + ui->attSpinBox->setValue(att); + + connect(&timer, SIGNAL(timeout()), this, SLOT(updateVolume())); + timer.start(10); +} + +LoudnessCorrectionFilterGUI::~LoudnessCorrectionFilterGUI() +{ + delete ui; +} + +void LoudnessCorrectionFilterGUI::store(QString& command, QString& parameters) +{ + command = "LoudnessCorrection"; + parameters = QString("State %0 ReferenceLevel %1 ReferenceOffset %2 Attenuation ").arg(state ? 1 : 0).arg(ui->refLevelSpinBox->value()).arg(ui->refOffsetSpinBox->value()); + double att = ui->attSpinBox->value(); + if (att == 0.0 || att == 1.0) + parameters += QString("%0").arg(att, 0, 'f', 1); + else + parameters += QString("%0").arg(att); +} + +void LoudnessCorrectionFilterGUI::on_refLevelSpinBox_valueChanged(int value) +{ + emit updateModel(); +} + +void LoudnessCorrectionFilterGUI::on_refOffsetSpinBox_valueChanged(int value) +{ + emit updateModel(); +} + +void LoudnessCorrectionFilterGUI::on_attDial_valueChanged(int value) +{ + ui->attSpinBox->setValue(value / 100.0); +} + +void LoudnessCorrectionFilterGUI::on_attSpinBox_valueChanged(double value) +{ + bool previousValue = ui->attDial->blockSignals(true); + ui->attDial->setValue(round(value * 100.0)); + ui->attDial->blockSignals(previousValue); + + emit updateModel(); +} + +void LoudnessCorrectionFilterGUI::on_calibrateButton_clicked() +{ + state = false; + emit updateModel(); + + LoudnessCorrectionFilterGUIDialog dialog; + if (dialog.exec() == QDialog::Accepted) + { + updateVolume(); + double refLevel = 75.0 - dialog.getMeasuredLevel() + lastVolume; + ui->refLevelSpinBox->setValue(refLevel); + } + + state = true; + emit updateModel(); +} + +void LoudnessCorrectionFilterGUI::updateVolume() +{ + float volume; + HRESULT res = volumeController.getVolume(volume); + + if (SUCCEEDED(res) && volume != lastVolume) + { + ui->volumeSpinBox->setValue(volume); + lastVolume = volume; + } +} diff --git a/Editor/guis/LoudnessCorrectionFilterGUI.h b/Editor/guis/LoudnessCorrectionFilterGUI.h new file mode 100644 index 0000000..601467b --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUI.h @@ -0,0 +1,55 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 "Editor/IFilterGUI.h" +#include "filters/loudnessCorrection/VolumeController.h" + +namespace Ui { +class LoudnessCorrectionFilterGUI; +} + +class LoudnessCorrectionFilterGUI : public IFilterGUI +{ + Q_OBJECT + +public: + explicit LoudnessCorrectionFilterGUI(float refLevel, float refOffset, float att); + ~LoudnessCorrectionFilterGUI(); + + void store(QString& command, QString& parameters) override; + +private slots: + void on_refLevelSpinBox_valueChanged(int arg1); + void on_refOffsetSpinBox_valueChanged(int arg1); + void on_attDial_valueChanged(int value); + void on_attSpinBox_valueChanged(double arg1); + void on_calibrateButton_clicked(); + void updateVolume(); + +private: + Ui::LoudnessCorrectionFilterGUI* ui; + bool state = true; + QTimer timer; + VolumeController volumeController; + float lastVolume = -1; +}; diff --git a/Editor/guis/LoudnessCorrectionFilterGUI.ui b/Editor/guis/LoudnessCorrectionFilterGUI.ui new file mode 100644 index 0000000..2a83bab --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUI.ui @@ -0,0 +1,401 @@ + + + LoudnessCorrectionFilterGUI + + + + 0 + 0 + 1048 + 66 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + QAbstractSpinBox::NoButtons + + + dB + + + -99.989999999999995 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + Reference level: + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + Master volume: + + + + + + + + 6 + true + + + + contributed by +Alexander Walch + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 20 + + + + + + + + + 0 + 66 + + + + + 16777215 + 66 + + + + -99 + + + + + + + + 0 + 0 + + + + Attenuation: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + dB + + + -999 + + + 999 + + + + + + + + 0 + 66 + + + + + 16777215 + 66 + + + + 0 + + + 100 + + + 100 + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 1.000000000000000 + + + + + + + dB + + + -999 + + + 999 + + + + + + + + 0 + 0 + + + + Reference offset: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + 0 + 66 + + + + + 16777215 + 66 + + + + -99 + + + + + + + + 0 + 0 + + + + Loudness correction: + + + Qt::AlignCenter + + + + + + + Calibrate + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 20 + + + + + + + + + + refLevelDial + valueChanged(int) + refLevelSpinBox + setValue(int) + + + 189 + 43 + + + 278 + 64 + + + + + refLevelSpinBox + valueChanged(int) + refLevelDial + setValue(int) + + + 278 + 64 + + + 189 + 43 + + + + + refOffsetDial + valueChanged(int) + refOffsetSpinBox + setValue(int) + + + 384 + 43 + + + 476 + 64 + + + + + refOffsetSpinBox + valueChanged(int) + refOffsetDial + setValue(int) + + + 476 + 64 + + + 384 + 43 + + + + + + updateModel() + + diff --git a/Editor/guis/LoudnessCorrectionFilterGUIDialog.cpp b/Editor/guis/LoudnessCorrectionFilterGUIDialog.cpp new file mode 100644 index 0000000..2508a3b --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUIDialog.cpp @@ -0,0 +1,106 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 WIN32_LEAN_AND_MEAN +#include +#include +#include "Editor/helpers/QtSndfileHandle.h" + +#include "LoudnessCorrectionFilterGUIDialog.h" +#include "ui_LoudnessCorrectionFilterGUIDialog.h" + +LoudnessCorrectionFilterGUIDialog::LoudnessCorrectionFilterGUIDialog(QWidget* parent) + : QDialog(parent), + ui(new Ui::LoudnessCorrectionFilterGUIDialog) +{ + ui->setupUi(this); +} + +LoudnessCorrectionFilterGUIDialog::~LoudnessCorrectionFilterGUIDialog() +{ + on_stopButton_clicked(); + delete ui; +} + +int LoudnessCorrectionFilterGUIDialog::getMeasuredLevel() +{ + return ui->levelSpinBox->value(); +} + +void LoudnessCorrectionFilterGUIDialog::on_playButton_clicked() +{ + if (buffer.size() > 0) + on_stopButton_clicked(); + + QFile file(":/sounds/pinkNoise.flac"); + file.open(QIODevice::ReadOnly); + QtSndfileHandle fileHandle(file, SFM_READ); + + buffer.open(QIODevice::WriteOnly); + { + QtSndfileHandle bufferHandle(buffer, SFM_WRITE, SF_FORMAT_WAV | SF_FORMAT_PCM_24, 2, fileHandle.samplerate()); + + bool leftEnabled = ui->leftRadioButton->isChecked() || ui->bothRadioButton->isChecked(); + bool rightEnabled = ui->rightRadioButton->isChecked() || ui->bothRadioButton->isChecked(); + + int buf[1024]; + int buf2[2 * ARRAYSIZE(buf)]; + int samplesRead; + while ((samplesRead = fileHandle.read(buf, ARRAYSIZE(buf))) > 0) + { + for (int i = 0; i < samplesRead; i++) + { + buf2[2 * i] = leftEnabled ? buf[i] : 0; + buf2[2 * i + 1] = rightEnabled ? buf[i] : 0; + } + bufferHandle.write(buf2, 2 * samplesRead); + } + } + buffer.close(); + + PlaySoundA(buffer.data().data(), NULL, SND_MEMORY | SND_ASYNC | SND_LOOP); +} + +void LoudnessCorrectionFilterGUIDialog::on_stopButton_clicked() +{ + if (buffer.size() == 0) + return; + + PlaySoundA(NULL, NULL, 0); + buffer.buffer().clear(); +} + +void LoudnessCorrectionFilterGUIDialog::on_leftRadioButton_toggled(bool checked) +{ + if (checked && buffer.size() > 0) + on_playButton_clicked(); +} + +void LoudnessCorrectionFilterGUIDialog::on_rightRadioButton_toggled(bool checked) +{ + if (checked && buffer.size() > 0) + on_playButton_clicked(); +} + +void LoudnessCorrectionFilterGUIDialog::on_bothRadioButton_toggled(bool checked) +{ + if (checked && buffer.size() > 0) + on_playButton_clicked(); +} diff --git a/Editor/guis/LoudnessCorrectionFilterGUIDialog.h b/Editor/guis/LoudnessCorrectionFilterGUIDialog.h new file mode 100644 index 0000000..8b71aea --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUIDialog.h @@ -0,0 +1,50 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 + +namespace Ui { +class LoudnessCorrectionFilterGUIDialog; +} + +class LoudnessCorrectionFilterGUIDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LoudnessCorrectionFilterGUIDialog(QWidget* parent = 0); + ~LoudnessCorrectionFilterGUIDialog(); + + int getMeasuredLevel(); + +private slots: + void on_playButton_clicked(); + void on_stopButton_clicked(); + + void on_leftRadioButton_toggled(bool checked); + void on_rightRadioButton_toggled(bool checked); + void on_bothRadioButton_toggled(bool checked); + +private: + Ui::LoudnessCorrectionFilterGUIDialog* ui; + QBuffer buffer; +}; diff --git a/Editor/guis/LoudnessCorrectionFilterGUIDialog.ui b/Editor/guis/LoudnessCorrectionFilterGUIDialog.ui new file mode 100644 index 0000000..cfcb92d --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUIDialog.ui @@ -0,0 +1,274 @@ + + + LoudnessCorrectionFilterGUIDialog + + + + 0 + 0 + 486 + 495 + + + + Calibration + + + + + + <html><head/><body><p>For a correct calibration, a sound level meter is required.<br/>These are the steps for calibration of a stereo system:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Disable loudness correction during measurement<br/>(done automatically while this dialog is open)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enable only one speaker (e.g. select left or right channel below)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Play pink noise at full application volume:</li></ul></body></html> + + + Qt::RichText + + + true + + + + + + + 0 + + + + + + 0 + 0 + + + + Channel + + + + + + Left + + + true + + + + + + + Right + + + + + + + Both + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Stop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Play + + + + + + + + + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Adjust volume via Windows volume control or external volume control (amplifier, etc.) so that the sound level meter shows 75 dB at listening position. If that volume can not be reached, please adjust the following setting:</li></ul></body></html> + + + Qt::RichText + + + true + + + + + + + + + Qt::Horizontal + + + + 1 + 20 + + + + + + + + Level at listening position: + + + + + + + + 0 + 0 + + + + dB + + + + + + 75 + + + + + + + Qt::Horizontal + + + + 1 + 20 + + + + + + + + + + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The reference level will be set to the Windows volume for which the level at the listening position is 75 dB.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">After calibration, volume should only be adjusted via Windows volume control</li></ul></body></html> + + + Qt::RichText + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + buttonBox + accepted() + LoudnessCorrectionFilterGUIDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LoudnessCorrectionFilterGUIDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Editor/guis/LoudnessCorrectionFilterGUIFactory.cpp b/Editor/guis/LoudnessCorrectionFilterGUIFactory.cpp new file mode 100644 index 0000000..3c2d1f8 --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUIFactory.cpp @@ -0,0 +1,93 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 "filters/loudnessCorrection/LoudnessCorrectionFilter.h" +#include "LoudnessCorrectionFilterGUI.h" +#include "LoudnessCorrectionFilterGUIFactory.h" + +LoudnessCorrectionFilterGUIFactory::LoudnessCorrectionFilterGUIFactory() +{ +} + +LoudnessCorrectionFilterGUIFactory::~LoudnessCorrectionFilterGUIFactory() +{ + if (timer != NULL) + timer->stop(); + + if (volumeController != NULL) + { + delete volumeController; + volumeController = NULL; + } +} + +void LoudnessCorrectionFilterGUIFactory::initialize(FilterTable* filterTable) +{ + this->filterTable = filterTable; +} + +QList LoudnessCorrectionFilterGUIFactory::createFilterTemplates() +{ + QList list; + list.append(FilterTemplate(tr("Loudness correction"), "LoudnessCorrection: State 1 ReferenceLevel 0 ReferenceOffset 0 Attenuation 1.0", QStringList(tr("Advanced filters")))); + return list; +} + +IFilterGUI* LoudnessCorrectionFilterGUIFactory::createFilterGUI(QString& command, QString& parameters) +{ + LoudnessCorrectionFilterGUI* result = NULL; + + if (command == "LoudnessCorrection") + { + LoudnessCorrectionFilter::FilterParameters params; + if (!params.deSerialize(parameters.toStdWString())) + { + result = new LoudnessCorrectionFilterGUI(params.referenceLevel, params.referenceOffset, params.attenuation); + + if (timer == NULL) + { + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(checkVolume())); + timer->start(10); + } + } + } + + return result; +} + +void LoudnessCorrectionFilterGUIFactory::checkVolume() +{ + if (volumeController == NULL) + { + volumeController = new VolumeController(); + volumeController->getVolume(lastVolume); + } + else + { + float volume; + HRESULT res = volumeController->getVolume(volume); + + if (SUCCEEDED(res) && volume != lastVolume) + { + filterTable->updateAnalysis(); + lastVolume = volume; + } + } +} diff --git a/Editor/guis/LoudnessCorrectionFilterGUIFactory.h b/Editor/guis/LoudnessCorrectionFilterGUIFactory.h new file mode 100644 index 0000000..e664e5b --- /dev/null +++ b/Editor/guis/LoudnessCorrectionFilterGUIFactory.h @@ -0,0 +1,47 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 "Editor/IFilterGUIFactory.h" +#include "Editor/FilterTable.h" +#include "filters/loudnessCorrection/VolumeController.h" + +class LoudnessCorrectionFilterGUIFactory : public IFilterGUIFactory +{ + Q_OBJECT + +public: + explicit LoudnessCorrectionFilterGUIFactory(); + ~LoudnessCorrectionFilterGUIFactory(); + + void initialize(FilterTable* filterTable) override; + QList createFilterTemplates() override; + IFilterGUI* createFilterGUI(QString& command, QString& parameters) override; + +private slots: + void checkVolume(); + +private: + FilterTable* filterTable; + QTimer* timer = NULL; + VolumeController* volumeController = NULL; + float lastVolume = -1; +}; diff --git a/Editor/helpers/QtSndfileHandle.cpp b/Editor/helpers/QtSndfileHandle.cpp new file mode 100644 index 0000000..c3aabbc --- /dev/null +++ b/Editor/helpers/QtSndfileHandle.cpp @@ -0,0 +1,66 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 "QtSndfileHandle.h" + +sf_count_t getFileLenDevice(void* user_data) +{ + QIODevice* device = (QIODevice*)user_data; + return device->size(); +} + +sf_count_t seekDevice(sf_count_t offset, int whence, void* user_data) +{ + QIODevice* device = (QIODevice*)user_data; + qint64 pos; + if (whence == SF_SEEK_SET) + pos = 0; + else if (whence == SF_SEEK_CUR) + pos = device->pos(); + else // whence == SF_SEEK_END + pos = device->size(); + if (!device->seek(pos + offset)) + return -1; + return device->pos(); +} + +sf_count_t readDevice(void* ptr, sf_count_t count, void* user_data) +{ + QIODevice* device = (QIODevice*)user_data; + return device->read((char*)ptr, count); +} + +sf_count_t writeDevice(const void* ptr, sf_count_t count, void* user_data) +{ + QIODevice* device = (QIODevice*)user_data; + return device->write((const char*)ptr, count); +} + +sf_count_t tellDevice(void* user_data) +{ + QIODevice* device = (QIODevice*)user_data; + return device->pos(); +} + +SF_VIRTUAL_IO g_deviceIo = {getFileLenDevice, seekDevice, readDevice, writeDevice, tellDevice}; + +QtSndfileHandle::QtSndfileHandle(QIODevice& device, int mode, int format, int channels, int samplerate) + : SndfileHandle(g_deviceIo, &device, mode, format, channels, samplerate) +{ +} diff --git a/Editor/helpers/QtSndfileHandle.h b/Editor/helpers/QtSndfileHandle.h new file mode 100644 index 0000000..15503f5 --- /dev/null +++ b/Editor/helpers/QtSndfileHandle.h @@ -0,0 +1,31 @@ +/* + This file is part of Equalizer APO, a system-wide equalizer. + Copyright (C) 2017 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 + +#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1 +#include +#include + +class QtSndfileHandle : public SndfileHandle +{ +public: + QtSndfileHandle(QIODevice& device, int mode = SFM_READ, + int format = 0, int channels = 0, int samplerate = 0); +}; diff --git a/Editor/sounds/pinkNoise.flac b/Editor/sounds/pinkNoise.flac new file mode 100644 index 0000000000000000000000000000000000000000..81c8309fd2a3216be1880c795ba9cb05b14ccd77 GIT binary patch literal 86764 zcmeF2_dlEa|HjWzZS6*cs;U|h5wX%3Rc(z}5hJl@jl>K!Yn1lb#NNa@_NFmwAA42R zh*4Fwi>iaFqpH67{2AZJqxaAEPtW9jy|34GU2g}Bt?D0t{2>VV1MtTmSG)gUtzmxs zr^g?E`u)-3{wHCr>&9POpk4p)nfxC5KmLI7(_Zx=I1s!D?)C(JqO+YkMp>0#TH>Ar zf*&CzEhi-{B~5$fkB~q9_vY{CH-X;-eiQgj;5UKa1b!3vP2e|y-voXW_)Xw9f!_pv z6ZlQwH-X;-eiQgj;5UKa1b!3vP2e|y-voXW_)Xw9f!_rFzZ1CpZ;a)SAnreyF8&zx z`ZJN4&Ww7ib*CC~6?6NcFih+|QsMFAM~@#XD#+XuhrYN-`8ERF-=9vrFd|r`2bTt? z6Wnx!uYde-;@lsDZYaV`g_Vc1G4LUYZNT`t25rYvBj|j+!$gj(i*fP6nJ?K@Rw-FE zB3w5g08sEbG@y@0jzPI{&o9g~T}R%hHVeCKlBr$iFT7QseGtU-+G}V>-;(mk_PQb8 zrzP;Q;3Ybq)Ldg)t=oK~z3$sElGn5G1`pD5C$%gt6ONtF(V>^!N<0xa>+(`Ze+!TA zXc03!&qq!e=YcIL$5Dfc%nSf|B#;*?BTnoaq zbeXC~^V+EV4553P61=_S9ATg(-f&K`nlIO#aOK=vC^wG35x9=|rsHVMui5LZo{;+p ze>gpa0YFFTTrvuNjhAW`a3}~W_z+Q;&oO>ut=|q5%p?FFAOXFnSnrm%2l$e zY02Jsdu&~?gB4<@bu>AcN_BkxMd*b|XKmhFk`#@H!QSMJ2NEaRC+ioeHm~ZHaf+UjB$D zwYXU{Kby!g5;fXhZkk|w?@_2S^d?uqe{lvfmI_&NElLPJM&~_{gFk6~w%Nf2)35KG z;q@b&j`1LTJ{9(=hC1kFNGPS$iG+4vplhpJ!FVvfq)zia#k`>NAY=5=B5sH^AMl7D zM|VAe2d144>`AjtoF>?6M8JfYf zr(?>N_PW*PMyonto2K7AO9SrUGScgblg}>=tYpm8K31>HeTz&vyvD>W1vRxblfw7f zg-5^cfUdO$8&q0T&r@(F8yoIV8qLeQT}i}8rj%87wGfO@|H=HkBYvrjgyDFzt=MlcLy6&w9;A1CIkDy%E|?+ zuoWS~H$KlT!VQBN(KM?+=-3F8RIq6)E+0UJ(@)0a01%d=bh-_FBZUd=$CGzYPG4S7 z*?ykZb*?PG-U}3(4UC`Kf2sJkrKfNJpj^i1UmV5Ag*+UNQEbdbPv_3;NhCvvqm+B) zdU+Ct?Reu30*TnN3LIq4gA3vp*M$=v>4&jGQgWVZj5Sx!cR8HIwp5#zU$7l-BaJ;n z!bvN7UeOv?kfb%6=Dc*XbLrKFFCU>WkmU#)oN-UyuU^Ek=)4bT@$Y%>l1KaWf z!BEqGLj>p|$k5^}+(Mjv=W;;fa(%(Ci!N7xU8A%evcrRknfES52P0);3S_smViNMPia_fJ+Cm5&nVq54<^W#1Fg&U=J1O0DT8pLMS9LLET>DSdu@ef`xd z;h=5A^Nl=sgAPz@E5k+W$rzUV3xA|kpZtI~q|$Hm%r!w_e!_2(D5Y$);C6~mhT9aT z3TtOTlw#SSq=0j(`Li!*%Z!xF)QptWo)=w#NSPJkEXb2QWDv&E5RbR9vBc=F_HEpC zzH{r~hjHzb9uZLyQDNa1C(rLr&l5qGu|v#!3j7d$2qZ{pClwrxy-LY0Iu{v>jg5{_ zQy(52P}55n-0i772~V>rR*&Z6cSqo^ z56x=aP9b)5SLvB~p*CUT@N{u;D$>Xo=bB7t{?$25yr&#_%Xd4vT{A54)$|B|fu%vA zdr97?sFtYn{aa!gH5b2Lf8M#`FceAm=U=b<6&a#Jloo>E4i0$KM0UASaXDGxoOzTb z#X>CeP3iO^F+=USy{+1IK1S?z5+^rrK}>{7?_d8|B0w<%Dpxq@G-^4(CjI+Y37N}Q zPe#EhqG}fxC#896a&_+yKUq#t1Ox)Gy_J=tQ^~yn&e+@AEYuREf)^_#)t^jLCfxgPj>IPeDwWqN zHjM*N463%z%RXT>Do7Q z{$A(42Q87pUZH1U;sx+Zaq|P@ZPu@gye<)i9rPGwE(a|lwzczMf|xchw`8k=UuXHk zZ{evEil)i->MVRve$7=C6oY&b2AotU7hE(}*KRa}pGa0fjc-`_ayitB5CiJCoI;8s zS$L#ODc571cU?H%L)%f=233O=TyP_bO8Ls(oV?)Djby0JRO&X0EnmTh(m%&`d)RF(^yCr4td4A%2c&7`S64+)|9|S~O#9t88mK ztK2aQ&~DkoZpS729yugLGsZE;v9ho-F{Q;E3rBZXjc#u@Ra28FLIQyo=b2UJg{u2a z<1)PX7$8AA3+6<@ZD=JqpWM;{dCt3a%)f__cJ>2`@4Wv$J4;I07R9K1+y6lpE8D9| zwrXBFn2EQK4}kD_BnzQpK~k$%_b?J9hQpqhm$-BnY8ACwZ|sh1J`-|P&{3Oomy4Yj zaQl&zlTsUw`Bm9VgZe>FoN`%_hg zfGQdjTrkyg$HX)lU*ox(8S{R|^Tu4h$fmp>?&C3twK_b;kQgM$$rKUxr_`%|HBFrk z`y~;5ftM#aCY$)JEs?TvhpjE)jg6;s&HLib<7&CYLfnuZ83=fUi;75KuK%iQk#48(~fx^vQJxT-bkL3{HRy_lAEGCh4GW@#6u=*{LV;jS)D-QVY{=}P05fvH^FWFC;E1Q&B+c%-^sK17RPv=(oElvxIWY;A2j zl$Hs*Ti2R=mG6~V@~Gqxjw8$B+xj5U0KxItiYio%Q2W+tmpAn7yB5(Zdtiwe+a`$W zJN+~V;(I7JmX1#}>h=VuIfaDwsa&}p#aXN#dlRfRmuYm>(R{`ua9XM^kGW+O z=b&H5!4xfr+d(08ASvhm2rii|u5xjIlT+ipeZFn)_+U5bZ9b=Dx=Ema7?Ws%`l%HH zj6%o8M~AB!##d+5;wm-reFh=cKBKC`;VAX1SFfqjN6@q8z?HvGsY!JI8hqYbvav}Z z5K8d}BTMz>i8%+!E|u5EG8OQKpI#+pB_(B3=!Ue|X_~$Nt>_fR?xgL(v=WWyOUo4Pm%SP?^RbyI*2wjExq|7M4ujo0=FIV1FJD z8xvs}Z9eLT)^=Y0&x9TV^P|}kSujjmcm5)9{cqj+^Evk=fks#1V#oJFy8l+~_HIVe z*xHtsK-%agFdG%B?d$5SqjBv1!P#71NJQq|V?fM6(W?9_V?mCB+c{~eUGAj_@#RNY zBQtZeH=##5{7Iwkc-c9llb78mi{&r~>zz&!QBi4WNm;3Tk`gdcp%;f8oI4<)!Fm(6 z;J~xf_3cD!Mc>Ua+tOHYo29Gp!lv|s4?Ahs@I*u1*H2VafK571I$U(@iv~57?x&X6 z$BTo>w&VEk^DF=>hg|;SXGlxCOV+1ANB z+QXzwMF<#ykXJ-LQh+P)Lp`Q|V(_{igW4BgI-6!ceEab8&FnyKd^PBNojwzyv`S&e zhJ+OMQYfJrARyAF+Pyfm>2dY5jojSMfj3RF197$E%@yxvf^QO&4MYaQBd-n)4XLTq zU&Z7=wIY-B@}1}EO-dxSN5{ssMn{)=%zIXzmZ9sELPEi7^QAhN^&0fdPPL7(I!B2QHd-r^Qs zyQUn$`Bt6a{^CokpRcsHQ@fD?JJ<;E!{PKvX~~fm*OX)D@tnDJX08a)8x3xF3qOvN zZ_GDiwcv<6p2NDGjHO#H5sV;JqTfBE6pL?g!Tmoa{qt8}i_?_XCL_SD6r^|eelfba z0|=BW#g}Y?pN=lJnYZ?M=36w3s+6%6aByHH<@loeiX|PjW-~`JrH>zp2t{sE+98UgSi#EaDbp=-<`w z+S>Y>%9@hGzYk_2Ef;6&i5F+e&H>q!sourj)$Cs95NWZwFX<)9afbiz~V85#J1JRn{kE^cl`uv4!FuPfRmBiz>f?Ma?DrtekUs@Ys{ z&$CGrbt`;q$ujI5KTmk_tGTH&l@$QD`KQn}j4q0W2fY3Pmmd1kCua>Zpr$q%ACtq5 zI0G4(658vMAo@F=LZN~p;S>2t(~f!sS^McGQ@Z3_N{7&ki}Q?{*2;#>@7S8n|LAxTsK0(22Z0!7N(Z$>R-Mb4PE)=S z1MM4yXJibea@XCvOEsLajCIe1U@9Ag$6MTYWIno^;okBDcY2_`65zY+Di zx%AEnLTt4ShNYye)jS78vVkp4u@Fr*JkT#<2ypbwu3AmbNXN z`Y&2v6cl@v&Q|)YjpE68h11E<<3!cU?76Q{s#Ym321!=)_O>#)Y=vi0n}gJC?7V{b zIT!{)J^%dl*XuWhW_0>IkHV^kGRQA`FH?U|B{;A??Bo;-f!@fj-8Hy4!bLjr1rE0?hQw1-Z zm|C`%chR&b32ZSa+5Q#B(#t)XIypIuOMi=Xs;j!NTk_g@t^gl*v~R(A-Eo9P4|0y( zi#EuPFc2^>xZB>`I^jAhT>f94xF}Ih;vT3D8pXXDGu+u13r=A&6LaB;bIrM|EUJdJ zj7!x0AU2SKb^YE7xd(u=oWf8sAb*PB4P^N1la!Kjs|(*1;?mZeY*}Ju=6FN(gEt~y z!ODGfSFf?KFflMQ@X9+EjR=pZRut(RRZWOBOHw;<%S^v6KDMnE)hVE2iZrzIrSPk` zff)G?hT@(_xN7Uc^VX85npPdwgHM9ztvDs3(_umX{q;}QyVtMYB~`?v4#Y@Ji*2jq zJ;>#pw#f;}5d7lt+V-`#Owl}wn94^D(R->(G8=dTVXJf-LLh8P zSXQepBa9jbQCF|hU9nY)HiZJ2=&ub$B=Ev#G@X~lWJ^mS+uL^3hn&%qlR6m9r8d=N zAe}gsg1{)hIBMXnBek`dd^Tl+di;9Xh&Won2t<9~`D*Mlo4~`o9A#vhnAK9<(T2VQJc%fsF;`B{)jonrSx%y+mJli8m@CR zgt`03!}K$y*ItmI3*tYHDN0y5#ZAN>-P|n2mzLt6j+oaQd_C{H5K z86fsY_+IrTEbD*Yo3&du>c`MpBcs-Bc0C^t6L??mqzu#3wvWMx0X5y|ObKP`jDgEz z^JW22TqB2pLCQ>nSBkN?!71@y-;}|L7{=JRcvdzxrudiwAjGZJSXJU-v2pVrLawBC z%%sOI{i(t_ueN$N^w6seldfo38A@;Ni=3J)cwUL7K4{*zDtE7y{OsA1T{K?DDM+kb z7tzT@IwiM^B6}T3WZ%4AOTD_+<1F)pqPklUe6M!|CIgK9ZiPk4%=i4H zs8C&>i7!wbN@2IGnzom*R~e;j4sGp2Lx;m77#V>9n@KA2Z=bc}jTG9Z3FD~}M$^09 z?al`$okA?O(gb8EBe;kKJfEvolB>LtVT$Ffvx>J=|zH=^6>IR0(wnY5VH5_hY|)+p2SdMN$%_uqy8goG$xqX+cYI86R; zS)%je{v&|=hfLJv@srEO-dnP}Pc!jn91iGAh!J)9U@upT4XP}d$mKB;!55$o8mjew zx>Gjp-aRV2=;mY4wqLvl?F(m6G!2Q2mpU31b~b1;0`BHW;CDPZAlj*x1uySUJ(cn- z3=Qj3=?|j|?PD6>Bs-ThK6&YctG3&Zov+oLZ?!9x>{wCca85yK<-&bNLW&*YK~UvZ zEY`AnH{HCYW~I@j-lPlHCE31?nX#QljguApKNn634YOrKF=68Tp`Cszc`lj6#U6)= z_T~!xy-x25!bhKM1DI)BN~9J@@YuD6lzpu8#DwU8?2~BU;L}=+v(s4S-KwOS8z&Z#6WqYN*df_$53r5A@)vybzXJK%>(pGmx%R+*j>M^`^4mHF`Q6h;nCKcTX0NqU0okkw3Y8n zYL-nFUx!U zOUK#Lp!z%gbdZTVaMR+tsv_Q}DiNmjfg5={7Sk;o)s}@C6O<{(=vxSWekrKDgd$u4 zEbF0P*{TU?SUNS&Yhyk-^1jD`m;rjtOPbcIuY7dg$c6g9Q&NIUff!louV1~v2nraG zAstARSy6zFC`@#YtYxL~6E(iKUUNy<$b65&1bQ|m7G4n4rjPAG?zrRH=J%&5FXJ^E zJ_@hRIq%5q2Wn+ptjs+Zy2EA{f6X&mn&n>BpckiUyyLeCE0e0dYEEGs=nF2X3RQW3 zr_twVl*dJ36gggXE?z}C@7cNU`T-kB=2t4D8z2GrwH!0nxdsK;=AFvF2mTOkZ9f0- z^;)i4QY5I52^5yQjb&$dohMUjS;0<+g6c6qGl$a4M)?GLCjKyzd@$KmPyjCCxh!$H!)0RZqzZTZwsDe^%XU~x-Y^CMgeq?*Sy*== z5|;?a;?b!R#7dxhU4b9EHj2-Gwf9e3<&Z+<_5N##AkPg7X|TK7l605j5dwmc4Z@$z z;L%kDeqv^7ydH(B*I4+#9)%)|;Fh-7ap(!xjBnL0S2`Xq3xoDlCeXnMdA#$_|y~`eBo>d@*OHLJO8)_%__rRGp^tNAIUy2GabC zhnPUL2$GL~K5I@?{_E!n|C?vKbNVPUEi1LkGu=T z>`(;QJ!y6VHpG@))sczA*C=y(-uvKtB&n*QuCL{7OLtidDe?4$#j(vmv$r1}93dRB z1-TZnMHmbpl;mr*tVD?Ow0}QS)iA&sSeaq1aNajdTn&p`77(L|$hXlX7A95}=2%A3 zZF$2SVo8`)uyt1@<1Nwq_X8LooVuBmUMg-RV%UR5ajZ;i^w+LOMA64H$ZMg5LOx8aMZ?93C=H_RX6d z$w@NAQWeD@|MN2)tjQBcduL~Yvze8i&gFc3j>p<;Wo+cY(~+@}Sgo-bO{N>yA|vRT z0azy0$5tu$(E^E}QEv!U?$j%8Tg)3B5W$7Tr)*1WIXu*>X|I&iYLBq;oij3d7#|?q zxgNm>fcyXZSIGMC4G>&wFt=!CZtq#(l4Lt3XWLA6$7GrC&FoSWR#A4JCt6hsf9;=0 zfO7QhYqkukfH=HBMLfe2B(D9OYFM{X}wjL&dIEs%-h z;39@63Wq?{NV=fe5h`1-Q45x@c(8f5#<1RbXCpv90z+^A>1=1Je2PI+?kVq-&q!eJ&hUKWlF#SS} z5&3bZ;?TUq3mz)*Lr37ZxQM;^FH6-X?{DuUxOVQ?4JR;?nBr)1nmLxld>aTi(vol2 z+~4Fe5!V{k*3o{eJAu*F)_#)GXq|qirC0q91hbmVu~rls8oFjDsFA=8)7mo862E`E zTZtZwH9DAUb0{T)paMNrB=1t9B@$MOygj2{ov?Tq{qO*w(PQjELR2d>Agg z(ZI~#I07_tOmU_M%!z8vwOwM>45>PX2Jd|*b{@ErZPj?4QUpD`cNz*Pqi3i_i ztf)_XTb2mvfpzQeaSWbUxy&|moj1AdKdKl<9H-A1#zM>xm~4b(adlk+tw6*E&@Z&3 zQ=_M6X5^+t{JPJrYo5;;Nt|JUc4ZSWnFy=tiu(6&EtRk5N*yt9De~JKuF+(i*1k(h z-ON&LoncPtio_m%#&KuftF}!~bR!Lm%2><5nWq)sD&6iJ_DgV6u&_KDfv@Yx&&0!1 zfoR0O#aYI{SVA6Sbi|NaU%5I8o$B^{(v)B6Q)ss!L_!LQltmyUAc8@?2qG*r|E2jr z#954UTXfvO#%#^tc}wN_ckZ(D&G@_QoHx1nXl^nO2fMsS6}u;XdS%7cgd*@v1@|p3 zerZn3=qSO`Axxr4Kih6-{0^LU`k?Ok%1rgYxL>-d&@8%8ZXK?j?8~+O+M5Y^cqQ@% zFE?xhPCPIZhKJCt08#@3B;-`w&u7?lMqv9FU zA1QUzS4_)qPc9o3Ti1@w?Z2nEG?RQj2cPmjO~@{OVEezl=87-(r6$8=f#KzMu0OPk zZYh?Nk4Elqkk`hYO90qJsjz6NMn` zbwy=G`Kgy5mu0pGb7v6l3x9>D<>uyA#1-ysyDow?Q=hQ?`s^aQM-mZ&Q?_ShFl%)~F1X{P&h)TEx>DW7p`zRglD8bLc=xkdXK z0JcK_&oTLOsSWuWQBw`U>{}08I}ThX6qAM2iVu<@XSn?w%j(*)ik zctB9NjnY)nAcJBN3769Ks|Q_XHH{xj7W~0(W$d2o$YLug)z1cS-sZ*bi|eX*|9B`^ zY5XQt(Dw+uu@3u9dFy>Jsn^6l$sCbWQuDpwt6+2cTG>m z`Itq;B*~iY6kdr+yiN0$H7wjfY3pQ}i|1UD7D=rFUYq0-r;Cr|t*Q-63CrH~P&yIR z+kAO^FG5$UPjqMamr1dE?T^#_#WuSxbEH#yPeO2b2CY7#&cX!bf|?#rDi(8Z3R^CN ze!bsq8T)$DbX0q4_7poLI^jBdc#Pnb{&_Kf_Q-q=ZC>zg;Hlu-RFK^@eyqB@9~(AK z8f|~?RxpQ0dguAWQ|RWS3z@*2hdKopxz*hVo^PsSv25T}R`~G%pMMA)i(;d-_FR*_ z;qGIrmXUVC(S~k=CHbI=?ALXu>pYkN)lXI(VrBpWXj=DLhQ-2w&{9<_-U5cNY_=|? zRa>U3(NR68t+(wGTZUqe8<)D{42fH!}S>Q z*%dq%?ZCO>Tn3MNel^D$*MI1EF<3=?c#e6sxy_s_A>nU|*T);0Drqiyp^S9+UY5H2 z41d%4bK@J@J<#1+w|=Se)YN8u@J%#{nJwW4y?Wo3(0&mVI>i@n@-bc84_s2ZStd-_ zIQZCcq1!y}kPE|)gdAd}l~s~KQv905V^xV(Q?-F_n&LWZ-jp<@@3n{qynQZpXzw@> zsc|h-;Lm@O-YvX}j=|}{B0m~7KEs*23mAB6hO0b#VQr4yG`D(6u1|vdD`E7BcuOBs zOGBkYuMHkE`UeL~NVnyB0f!6irq5H`xvzAhcwvUI+;C!>-M~oa*iLByQnB8gI_~wX zL~^0SIPXT|yl@UbW~%T{b}Uw!@@MjuKJY|~vW(%%ipzxdJRV#kj0a18sCRv@=o*P> zKo#C|V9S#Y>OE4A23l?0(|dZqQl-}Ao#c`4pu z_iGLWVu*iT*@)LEQoo4r47(b`#U+L02g7YliwBd;rwdFJr+<}dbHa`4bt7)HuE6Dt z=Ei9I`INl_b2?b3%tTqdqfmYD`RP-wktaO@=F3b$t{JtDh|8TqLU1rbSQ-Ks1P87* zq(^(XZoUt@P;;}2Pmd@iMCgd$c$?$9f$A+TLfB0os~UuP+Z!MnPB+A}$Z7Jgzn$u6 z+uOUjJ$LhSb$w!@r8=JwWNvcbN8i-kofzcKe_vki9!x~gV-Uw;*&b9@S=Z9k+1lOK z+|tY(chtdL*^2zf)1F1 zM}q$PN@p`e+NsbbVo_1xwmR0=OQK!JYo~o>-gsvvAaC1jPNt>I zFveIm8>dUWEpQeiN=R%VL_rx^jwxcUu-zwlGV8O=s{;2az5m7Gpq$L8(A48~Av!-I zm49k}Q#_*dL!>q=6}@rfEIdec)A&U`bhf@Y3rU`erdRP)`Wqt*;N!_i>mZf_6CPK% zin~j0c$pcJ%z}kmU)PovZnx4w++t|4l+J*l^dD)OfiFDlEYT;$^w8#|7gSLC+%pr| zR<0IfPQ#U;f9FFGEnjI?b?k(WPJBEoGqcv+R+l^`KHiu5(+j4HA3j)Gn^`PS4X|gM z?G3#}!g@?G^y+l|tAM2c{{4q?JTMCDJDVxC-run}26}mUaebj4|CFN6zsUjsK{vU1 zxNdTAB0Z{0*P@T9fwbCGiw}EEJGf`YCcsb z_-8e4IY!&dZ=>F#!>H=~lv{ZG28V><&U(~yxvg82($Wo|o)R_$$!XG{2Gx+&flMVNkR`>XqK}(82=ho;nm`$$%A(jK8TsEgB ze0HUn-0We!Zr?I-objzdLII=OJ0 zuv)>C<#Xe%QRbuR>2yhPAu*Q|w6$mCI7ra{^WXoBeERS6CzPd);aOj}sm{YHT$_N{ zJ(PCjbHU&_A2vfW#!tXbN-hr>{r^ zEm9ioM08VOW3ZtLtu5Ly3WYoUn`|GR?<|2XPy>Q7Igfk{F*`FNMv#^jUMk@TrJJGP ze|D@wm`H~nyYfdpKDS#Qy?-m)EZez_F}AgO?l`a6Y>hCG$im8YgP!in`g(*E9A>%7 zMOAz1w9qCjDs%7Q!-x0p-xm`Wvg`S<6BU<=@c6f)V&T>2*Ps6GJ>DASG8$-DOU#6g zSXJIH9vbR*I_u`d~i6)1vrP6 zjk}De=nRqCR5bXn&vGyvG@48ZWh9(&}7Ni2V^1nx>z^Q)mUOa>F=zp9c&6=r0VUd zsP}}FCPn$maCrFD8w`AKo4$b*W$1g&`MtU}Sxh&E7MFITa>VPZpb6d&)+)3|0@P@` zGjBjt2Do@sF?57KqRrVr_Tu@+#&1hj#%6?@fjuvChdwoK|8G~Lx8eR}W%SDq^~*8i zm)liN_YR$wz1r@j+NQDbvQTZ?t%5VvAG2b{rFQWOUD)m1V=C8j5Unye8BHRQn3+h? zjCT;G)sg}!zs?;x<8qrm(Dcj;0bp)^c1DkgZvFesoWx97ewZwRgO8a$R9Pwd^S>2s z{SnsAVEONrW{tuk;*t-=0a@AEnO!1w6L%l<(~rm+wlxk%&_|#mP~kW7qztRw;iTJ# z`#iE$OU(ifC*$e+hP4OFo*B2`GxQ%Jh7|9s@mz4zWVr(82%H+9%=W>HN|jO<8L>-? z_m6$IavUQo;WHC}WTC6E|C734P35&oscpb4A%|uIXA;j0L?&qNMfNKfECjR>4;wXN0Oy8fYq zy!?k6WLmUW#MLSJ&B$ne*=PJ|CaTA3OfGJ}R(y+xKRJR?zL8nri%X07_RxpmX13t% z{r1T?K!4f?w>2;`hZJTMu@Td$=8&dl`C=>GoOPjFojf~os@zz7XPF>xdPl|Ci+N5%m?H@kLma_!B2dv#;3 zRrxdBs}?#ifilHZrhqrnUj)#0uikL<{3H~Tc_qi1b`(1S`V6s^L7f~9{* zz;Pz{+_{G39#Dl8JzIGyGE_4&%JnMm_ST-eFGF9JsB$DtfYzCOuj}CbF`j+ z11cY!EKHs@&iL??aH1l@#a)pjLe7Ut;dyqC4S!I)&R9h)^FB1Tja234B`chjTHpvNGI({-~7PzYV;( zSytv)R_-Xg1;)b{kC$KRlt*Vtc$}^GU5~#lr4;Kb5Ee9DYxf~MF1)7nOnc6Ks`zWb zZZA;%2F+JziBDv?!NS5o;z68oCTY9)z^!`3gylpYh)IfwdTp8;eQp0zc|D$60UBg! zNP9HT`thUk4Ke%MRN_L9zzZB2t)jj;MumCKy%$=iXH1qgTuqJC$v~j0>ySlGqOEt% zF7CRV8LR~F1$&S3rFqUr({`vz1U^4_GfPL<>wtWhTJBPtV=Qh|IK5@!d;9DAqX=Fp zekXlXO?}gU3sv6&v@j}nX=RS!WRrohJcuQ^{zTq*!HkZLn;)#HPw7`9$*;XfQ`+Hm zhHa^9f<;1tf_57NBdJD9-whj?fFA!OD~I)ks$QcRs{Gi9q!xju$$7(Af|bHzihC-d z$zb1+5382MuiQ^hyV#%~;y`xR3B+|x8R5VykBB2#(J_Y{c6gm`z5TBzgFx&sD-a|l z@8qv(s&D!!Bosx~&aL@DYmdPomO%?6uPYDlm5wB<@s5$eT81#E!(M6WKW+cCAAXw! z*P_m8TVGZ?d`WA{Rk8DW;pJ}UqI>dVHfWDC9fnyw#D-AbMJovc7~*d*k$4rAzbES@ zRxNf2NQ=qQ9+`WRFK}H27c2CZP84sEfdRGf$gA{MS?|D5p}Ru$hS2x4OBNfonwl$J z?p=C6*6Ek=^9d3ZPgK)504O?fLP)Vf|+L|BCOKM?k!<*9z%=2fc*NL;=mIOxCRnMf93 zaOt~I@^MwO*b=Q2($PYkEmuwOp+fSYN;}@eH)3wF%i2&(tc;K?TLs^1tb2rnXt%c1 zspQLdE4=Oa5HDJnXmC(nJwh#l{w=rE@hG_bCAD54(Ae7C!QI#nXG|TNJ>8>U1fTV* z*wMO@s&s3i!vidwD!Smt2QPIevT33##T)2_$Z$73|M;pmiXk20|8G`B((AwfS3;-d zB_PA&UE?pK)|n|;Y3V6RN%iKLZF>y^Oc@G-FgQ|Hf*r)i&ZXd=EjzEOD>~gMWUdT94+Yi}dPFLCAF!S&q2Rc>`^ee@(+cLFHh*{=yr zP&}Nt;WS%lan5IS@YR3V9dHt=rj#9~_BKb>r#F>;gx%KX-BM!3Ory)iqUbHr2S+z0 z=E^meWhNX(Xmw%~N}s6jWP{f?MD0w6=UJ&C3-@zwmXvG}L?8}?E!x5>Zc_T|Y_&S! zCe-Is)?fc5y&Czeca@`zIVCufUHj3mqh|E?FZy^q?W6uL##_9-;Q4KX7^y#@+8eAU@md&QNmUsJ{vzq;aGoxB*lNddYt2mB?#3^B}n#}l_YS_|S_qG9ZFz0Y>m z*gI`&Gc|5cJ7(m1w0s|fcsP3DB;b}x%kn*VUvtE^LjS9-)5qIqj_{LN3#;@+zk`#G z#=ApaElD&>-fV4{i5KayYJW8bWL65ytVcZ~<1HX(jgqt!7gLRYv43uc8-*8#GXan` z6x!|$)Zp^AZd(1>up~T|$|(_q8Ht%LTYORxdKj? zIRo~=`zYRR-gO3hQn`Z~#P|}~sLkV)lrmH-)=;oWZrB%~hn9o92e<-4wSd@xG=l{d zuCxRC?aLGE3()hQTjwpFtQK0%D1h13BxSb1{O-oP%TMa|#M6o68`{Y}IaNK>mv{Oy zR9WN;)f46UHI@EO{*x}+v^XL~V6ZtB?MHt6W4B=mJTExvgRc5PEFuStA(zESF8OPg z%HWJ6wD6`>C;%N)Cnr81d@%Ln!pQIi>3O@?gzrOJarm?@NS`7S1K>ZrnqfbJdsl1J z#yKeYdA8iRS>fC6RG~)Cvn34HC__`hFgtWehb-k^+2Ftc2uPn%)=~VLuF&Fcp4=+( zOyy)LgJ@8GU}aPGMgo<5@7e@IJ^;uL1OOg+ta^jB9`(mS5*{25ue?;LWl9&N7URmB z9~Q1GW9IUkOB>ocvMXyUd^^$xL4|Bm%3&(Xq>zjuk~nDA4*_P5k-yFSF&pett@s zP}DDMq!YQyM#={&_>bCc+zJ3uSI8g)5k(|I*cl`GY_U^^p5Blrj%SHd%JwSPqG)$U z|M*7<%FWBj%1Y~mC=W_kZ2lid=N-uA{=fa-X=&{eTh$4IAT$U?9mhxzdn7isD^~5A z?XiiO*fDx+L8!f|zV<3gjH2qGD2l4$pz68Lll+@M?yS%K9@q7{9%Q;Kv2TbC0ZyC2 zDSUw}rdz7{=V4nsE0rF$nX0lb#vi)Pj}y(I=I9l+Q4;rLVE}vR-4y0Ztp0TBC^s(b~v2|HKOzmuPCG zG4k=i;W|1|PdRMfw_K*{wCy&zBGDzyt~%L4&wc7PRjrH^n+FA;fP{r6FSMMfnRVuE zvjbhl_2_0g*W`yywE=7xhz9oHr&4apV{TYGOy~ZeZG|(6C^#@q!KWPPI`XGs(n+R z?A>KBdywn8cs6-1j-$cDLjET7IilQW%4o;40`c$4a~=d;Gt5gb^n^`bJtD7^ojy37 zme2Ph-_~L-Rt5u=+nw>O(5)=AeLMNda;>17*7XHr``3?UHiun(53IcMg@)fH^0O)n zdF>LnUV#VbAj7?(HdhL;#a{Dy{G3PV8plU)4jn>grsQ_NMnWu2qJVEg*5~z~U7fOS zM%lcg(k^4|V>MGZDHQD-h)&#UodDwG9I-txex zF8CbTh|!V~KqAO1QT_=R>$^EF5Bt2jGFl%ovC4HY-R;*t80PrJ!;$2zhyVIcc~!L@;EC z7>Jhl+dqrWsjnG+yU?~#KipJGOjvuNEJ-m5BnVf<#tkLh8HrDbk4Fs|70F^^U}Z6O zCKWzIyYx!DR#xITUdCBYui2Z`A0vPdj=+ve&G5AZE3$IYGchpGalzH&l&Xz-u>SR# zsG~xkIh5igUGhwl(Zm7N zWQjT~QTw&yVrh_5bi8m(Olo;_!Hq`I3J7Fs8XzwyBPJ~3GvDCxtUgS(&ti&4MJU7) zX^d3UpD9BoDtA@m8a`2hS{zu~EVue+r+{ll~ z2_p{jyA$rR}S25YOyR4dtd;0w0Tr#))+kBy>PHp>wI&$Nvq^oieexs z_+Io0YftjPk;z^X@wVYx){koKdMqFmyBg!KJ!<>N0yzYllQ)uuk4gcwIK-sTF85Vm<=rCe&IhP;O&|{jsJbV z*$pr^1M=rliw5(A@SOz7x0Zwxp(@ni5QDJ}@}l(fXnmK`sa9%t?pB59R+Z@1wn(`P zY@y=Q2e|Cr0wwbjO^(`v3LC?_k*}24=tgK#PkD;*NkA~@d zn~zj*C$7LY(USZ;S>eQ6XgRDRCv<1wv0w4hex>D>-mu&xq3Y;o4avJ{pOTO1e%MrV zJ-JK6VcZ-&ln@*J+i(B-TMdj*39)Q+B~^9@Kal{~Ct(^Ix*CQD`#Ci;C5t=`Wrl1J z%#8EUG`F%2^)tyEX|h=S#h79^6t>`jEz|3{=%dXCKjDIVeTeFlt4imUMb=D#b03&B zy8Wyx{0t}}ehj{8iQDIh+($kEOi+T2>)-Nh;hapg8qR&c2?pHJ1L{&zQ2n@`{`8=b z*$i%XcPz=KUBY+=W)T<_;}$y@8%G;UlU4-R$wb#Q2$+yP=Es1)^KfE=1*_McHRO(XqmK(dZZ1X?0f|ofJwQ8ym}CO2Sz&o_U6A+OoMvu_w^P4UcGRl>Fb{ z`d~JJ`|Ml_E`%D>WQDh@y=G=}%a)cOt!yl7rqZ1lJ=UT{Q<&cQ-E&H^3TT zYI$)L%s>!a?CtH6u94tGXvV`bUbi} zvU`5+&VELRHP~5E9fHKwUQ->l!fe*+4i|V`G>O%*GDCB={JFx5S{JJHm8+TL>C5g} z98NR|?w%3#o6s;|W~3$xq9MVgngXwj%+jaG=>j&1i>$??1AeI;yo&pKLdkE!Bgq0_ z^bqThiipy#3M*Nc#3VDfv69^JN+a^x>v);LJYZ1{_j5+lfqXN^S<&g@4NkUiA4v~A zu4;QR?%lNa*+R4vVRvR_hrLWa4>RKWZtqyd4*{&SwDc-g8cEQq#w0`DL$k}8`sAKx zUjXY|$Z}ZNBD5l7M7-LxwZY_azX|0pO>iYD`SfO0mnQRpLyV0rp>P!rejYjoKHxo= zFPIk_mpOfmTf*U#$T%F%rYV~;bMrA^vTc+{I^;`UUgGFq)g{ysJZIz zOf*nD8Z=x1$u_xhGuWVaT`uIzim`$Sl8aBFGtigECIMpB_kB^q@k~4p(F`8*L?vwS z6Ky+*tE0EI)z^GempMJ3iJs|TBHoJ06!h*Y>#_@4PkL_E@P6?jfCtQ%iOpuSZkbjPuK!glnzU#*##rm<(8Y5|j8WhL77!Z5! zmriz2Mn$R-V{(*igX>z=am-GZ8X(hAv0A@F!w27-f)f|E%9P83E%Xaqo`@QJhoV|_ zw!#hrhcX<+=n=SqK?XkGvNWkJ83v1REZ3S#68Ct$K`?gB^S&cZv1Oyvc!_^pat0LL zo{i&|+b0D|0>VYDpIL>e7mqFKsI^BnxF*9lI*nz~cjJ(>z%fHJSqQgGrqt`AdD|w+ z$8!N%kv{}|o#jlj(SuOaSt05h%&@9P=<_exULE;_=FKuE;Zi42!>+x@mfPJ(G>>eA znwq7)B}NCMw*GxEscD?Y^QKi2(69?iiHk`Ii-#Q~bG+Q~LYVA~<-vs9jlbnZ-5U5$ z9_=((>`7<-$4Xt7KABhv%rcO*E@6jP5meP0cHOM468!jm%1w~R+cT1Zl0EBo$Ltgc z3YcfrV-jeS=oS6Mh*e^%mHJ60a%Ij=>#T_#7>pMZxqsAfHDc*E63c0(%~Hgs0CiAX z3J?(xS{^IU`5Ai3m+gh^v|g>r*~M1e6dlzoLi!sNk3q3xxe}N@(B~w=4#|sUh|qL*C7P8&%7j`KTq9F5_D6XHH4Z70j-|~8bM65LKatq9G%+*KCw-PtVoc9 zmRw)nw5EnW3rWC(AUemT{U{ndAJl5NP_sDpY0FF`MgiGLtsFE;%;k(QXgUDyhi}$v zq=p2HW2RM1b`lZa)4|cU^NoMlbH_im>u)c*w@)uIcBC7Rk!rO;ARm2cW}ntr^>ncM1Cj8%+>4{{Eg zb_t!E>bfF+9oeCJ+jF+-9-}w;%qC$os1!vt!kt9OtZjP(%PQ8+cH*f{zO-#8_v*8~ zI{$$n8=xrD<&_O-+|<&~fvUxLR^lTAG*WBMea7u`GO`I78O_0|_HQOQo_LW1Mrh;W z?#AIm99$fL@^H*lS_1#Zm}0H<16cA8{`@-kv1NYgKIfl1wu^!JWkUU$nq`h`|4aNf z`cHMzCgdZI_W56o{QtE}cYJt$4S;_6o&AE^Uhin}66P`Hme?gg$82i)@|2q9%{$xR zb^vVV)o}!Lr%6<(l8of`L2i|9uJo_SUgMcL40gf7Y+-H|lU3x2x!mzK3)*mRJJ?+yRL00CAkpw)C!$t~gsZ#E%xAw^Z zoa$H-Qef>bGO8ZSZ=#83Rr^fml%H=SIcs`i?$&anI>}I7+LjNlmCuY9a?t(vxZuV! z(OQMz*+Pxi;~sAT$-m6J$Je+{#^;jDzZxQU+P-wG1N^sQWk?9-VqH@k@C>it;XrXI z939n|R@qY|IV3LhiceRV#H-~KSRt9Qtl_C9^oB~8QZ)wWhHb#@ZV*r<-)guecy-h|%h{Jui(xt*LJachbWFbk$Kr=_qYC(|Y7L$MW{Pr^97 zm$EW|jaeb7Fh4&#Gqcg7>e@5jphdsY|4jr^A7g23tB+CpmSl$1X&ZO1YOudHEh6RC zuD1G2F1LPtX?FON8qsJVdf#;xfFG%Kf0dKR7~l_t~Atmr@x6r)MvgB@$E_A+jw1cF2et(1@pD<_j5M794kOwWz>KyCj{sP30v_*u=dKWXY{9#vC|iMh+O4 zfKfXn*YX?`0GU(ML-Ue1{Xnbu_s*)`YZ zV*tNjxFWR>r$NKS0|)=N9vlLM;|-o{t5ghWTNltH{hRb??d6wGZ0dIy6oJ*)%=)^Z zh!@%PVM`PK>CO+Yl}<5{hmzutgr#0+^@K&7;!`rqp`nv0(5;|WXH2iRk;T_9Vtdk75P6NvNil3C7$qILhQuy>E zFekX(REefu51Kg!d2Ycak5O%3F2pT&i9+Qb{lN)zr3n z>KYmJGBPsqInL9@t!tIJRZ%D``C43f?$&$Fu494YvEOT?qr^;+AI85T{c>JPbnK+DN@V;r{ zx?0NqUE5XLHmC{nq}8JOajB*i?LXUU8f(k{%hZo9(f%zufNA zN}Ah^{1y36G&v(0D2=%gFenrT1w-5$D-s3wO<#6PcA-wn4S>^r#mzzp@4hXUE?=Iv zZV0ma%-|Qp8ckO}{)C&Ryd|gUQ&Z#(nRKel8SAI($0UA7Fjk#7hTWQ+@a>duSo>Td zQb88+L@T%V$#!0Rd=u-G@tMPnhQlH1E*%H3A7fC|OmVcWu=Y5v_fMNgWvzJUthp{O zdABYZ$otowSdFVx{jtUBCrStFyTflaNK$7Jk<>-ouJxi`zAdP$G(fEE{aO4p(eEMb z1jztGsI5EQ5rCBGcpASATY>23awkCgfn;uOb?KKC)*s_`(N<_HD|)@4;EE@YsgNS~ zKFtK0K@>8cCU!9G70kFGxjK5~<+bi}Py2TN)oumo{P`p>@!|D~;-1KqFs@;bNH&lr zDm`1vs=+k7%};Q9T2~k*{NeaY_YI~bioHKNdh^x*P~+a6;sv9#S*Lqz7J3AQ%|Oj& zX0j)yO{UzQ^EYX-237cM6<=dxTU!SQcVkzcpSU8;E>gGV|N*r>S|;`UO+6rPa8~n-jAYMBPHW(|pZ2ZmHHZ?)N`d zBK>s}t+e<;8ij{bn0Po45Z}g;WO)DbXIxi(vQ)k;GC$MJpdjLMYw>B#)a%>~^=Kw8 z7@C<6<~}2g6ae=pJI|#^{%iooIOn5pu}v;dyicE?FqbLkp;{`HDwR2oZX~dJg??a+v|U@^4uHSs*&E~TI|_%Y<4#-IWaBy9@D*)-nBUcsA zQLGPY7rj35dov50rFwdzNv(Fv#kDyT-MSPo6=kH3~(^xj5vW{T=J* zJl$PD6$Yp?bh50l3T5V#yH-gdDRWv?RaK&*E)SQheqDK$+@0bAf!KLCd3ZS4+1R9A zGT%Xot0$(}$-xk>wH~*566R*@4eQMFCn0@Id_0O^n8Qp}p_Qc8@B7c*Vs`yxff6l0 zFd2H(k)CBz#!SbdVyV;UY@`CH0K+E3e;8m_kgpbfYCkon$*0SVfE@2|l+KigXRid3 z3GAgC!lgnRZi8z<*W0tWnE1Gu+iIoR{Ji)1x$lG6Rb9I4%QFQ`DZxMAw0!=k;r}Ac zI#@$@RktgkfQ1Vlh_Q4J34|!KJ-E-qsp{384o>wu)w3vf-G1sOBnK!VL8gx+&GxmV3=gpGSh!H&#T^d7j z3In}>DyH@TmUi#RI(y|&#lvx(ah)fPqPqnsGSLgxX{K3cGU|kIWFZ6Z3OtT0vV?LUJaQH%)%>}V@v9YTLE}5|?&O#+e z0&(+=9;e;y^&eZ^h9nSSD=DU{=5}70gNd9GNp}l_+&EE8+;!b64UbRWUhYJmj4)BX zpRNiS=gYY=@<{795Ape#gZg~JDz3vhkL){NXkCvnnt!E|HM=A~Op|b$_vpB|Sb0=b zz)&dIcczp@WtjL`g!l0JhPro+u1D;hmA48B(wxfb7p;$oVh)9R$V`Rd_qtXpY$PtsN1HA^50pO{ zw`G{zA%$+UK#faLS;ixN_`OL$XD78`F6CsdcikPp_Aax!ob}MTCND2KFo%Q#e*f;7 zNYeWS(tmx?T^tY13=Qr7p=e=2w3rKCxe0LT6f0ckxUBzf>^*+CWpj#rou`}ur(T=a z64hB$%dm(u;_9%e{w4aDnbo*d#eon*o6G^W5+iE9Z+UjverDxOl*mM_HD@`0)GE|u z0c>nm<%I+0+k*5v-6$${L8sEr+{Pf{m(q}8^rXuA#$6@>gm0gwp-R*8oY>p8?j4=P z#6)}&J}EIVU)ojH(=K34G(RFq7&%p-?7K@%z!!>I?G#}V)RnZG66>OHb8lx4=9QRlS}L6@I{x+Sx>9O{BZT05 zCrtp*cdM1`B(frjI}aHf7FR@pgM-}(a32&&Ur}mb_SZ6 z)ZD9b%(QZ{maERH(Wj_}0~av$??$qe%CW|l0XHk$eK$swjw@&@9|cp|pWd6H_+($U zd7CvFU53BRnJb?i_=ITO01PxL|HL5ktPGssHIW93Sm3VsM0CQlA;?k5nGqOZ6@ncy zhmsH{cLWnOAJP*);RGKt!rU{B{%EiccI6m^SF&#K56N-{hdb+XbC$X)-3H9gNI1B@V6F>y?AI;J~! zZpYug!@%#S<;O~yPPbz>=;_WcEH0cteS8LV2RTyfqNyOnOl7IZ(eWqYL&!iX2V7@8 zMk|dAsuME?iIp$f%GT-fBKw&NZB1n>TyR<3tQXth6`Mz`HjMM zWp+dzm!I7CSpG2F5t|cjGFW?W9y1^5e^lUYdD)x&$9a9wG)GoY)p!;x@PA3oJJW@V zzT_%B*dAKASJk-6d?-2R;8R0bD=F~Tc^X{L)K8QT;@hXA0h1r? z5Um!?;LtrfZUwahsdBvSfkwjV{(P(bd@^UX%l;NNNaa^!an*!>VWrGtbHBrrxtD7< z&);p)$ZzO_5Wer%*Vm&Q*B#ZJ?=r&P*l`4Hr=RVQqR|t8M|ILbUk^RHMsYrBrkV>t zEx-KrcgOn=-@biQbB}Pav{c*mFVFGjZ@F2{-kT>&eg_8b|FJ4_p4m_gi&(-}m^-v{ zti8X}&}YE(OPo++TwuHJ5FN$+Iz&^A5HZxVU4G>HMmJ75aDSGf)qAQ^vSxiu46j^y z#sPL|Y!)-TuWGASQi@szkLw8!_n7@^4Y!aQ{Yd!x!-szpJ2M;Frp}Wn{RUi$zW==L z>x)ts9!^)piDvw$U|Cgd8voigQPbADxOPMu|5s;uZcd3`m%Z|7x zUqzt<;g%s+Xul5Ytog#11~btmvkD}Zl(V-~Wtw{(o_1MEg(T+4dj0d)Uwh}JgW~O`kqlh_ z;60#Gfq=oj>l-vlQrl&?;IBVO+pEfPlm9v0i4~(=D}QPSP$0m;t<3{%)JWy>ZBAo# zj3=5EFABq66pYJ1*=;_vL-BfN+1PPVe_iE&G8>XFS0PS7zQ0p!Hh?rGd49|mR62Tj zx!W}1-3ADej#f}U0NmX4!v;PilbOSp@HCMR*c=MrL_qzlEzL9m?fwP|r}}cQ=dl7rO2?i6Sk~ujIJI%>#^0EoRGBzqyZ z&13XqRt7nr7iT3d*$xAy--_VoXW1}cK+E+yV>I#gUnOM~$tuf0nfj+$mB(Nz*@o!< z2vWD)U@@+I5amEM%#yI&ZQi&$n1<(5bsy7kv9XHveQN!?Nweu{w?Eg>YLCZiGDl7L zIo|;u*Vf6623{Y1>(&TOTrwj_Q70x@yg@@|`EleUowt}Ie?V5@Me-?4J-0TqHgTnc z8)pvTS)uE9IkY+tbS)aN`}=Kn0k!#}W8BoI1GwyB}JSJ1OQbILz|D{k0AEBUWJMLB2&kxN;DzbneMdylc5bf3lWq|aVM#GzvEb`u zjxW#KIwfoYn1FM9tRa0NzHfhiH?H(+YMn%-TcI8AnQ)h4O{56p2wehnB1&}PC)VijTQ**)mK*et`P1(mNm$*!pxX?zc^YB#9=F} zEBMylm!WKKZWw@E1%6ybA{^)=5wpeqiB7~6xvtvm8Emkbci`IPm#SYo0fnNmAJFn6 z#L#|b9`JfJ4V|L8Yu>DRxnu9OtR%25_&}BcY6!RKTxleJuPkB%@cH6FtnlcOJG8(f zU4RwrH_W5ETzFW#rk`B#L|Q-JUnE4yapX6EmOS&cJd>)i9x*A}dy}^74=-07 z=GkNdNAs8qSLV93d>>8~P(c7c7KnSO@C3RQ?nFb&^#6T{eH+{Fa^T3!uL^h%@97q@ zd8_~G(tRR#y>+vF?#;8xLdg2v6dq|IFEusfzfl9j_#8o(;<*xY6M)QJ>QuVvv{}0O zRBNJ06szzuzyG7UD**UONacfB9^clYIbFr%1?lY#RYoI%& z`%Y)#L$A%{{VigwfHdHT$$MShF`6;@SJLdZfOo9f<~SDE--?JyiUX5|=g$Cl>cO{( zN;(3<Dpp_ZwxhVFb<3xKx2aEM~pW?@K7PG(F>Nu&oZu>fCJ-@7TLq*;QxtsAb=`r-!}z3N>|j<_MeTz7{NhIr^mm%n`zk_E zAs7r|s-gX^Kom8!VD|gtDy`Lm=SOQjA^>^>z%w4awX7Lv$b?$1J68dqKZi~s%9~ja z!Q!5v1G-EJmoc+Sbhg9ph$a5dG7Dw+~1t8y%KfRC-p#tWT5i0^0G=YGH@wTk+9=%1Uhqfb#r7- z*8^X(H@@6elb=oK@yM1A7;sDCK=|tGTY6c#`(nIwbaXmPy;ak6Bj-IiKmUIQ;rPVp zxQ=bJ^PY_#QD0G~x)2VI&^Ok%v_%5g(Sb^OVx*g~-owY2t&h3A?TGE-fGT74DqH$} zmG&$76@(lv*B+wNsR`jwK>(L;{H-KrkQt_K6oW!0lZT!m>}}VbiisMb)3DhD9Ea#4X8& zxz`stdDT=A0a?s(%=R=t@EQ=4bTnzE%;$}nF=kH@*F*98{OMn}=(_QXy>(|Nu>p1l>X892xZ9243aWh zHuwV45CmEd&ridGfUf4?KT0UTgHV-VQ&rYCSfFZUo!k>pfpG{hi$@902mf^>H#AYx!{@LXywyW z<>xSaLa&IR_zT_7apBn#k=i~@0}pcT7U@)s39{wMnGj6bXSBkV4{RJ?886{ryY*@H z^t=ainDFDzlbiM{``3$1*(Sqw&|UvZ1_24L#?taJg6o`x%|n$c0Bf>j1;|^%B`Yc` zuWzR9`Mx{6Czs`x$I%XLZ)tJdJ!?I4=8G#UYj$gEA3rTGEl^E^ z56ZYu@vG}>R!R9|#~(7jXa)NF*qduzG!5>`{Ww8sKeY*Z79JiM6zc75g+-lLe(cDS z^Z&Q7dhq>+FW)|X`#AT%j!GH>9$c)|Sp^^#?5s}F5N0n&cQ?z^o9Et_ZNH1On1c`Zu$C zqHU_rG?y{Em|8=M1VHa!y`0wnuFgi+m&f{76cgA9zC;wuxiS|-k5=kx_(O;)4<8sR z*7F|<6mz=ay`U@J8NMaAAfudtndtyrxi(#8252+PkDd^^MF6YSocQ|{+vm0f;ioi1 z@j#q|h9GN{0S$6E^%YD#Zf@;zq1;u>ZKIK=#a^5zX;AE`%MtK~^!)>)0!HGrhP4xJ z2@k{wJ0%R#=iplWRxYuIs}n?jknE#!Df5OKJEwF5c1R%Ld6DsxQ4c7MH((d-M!T+M z*M7>#%*!jxFDT5-%jyxfuDZrqR<7UDdPh$Od`d|vba*gB~*8EZFXWz-937kNb>6hcr+7r>{d9{VX^y@fFZL&VlN(x|0>A z??x`>RzqtMQJ{)(9$9ztZ2TB^L~>mnk~4B08#NEBe$?2uiwHlIl$4VclN1aa zYN$Rk{<#xzJQ8Q) zQtOs@U&41RGhXjM#)LAeViC>Mw%J=fL;kNMr&u=HQzS4Q=Ml#P<~*SrB-yN?lv^i zYe&@!6!WcG^8=e&POlrISKoG!i+=q8`%^hY?Mwdp?J5=yX>=6^>lJ>r&v9teBN(Rj zGj5_?-16qInJRDXTg@ebLB5V^ysgd53pF{nLA%PXO}iU6Ypgz&uQjXC{?tj80y(y? zL2Supar!(G#ssbcE7>9Mu&c|tx}{bizAT7hOxfvvmR8+0CM1IA2igj=EhYe+<^_v~ z?H}B$oozMUvcY;_FME#+9gW&&Hj0^V z={|Wr%yZa1%>{MYEgR7Rad8R2f$^}BI`gE0ji}dX{!AkqTjtJWwrp+h!Mb6n7C(5s z3eN#uM*fWfEOl_RX4G=}J2i_y-b|rUZ+Yo*#rhEmqM1{?@Mv>q0}tRyd>2 zl4*|pY;MI?RauUPGTwStZAVXbA`ebF41?!#2AII=Rs6Ur4z#qX@zl}e(ebap{-@&wmX(zi3Otr&hQ)t+X+60sE>1>|;mAqip6zv}C7(LJ47encWWA2p zZ%2z7=17>#9>ijvJ`AhUDAGfCE|i!}1CF;AY&#tYYTH>xJH-w++0chK@d8~1j@s%m zwEw8i8ferQE7zQrWU2~b#xny6$@CyFFgBX208*)3fO9W*HPZa*#qzS11~L_35`|dm z9B|BO#n+|@ua<-**_(JqN?D|BvE4tbFsE=suO?onG5Ot`l2;l8&2?KRFtyj3k>7`cK0%WmbV*v zl-w7_jk=Uijl#jJ`$2WaWy2SH%_8)9K&G3J0z^Sn8Y<+wJYG8_Jr0szi6lY*U1K=_kULBDG{A|G5NhuK`}UWPuO$qs z2!%SRJ56YTj9&a%VLeG^{BO3{WD0uZWeuPR{O849i8{QGno$SNO%t=7sr#a+;S6*= z)U46xM@nH~s~!-#vexQR!E!dCWe0Ki@K;`S$3LU*0h~@oV`h0$T|a+-7G-5tjy>SN zw1yLQGg~6A4An7I)S zW6iWF>|j)-ISLlJa`|eZ*?iuj9SD7SaL#N|F5-_? z`>#b8I9#X|nLc^ZXRhM#(!KdJ6DPGlMq^;0Y`~F?P-5Z?z+voK;U|~DK5LnIStL>x zkjm35Xx_VeOk`s7?aS2ArbW^k(Z51?<(nR7XIMPBsqin)1`r?(Yn@I*Mwcfva^6iM zW5u%IKsB}X&59Ht{$jUu_{Hp54}Rh6N}~q_Q*ln(Co#7n^EE4`i&O$HVqB95j#&%C z>GO3uI_Qaa`V*7-x;khpigwXAyA2=*=Z;ptBlvHZcWLjKCdQLqy>aQ3;D6Kgv#h4J zrMauKyQ{0Mt+~4P?+dD1A$qahS6|oG!OhFZ9|)s#w>34qUQwVoF0Uugb-yq-_wfn# z5BBgew?17#^qjyH?7pLz3KYQ+-x8x5Z&lF~ga#6;i~rcYd3CM(Y7TdyIaVBIu$q^k z&fv%($YTE3B&1-W?@!m+7wugP>UUV-f5jwl@EDh|n0%;Fya?BW9Scta-d>0$!O2=m z%IMu^Wy3PXDH+>sjV6gopUf4`tT?|E?9zXo7QH6VZ_e=F0Jl1j2gIgD+(C3GlPUyyitaelgM z-RLv9cUCt~IaPhM12G3kd0Em9yO~wY_hns@p4$b95c+yl%2uth9vF(I$XI41_tDbu z$Bkt8x)>jP00~0tc~}&iJS|@eEI7<{&86O4L05D`H(0n3#v6PH<8`qlNE4=Y^VGt2 zMF=wWN*RM3lYQ3KaKl4Qgiwe0S-~NFWhh3D&|#3Kd8~4nl}I`a;-1mC6Gwu4|76g% z@t8lJLmkEHn5OhjT-Qy)g!>hJ+}cgOIkEKI;^>~>EI(@dAR`0G#^l5l10HpO;GE9a z?fPt?8*xzo!bQ)#R=ti2e)R-8#gZwGP5w$#>4QrgBo#5UG0kV0Iff2?TkMpgSQ0}^ zBOJ@`xRte$ll?HVvR1kalh5vo&J;BWwi=g9X&vrx1kwQcRUE!WX=)WhHU;!JDyOXV zkR(K7*`$DF^C!>QwpkA+9-=(=;&Z8+hsY1T@{o#`^jNHFtGaB)U9GXlZa3&siPmA!%hgAJG-@;DfLe?u6 zlfL@thGEYZjViVrh+|}ht!w9aj#)1f_kF6*?c>50JQrKdd!FnapLMM_87UZ-WQqaN z6LB*G?0u8zyM^NXSQB&JK}>{al0+)9&N$KqmDc_=s{cuXSlKOH z9-5`&SR7sXz{Z5K_%un#L?-O`ZY~ZoH(mdeqBV(Qm^J?_1w^j2dK6vPUx;?Q&}Soj zzx|bw0i=XDC#2><4XQY`8lQPQS)zDoe2p!PjV&6x&n<4bofEdzmBtE4)JK>T6Mtt@ z1cx;CyV^#&nGJdWI;)W6l3HmuRYIM>|>z)D*7KAer*yaRp#B7e3dDJS@A;oZS!7DG{Z0FI z_sz*m!XI=*sUezpU4v8WpZ0)yL51xf;^U6PVF&k;iyF||x)Bq$Ea9$m6 zYO`=b0jHQcHsEBL)6=#&Oyg_nE~CZ1MeR+G*`rF3LzM8XTepBD6wO=Fw-S;;kpAT# zwUEPc$tIM=6W8E*l-;lHxGg$UV!4#EtN4ePkN1=;E{sCoZrSQBc&5WQhYDdjWpO0O zAw+p-*xHx+aaXEre$a}m{87tlRh@V=og&o`(LXBlP>XlGrtLybL?2)@k!5SUvyOv0 z)CdFWg5cMdU;zf&+ZuhEBgqf>)wv#5m|pLq$>a~QhG8O)K{@$Z-EQr+>jg(7MHpNG z#6YVl==(nh7#lr6yCf-Gd6rCL>XwO_`Q)VCv5u6v&u<2^;tB%)JYjA(*`81hETIa~ z#^M2FO(XZH^ekB@C+v9I?&|!N$zHZ#G=EGS zz*{lSeSLn|@uh4s8{*YSdpGuO?44L<0SLN0<%$Z5=14xkvW_d#s7MVo}Tf%;RD6I_<9rH&}F;S zrw%|w`etStXB;!Pge(VAOOi3-OTFS!@{g53Ma6~rxx^m(S6%BRca3;Cxp?SU06CbZ zb98?+Z8BoH<9&(G*YbDofbZ^I-JU0T_eIElm8yv3at~NS681E|63(I`f=-ZCJjrGm=<%>&hdzlHDbmtz; z&9~;5x0#={1)a%r{IHMkU5sIgd-p&|P*6-%R6;^T6e9RytJ@SlA}C!tAquRVk)7YS z$GZ%&UtTSk40mc(=YohCLxMb?PCuPT?#R3;k#?j}7tQl?0!;A3IVOfqc^_vc$X%%K z<%V4@NwSh{z2SLA*58?7Xs0P2-zYH>&XrX`xaQFl*PbmB)yS$r#HGS>;jrrY>7(72 z!-G%ul6e7fK&Ape!`)kd{m-<`f!sv97Fmha7Er3vnw$VoBS5)r23YgHTO7{SE-d*h zfq<;FzTf`&+Ze&gLK7p%HZ!As8(Cy<-(L+LBGFNCKLkO--$Yq0*Hf7c619|Ps8&kbVFi*Ap{`f zS&zCEo2XK1z2(|-ja{-ko5`U*yIy^Uw=x70+7!#ipq28_NgIMIwZgE7O^5f)Y&8LmTKD=)Ze6zLwc&og40uT+bsBp{8$Z>E(d& z)z`WB9#@D%7ECpq9$FKC2f%XHppagk4AZ82u|jM+H=o9)0$VcoMvG-Xrp1+;+WFQO zAB&ch?)wKzS^dh5STEjtdm&rJ^h!kt@ZEigYGM$8`e}9U0_D`M!gRj0P{fC%%&qb4 z$WyDEXZgCeJ2@w*|B+|i2Y|F_^{0F$BJ733ViHF$u@-=S%)bhqNKm%T2TAk>Iojfo z$tv#O{(hZd?BD=}sD$XOYw`bprLRh0?V7^?u#%gWn}+#y_dIlFC1MtcZQe1YXGls- zV#X)a(-k8yW!}{}cFzrqk{|i35P(1iQqbkEwrS>^$o>kwe4xAT9|o*aSwyo}H?7uM zS{iQFh91^GTMJrn$HmMF?xPv6)qD{gEO&3KzXxhyHrdjrla^%7s%O)43kuI=@9$#b zErhe4j7n2H_Fjfj8zd^zlE#_R(r8M5)5$Ov`;(pjL2eFvmv5sibGPbMC3&f%#Z-wS zeP~n0vQW#(?R32NxOkjR!dOjB%>j@oyJKwi|4oV>vh_oO)}BFcOetZfUh@?5xZk_; zMf&}8ef#gSFtGCT_yVaTS{&MvqCsS<2FUj@5Y>Qc`s&|PNUG3-Q76tpclNe%&5~ma z@3l0oojYXt=cYrA zW4?a7={oP7>tcr2@8g)u;5j0kb;&IBXf5LV&wH!E2mO*o`VZgPLPRMF4{`SmUr4IMp83y%*1e{ zTt3!5pL_M816LSrmzHhX+trvCtA6yF zS{Tli34Wwu=Vor_q=En0t3)MKbo*4LDRx@ayFs);Ow_yHNN9$%b?!v+$7U68E0dFh zelh4E(&h1Leos>tk}cFKtxB)Oen@EuZOUGGx0ff}>pxA9H3aN;YFc()Q z7Eew5h$h+VTCnq^g6p)=IrSrmt|4*1AFw0RrOrR;c2I>^z_!KGAG+B{uoTOE%kbu0ySTp zE@i)?$KyRilY6XA4TA)b%sZY$Tq5`InfmeQhpB@xtRQ+F9+ewVc0bYZ_O4((AdRgd zlvfzRvVh6rJSf*0DcleTmXKhW<@sN4Br{jsu&q-L_vWh;Qxj*&(*}{W3END?+_g7x zepR+#IuP}m=qU%2&3rY%&@y*Zy(EsJ2wEv{H6v%{Sch>j$HgpOJ+9t22{hBYbnz3- zgg4AS!8adfz!GnnNXw!*g;gGEK%yw4=#!=xmL|2HQh(0-ofjIX`}Q!%(6FiLfFoRu zxBip!V^lc%X)CRk=jGM>cjYJ8&K=2-dW~4#QU0p@`5!T?*4MOP2+t#Hn_h|pDk$38 zMvZd^<8;~tCfvTCGfBw}4wI?KUDEuw>DYP%O@@RW0I87Cu<;vgztY zBe_dAOG-9SW~GN&w&3!SCxt+vWQ1gZpNN+iZa9_{kS6Sted;#6LQKs`&P+{dcj*v% zI!PQ(lk+twot*(=nBxMiNv!`B;X8IQsUw+Jq2kYSd zJ&{nF$nYcoMnfKYc9u-YTbpEx6{}tM>J7aByLx+(%u()oXU1{j(?i)~N5jt%BeY|E zs^Nu@3N3Ev-8|Ne6e!xfTEa5KscU}D#AwNIY4-ipOweh|6FHJeJ&-D9zAPzej4lb= zi;77r8g^~HxKcJTv9fk_w6ZYLRov@bW}bQN`{QNf(3k&|{E_z|dAD;Ip!GI8}-uoN`1aG?7jUjE~ppHW9Rd-aFnRr189tLc%AWEC0;ohVO}nW;cNGQKQk~k z`-m;^W!rK{Dqm1lK zGG3g!yy%q|bj^b>WUyoxbeDj*)eM`Oc~2O+;_bd2!xM&S31-qg>Zqai-c z0{lv0iWFLRK#nNYoq_HUfLm z=BrYazTnHSYxQ;*1L2wWn&vj%?Z#JiN+omYiX#E{tmzcr^=)ddd%txs{E@3lm8nG8 zBWx#}sexM%tQ~25rkeNrluL!1s&9;-b08>rc&ukOi~!2a4=o3mr}dd@l)MT9bW+wm zFQhoa<9zPWWexOxTe&YY@+^eWOj9Csd2uD^{qI|8)-krG3S>-bQOq25Hvo4#4(eRS zsy?PZokt%lknenEwdea~q22J*j94{Wb;=&NgwnImz|jkP{`*hzl!$&$_%({jG$Gw` zLxnrR$JJ&)ErjIP0^|Y*jOEA37y#EJYxSBz(!~&N(k8}}souDD?K!AF(7@bF==a*v z-PLgFEW~ z7@W-UI+uIISSZ(A?WBtgYs_w}X}jqte`TACcK&YpFW2V+M7Y~fbml)VUq`o2F*t&8 zVilil%QC*ou%q43ZL!+uJSZqUux6=mG#wrMK5V^wjJT#ukZKz$U%>MlH-KePT1jpk zacO>cy0r7$t;$Y~MZ#0#>+6c7m!v7KC>$)nf!-|Cd8XE;L}&;*Vwu_%x*UXmaAMJ@ zR!#{+ISmnddsHKCQr;59R(aH4qWv3Lwy*yvobZ>Y`V58g?2(CD<7GrPQ?vc(~$DB`#V2~2ViyW!fxJ^9VtS>+i< z=Iy0w-~dW^f~{$jy=>S;g@fAl(e?g#HiT0cGSf`mj$S_`dW8$+xJ-?|x7MRc;DI(QZsFFsW!C? zm@wX);_cN03hu(6;E%0?+ z`WgA92zFi^bY@w!Umf~%6uNOcK-FF_JxWoA^43pCZ@<}MU3e2It%<{9dtQIMFz}8c z)wH9M0T5s@WX?+2_aJvQ#Fmz0ojf1?5L;MyO_cyc%vjpUBzm-+jfnFb)Ol2oGJcsO zsb2-Gqr@ySu&F|i7S}P36I6HP?56Z;Wkz<^1}$evrkJs~(nSK5X%`P5D&YB^c4h+D z4?rvHfTsrtU*EiW`I>N2M)Ow9;N4(>e6{Hq;B!y~1W0G8IaA4gubwT&%Z|Ebm>iRh z<_aNwA8IIp0PxO?{Rl%qdXk!!)OjiOe6?{>i;GHs6%+6D$uhcIInS369_c$Z#Z`1; zGEe4FD@Ic$+j(zc*2TEx$(M-IVYS;plK8&xDsU%dO#aAwE8#AWD!yyPTcpi!TrK_u;@Rz=$#x0IL+{^sscjdU{_NR7E z67uq*{D8wIX`tda0J9DU1z0)kDqK0uXIPIRM;e*sZKsuibZaj)aF;G71R!(KCUi8^ z;B*SI{zqP^6v6q0Z+~tmGKRNVs zGN=PveTi2_)p|s_Uz6tG1KuMF*&T(6mG{x*gL@rD>va#W;YA20&&=?#8GsYoV8&zS za&a zl>aGe7^|kPHZnY7+2LKS{dR`dBw%ezGoK#D$Em=n*khl-0H5LI9Q6IX+!Cs%XXs#P zXruCtsBZbQ(@f|{Z(AuMN=5a?jT@@U1LS0MnH&nfDQk-se=bKfn#{jf7Jlc1{TYMMkVb?99g9xt<(m=en-p-4Y2AYicD@ST7wKt~RVN;)c_(zSW z)wW>P7=n}n+>*OAy@^~veoe^bLk8!29C0?0z*kHgj&ik&a-C^l0%_+Gj z9MhwC?fGC-B0CzJGUAP2(YHSa?bIv=dI_Z6u;^}c?o=OSJx=aXVTqLgQSTrvGipkS z`Bd6;Ved=KeuiCDnSn$*Lf62oT;|hMnaC^75>%P=m~BcrjXICHuU~9>-%c_|nu{Gn zLD)hE% zL>4e+RJ^@oF#iUis8%S+R-E5(Z*>fsJV@AX??E@;AAp$*fW-ASl)LmkPV3VAV%Y_M zh)-$=-MldJFsL)yWjhM*;9rDB#u8YVATUNIMtUZw9KZ$l-S)zZOiuFZS3AtLQqv|z zD)p=tJN8Ya!bbzfc9fH_(=Vu5(HuI)b;XecsMkgcE;~*vv?zP8M)~P-$Q-V$m4L{o zoe@jg^}0EMe%egdqmg{JTn2v!y>W!iXIn$ng zR^5AnPrfYfNQ~qN@xz0CmFzU|_{HN=Gkc#JY2JndBELBZCjav~bLi#kH!q_L3!$Zi z<`2=OlfEuS?SWc`dM*w&-ZqvtS~!*d?1HB?Ca-rw%_|7qA_~^Jg}Pq4BJ|g`DdpF8 zY$kpAXF8sI$&E{^-cfH6G^jdcv#qR^$4`aP(~BsGfD$l0yz$X!ZB3CWAhnH5dqef1 zsnGgkz2*{EgYzgk&(ujKl-t*TqAQY;>W&!7X&4cs6)v4`r;j^uwKn6i2Cn+$+qtgc z{PnBX5MzvJ{G!c9jNI-RXZpvf-~F+oMukEiv!~1x?(aah;9VQ^{MM)`QLfB#jDJ!9r^*jP9E?MnZqT#~S>=5(uNn<=$iURu~d z*a=xRnKs>%Sz$|a53k8aI!&cIjAo%sj)g?)qwPDFuTJ^JU8ZZb^0Yr|HZC5xQU;EB zrjX_Hb`Qi#JxrxV7f}X&jOUU$=+Jg5YXf5J+L~4vwx*rGY~(R%u(3w4kNIJ71RXO9 zDAe8o#(yk?H`N&P4TOj_hEKR`Y~kx%JOiTz$i4PoJALW+XeMF1?)4jE%%VD36c)+| znWb~sm)UmjzR1{Se#7EzHoL?)2*3I8$r|Qb4@R|5GXtd=_3o9)$@%HwN@_+*HZdcu zi_diV?vFt_Q`}xfLJZeL&bI1sH zB}7qDQqa41sAv-i)OUC_`^0?CpO$bVx3`N#oFcTiB&7vIPqp86E04mYcu}JAQZP!` zLxhBc5XcycQX>m2=j$Bwe&x}=(-b#Y0QgeR)tRkCCQ;2ZRz(S^^LW`N zgonW*jpW{Z`I*UPl~RG68`jwTk*BY@C*JsHPsySh#b@JIK@kX{zc|@nMdWr|Kb*n4 zrAl{{J?1t&8x>3U=V%-sBUQhRE=R=s|4K^N#fS7B?<;6p*gp;6>Lh~mmU`8aI&1d5 z-0z!XS*a}%%J!uL4rMB34&0@7C5hA|Nqse|`y`vqMcEn2(ha*Bs|Kx>w?6p<(e;wy zTPWilv3{CNxJi#DdfEjMZ#c2*{4W?=;OIkg^9*$)#LWLP_7NlZ%Z;C#}bjh9=Yee)=GGcH%o6aP7D zeLLUA z_c&z*B}JqK1O#06ABW7`%=e#aD8@WjEM8amDd4{ibPhhvs6sgCmS`PWT`H{+szZOOqNj5T!JVQDtQ-AujzpYSIJ} zVR$$o%UPO+>GRn@z?TXqgRXqIbhSrxGK{wzI&4QCFu(avh&i3 zt)WD^lW|8JE1DN2Bq0R>KVW8NISzb79OsLIxdu+odm>`e;$q^0oZJqjuctEjqXhF( zpy}z^EVr|ASeVn&A@^PS;mSEd?{wcASy+4;zPGVZFWVl38+qLE-C?Yb^p~1R>(SSj zTKr7RuQhn-^YU&n-VZFqxi5Fl8-B1_Q7f6wLe;BsB$*o->HgR*ryw3g<7hO;wIgm6 ziJh!B=zE7}92g5I#y*p2;Nt=xo1nBN!W%LBk+}3}dh#ZC_<>~H%d6WfIhj+xEQa1$ zN%DYb)?!x*82X!KwIdb{T8SpB=28#4XNr1?xoj0}0d~-a0+(v<+qhLKD=Jx93MTHz z68oB^2Gvg@-DM+8Qof)*16Ce7=08s;Vz)Du?{pMv&9&F-=|StElKbYR+Ly9(bh*BT z`V=>_(RRaniJfgjrL`Z8iXEDkp6tJG#S9vJ58Umw12QUlRwj0qyR2-0#35zZaI>(* zPhbc)rsP;O<@DC(9*m%bEJeK1Kasl~3}%X9|5j+ASZLB7@x$)iWq?Xx!z)}banl)F zTV-oLGj%M(0fgJc-Nys85_r-or-Ij1@@jY3sxG~la`c-@s-Kv=_NG7^6<{23F~s4P z+kWdTSm51{xyLf)ogS|CMvlQa_D~$*xqi#kR))W8XCLfYdQRQQL}(P6N;n<$ZfP;2 z9zLfjE_eVW4xA$Q9!NvLv$eHVx_Qs2YC)=IM<0$^xBlNtb2Rhc6pDX&zWnhynq;G$ zPAR6+w8%#o{@jQWhsE^U=r`0)->Q7K9?HP0@o!S*>vx}jeEp2I^9Kfo)s)9V^?`nv zQZu7kD`FS-!wMxgUyYrXp{w?E;d)KjBhWFRf%q3q1}F(en|U9&{@y&s|G8$-8Z%#$GQXBGaBVx_y0|xsAicr$0>m| zy>?V7qWGWB2eoo!+&}=B)eDijUc0B??u`KkZgAMeeIW+Uyanf!c%vbj?F*>WXlHOw%GoiNOZ>D7BPehJtL;?>AHDOTkC9}wU$Q%8mWr6|P5 z1I8A8(Tzt(=joL_BU`)2-w%F&I_MdwESMbs;AL+*wX{}W%%J}~6=X*nd~DXCqKW8!*ZYzXDKXaA z^X87nzA8JL@~5oDS>Kl%-DyoTXT{alzV3V1niz<>HeXb>jXbz~Tv3twHyeUO7$H@| z4i)u3(znCD9f{@{Y^*eE=K@&_fd>M>uEWjg(WL!!)BqTBf`RxAFbOyylyvd5U=bc% zRGDu6uGsraZsXXukKa^0yd7+`mPeLDV%R#dQ&XI}UaMh8;OVv2Ea!oHQT<5!HntH*jAi#pkMFd5U-9z4&pI58 z)DhQ;*STyk-0BiV?oajlBGpDGCi-l=C|`Y3@pi#!EkABw@m)b0|452{|L)z_&;R}R zuA(ArhD=SUNWf>=lvul%+`$Fp)0?r3mxa%+&MT=obc-TsS$U0TCZW0rai>omMu$b3 z-l$}y#JK|>D)k|bo<26jk!q~e6q$*#_>gHx=hEJ*&Q{4qWYunEKJad#&mbo>s5Npq z#fgA>u4$11bX#t7jFWw@nsV$9N2UTsiiN|I^rLN=zo;s!EdAH~YFZ6?GU=a@eO7(m zW?C&0@1=!)J`B~VSAn8>%<-otl4QE5<<(#z0;F^51`_8-0h1 z`qkvms|){d!f&H%->0LKGaDwBKOXx{6i}2L7$OPc*VFb5x7AP|iY{)#SlUy?J z^ZL~Bn<(p7B50M=6?%GMRB2YIesDHRDPCd|1Us{W~oPj;~D{6PpKw&=Vn z^E5ud-R!9YH>Kg70jV;|b;|9hT<_YycR0SSt71QLhqk2NjCtWT0pVohor3pMLWR4P zbHVWV$+*F}oe@$gD`PAbsXV`6bh^*|m^U>+PIq0I9W=7~v8 z@+s>}Bt%mIq?q{>RI|R#xNgvDx=^ngN377l_$|EzYqWR#hWxvRqSX+LT(w1`?^*7(uFh(H61n+*oH;3ql_s;}@l=i5-8&(`e`Y zi9pm8)PCZw916)V443dV-YEvToqEE~9JPeY@KS0@dU8fmdJB)`_+w}L0l5?1F zZzi4+Hz{{br`cR1U>s9$I_5{cO3Yr$kO2{YE`u3RoX`Lx+F@TIB@*MJ(a(4%Vy5x) zC@NNA|GG6*D!FxP+qt|sr792@h>jxw;2VLEC-akWW{i7)4X@6MhWncT_!oGdUWUy? z#>%hl^3E}D{>hAN&aCI%LE5=9O1^JDlo zs_CU4gD&2SUsr*X9{jXS38fxjB#8cX6zFPf`n50^#fo>GU|Y~KVw zS@4Tp&aGASZ~kmn(7{jNH&5;0;^OuOH6<79Vrwgv(N?5+@fLJT z46kl+0tBTnz-l^zVVGRld4kPA+O;xx5ZxvBe(hES*i!@Nxy=AACt!F7#p@YQdRMuo z0z0{d(1lRd@2%uB7zg;{crdZ|pR(yo_c2Xl96d;7sBDM$@#X=qxgEtOBp*-`%u)m7iW>=^tQmxaR|~EhixkM zhKu&}+A{Rot}B9b*Yuj`4b)tzdPa*3E9-}L0oS0|s#w!FB}X2_p=sfPzwfFoPoD~? zr#}bBD_cxBoPr8!5Q#q7ZSq*^;^Yj)?6>=puDYbkiYJ&5cEc7Do~!j+IQbI+cyp*; zq3LE8O^RfRhe@kmq@dI=4<)^*|Bsgy??G>AXehI;*B*zOmh-~(k0hn$Wkzc&m9NoL z1?#-EPB(@Q(OhcZ-0>4r8g#1$i^XBp zEmM=)pY)q{UevtdA4LphqJrQ3Lzv>cUapmnewd?;sAM^8xzsJj-+bg|9kT~iD0vFOIPSut@p3K*5UtIeS2HI zTdV%I&X^5cdUe zhmnBG$P39!$iRi*q^YY8@nOS!4yW#J1^Q&&y1KBburO;8u^Va-Kki%WALb4%v(oZ% zqP3K^;SBoT#~C&Ar7KeKrAU1D)L*)VVXLc0M@OsOpvJaMV6kqTTB#HV22CD5K@o92 zalqT>beVg`>t05XSp^ZDXn+T}8J+eo=|~?=PL*oXckG&NAn-AzLyS*MMDm{4JszIW zP18?5)1~EyZIONCKoP2{in(44O89ULkNPdoR+BqG*xMM{Y9dVtXmxIN&SK%?pWnIs z)aKNnoyzMpHPfTg|2LbQTb+$IdW5g8pktb(*HmR-GhQX`g1`FIG;?RhtKN2R z9paflt@QvMGrUt<5TsRYI`6tzYuSNg3@EH+`7eB9Ee2+Ma!ng>(`JCqS*c@K&u4jR zx1ubYbQyfr^rZnilAggWq(Q*^Ed-=4cc3RT0z0>`^GdG)4SolC9GYkdrDHw8? zt^nrlhr>>DQ0;9f28>T0fG$F|A>(-Lp`M^)rg-fy&i}o9{qO77VYUf!;rPfWIe;Y< zyfc-$w%yk^8l4Q+6n_>lO^Ha=U{Z~Np&tT<2&j6EaUa?kzFo`zUg_MNy!>-zpQK9N_`f0Cf zm#Q+JoV#}BlsPqwG93>G>HRbxA28&)b3^STCR2}EF*p3>4xE_BV$kGsadXLZ!t8gu z_Ag0^UE6vVoC9P+Hrj_;f!Y=l;ef|j3mne}fi79{T0cS6%%XAWaBAE-4OqYYQzHqi z!W=r-)m{p~P;8(!gB0lhlmtSiU=6&+k2i!FZd~Q0p^01jy;R_fD!d-9s-#&Ubo$|V8ak88;BzA8eF8OlwQ%;4{!y}GPk15y*LQdsN(x#Pk5D@|OAXw9W#8v- z$3M@@dZxF|6?l?xC8Dm2H%mJ-wO80gt*567`B=ij7XSU{VJPB(ysXSUG#7VZe+;Jo z@s3Fs_dSvO()aJnO5PLq)@phdG6TEws9~ErAML-`vrT!+c{5ePQudq}NIaNpw`?ld z37WGnbCq~Vur2MUBM_nqRBSR%B;-d;wu|qju1`x!O1aCx<@X-ev|9U<-_$KDq|@T6 zh^_+Y1ltfHa{jGQH=%F~A6&I-M$IrtF`1_{xlDfhxvoL9?y=LzkaPPQ)_pxHo*ALM zWvs~kX78NQ$?Zo)bj46~*38yQaDu^Dq1~Hf6((VmbtM9nw|I=ZPY{b~S@GzY(dIjB zAK#Pyca?Q_z=n@#vXaA^bEVl?aO>&R`+i6mHN-X&!Wj)KqhMyB(;}$K$+Z&=-;#r*|uN?R7JT+msjPLeS5Cd}29mBHeyk=~&0{4i z{_(k#EjcCd>qN3~JZHlwp!TG@^sP3G+$uMh_*W+x5rR3Qp?}c6-^(mi8xmE|O@3WHwpdlBveqqk9G^M|lSO9_k z$<*(ps@z4K_tz61x1{mUw78t^@BvvGi)>4+VT(#6o$nGN+x+=PY^IRgVz1-PEVOYQ zR9mW@vMB09K-V|FW5if$@P!?&Qv21}S-ic6t~PH`Jm`+08`!J`gR;vwtJfM5RzpWy zYF1B(E5Ay`NL?4^5;H+po^LGJ`(eD8+B_lS4`~S` z-JRUroQ(0ti!(wgx`_M7d876EV)}KIxhj10(G!y+>Pubw8jY(drIR!)(qYcSS*@$2 zx>ka<^X)Sf2!j_SYNr{Xzo#>`Fl8_`6X(9o`reV=?T=KyK}Urhr5=l;9UC2sr5>jI z#Hi9{@And&KF(^dXfjH%99 zuzNgWl~ncJ9e;88y0=*^4b9~e6s=dtB^)CP}-hqTnSCbZNi)UhK*b^7B?Q%002 z=s$-TN3yYeQH`oZ-XfDjt211wX}ZgNH7-&}b`m^#EmVN6MM~MwfCS9VE0|xMgFcS^ zz2Om@Ra(oFYO8=5@o1>19U_u^a|_59ZcsQlD03tJHod16rqab;;j;Usy87VI2wMV;A zCt06D{Wi)jENaCbuVa~Lcf!ZjM0GiAR;r4URz5_|=}7Tjl6{(| zephFeWq(!rfmxFS@p0Q88-x3_(!^~k0ORN7&~qGT<5%9&RjYB@cYpRmzUJ zx~gYyp(E-D>)xM&W_6WA8fKp^O?VGT72TAphjJWUi<3K6{M(;WVsL+dcXp$oE8lXUIYUejtl9=w_Z@Vlht*Uul zQ}OnTR;WYhuhNmm_8D?>q|SfQ(L)tqhaMVppoBq2wFhBtLd$$H()8+MM<7P!OI{Z1 zci=6HYG1vtOAFx;eh7R&%)D&8ygKWY^vWt4$c2(cZy@)xvCr|VNu4VoeRq>r* zPITiYyS)Er!pw`CKtFPprx_u-kocg`vO2c87Ha{JFPozVdxpm^%-72zTGBbXpR2~x z(6F;XVNe*_X+4t3M8I3D_F%=rqS+$EaQXc6S!=?nbYLcC*ld2yRA#Jp`4n%a5%ah$ z>p{cZcJ>-2M{yz(=o_r}ydXo7AVUFj`pEhkc{FOU z2{#8bK`z$Ya8G7QxMkg1+O3-u90b6P?y&1Bk<{!Q)@88cZe_`pNfI8|9)tYcXREc( z*?jV4kSEGi%)Scl`boa? zz)dOt!sp8I5R_Ut6{1wcX+c~SwO`P1ICV?Q*bttp z>jM#>EU#;0tS9kl3O?*puALAQ6Y;O!#-Do-ogwqDO1598^q1Ix%i@0-P0Ov$+0N39 zQSE@WpH;LqeisEdKk^>F(m$_S39|rz6 zN*2p3^9O$`#E*jWd+wwtv)Uu@+g%k4B+I!VgS|P@*@|;nAaXQ58G$X-ix{B~4xcD9 z<|w#-afzD~1YM@47{B(?&NheCF&Mwj&xW;Y<>ZsO3__0@4r+>IPSp1#1&6WV^#KBl zz_MI=H=vumz`u0{Q=|xAFX}D6)>9Ofn8e=$RvFaIhXC&}sj0NJrl0EL?cE~yuDtA` zO!wr4GPSu~a4=Gy`408p330K}1;V=X$%={2hE0IfDk*sXzL=~es1m^#0xPs8BUp=;tHo@ZJw>fFI9v+&@#>v)G6+X?JP8hQM_q+`3`&JUz3pF9!e#%Vg?hEeojc8_sn`TfIJ zr%e_Hz#4$Q2%2E+$#?Ggo6Uc@c`cZ326cvMS_;_Ja(EgbpFEGpi;+3mWq5eVp3|WhP_+z5*qp98jCE9FJ^Q;@W zx@g9gm5N1@%Vic#2KZ95tiIc}P0MIKjfl^Xg8PF=^4F~5(y>m3sOjN6(lf*UJ<+FN z&4j4^)T-cJzGPu+M9urK4&)>r{`240cRI!Zsa^MiS&^wmU-?WuRR*x@;rH_V<_!*9 zaRKDnPRSVM7|=LXN>|fXG^t*^a3hACaAUJVrmvoTp8OF7QHZ1#wq7R#?^E4UkXD@g zuW{Y#&DE9jGfYO>{ln_BQ8u)N16BeXhOt-6K1+_J3Dgp2XncO!#uYBlXbMKM3Me8x$3=;COI|MLNM z;iDJ%0cFrLK^87aeP2QhSzj*83>HjFjMYKv#Xq`^DJr>oYQC!dbGF|<@^j62T zCBClG4O8wr37gX7B9XkC5JhTRwlXosyks$|*(TTf8^G?}m*{U|GYVC+`|0~3QY~7# zHy0-SuR>TjfYaN?*otxa2|DvMssXHweRaE}xhk{w&LCDoE!<6NWS2j|Ej*Rtqz}b9kKUd zn;hN~8A-<~g>6vhtJ0R$oF7S+2aalop$zxN3zPLBh-Y_iPft(pUw$Z>sYj&#qrWSV;V=cTgk3_uTi{dYlzDC?P1F)oyrC)Zfg!y5&p@e5ga0`ccHP z&@j}kxzc6+XH&(Cbnw@Bd&azQ-V6obW1CxzMuEroVsp6pgY*2?txV(O@WDuOa`LE1 z3OZV-)X9i5)u+#$B=0RvLV-LS?$$c5ei?G`t_P6tRCE7ON+X#B`%|*-BnBRi)GM`b z0>gQWzB0>v+6<-C>WerwlxKGYkkZB@N?J|#8JFE!b)h!p69ix~g)cr?P?nl^a-Bpt zuZ1gUVI37(oW=BIMFp6%(8huS*4SmJiyhezgI~gLzgxj~Vj9so}wh zvDyu=JbKM^)^*Mxn4}MPn2gP0(_d0kW|Ue2-e+r*9^plgWZjfN=pYad>~lJ2l*;Y4SCu>InpwfhGc`oNC+G3&K+=I$ z=otVi4}>T7zhrB;3Jv;r%+)w@Z_da{w@_=`%+SNU9k`U9RGSvtUbP+G6xVGbq{!GQ zVZlB1k-4WiC}n>R)2EZEH~x8QCb?0Dn28BxvbuO`IE`ui~Mifu5Z*LqIGoVAkG!svcne$ZtcuVFNEfOVDuhY%(aev{ zRH_yjY5m?+(KB^$@iqFXk%FPLo}&Y{t8pgI_ETh z{-XV9jHz(SQxqy6jdSs=7@qU zQu*(@Xnd91t1|h8*{Y@1P_1tzX_3H(J#+Hvc-3*3QNrorkEEo33VR1Mcj2y}If9_mU_A%r#rrBe;CGa%I&qe1j?nHB8 zBnyoI(PAMXQ4}xeh_$FO#qtf}zbquB5>xZiGjdYXlR9iVg!~67K5&@V^%n8;^^qkM zNEr7{3b#q^ANmS_zF$Y1(Sz#4W!HRaIc%+oNX0pDL1V_WhlV~B{tiIl3y&0%{=xFI(!5aQ)Oq*N z*Yn}e7p%&UKYvV3i3Z9qugpfvd$2r$`q1!9^}tWz9YqA|P4cjpUm+A>FFj#o&p~`{ zEi!YiEM{XkhlSzx?K`*c++vjX9oe{89Efq=q>{t-_QeV7s-!3yC&FBAJ}&to63TrGeyfaH~FIoXD?yrt2s+@v7)=xreLIF%%#bjaC~> z`OYa{*1oA;z#uOQmw+RY_`NR;`8)6)Ep`qRiWkC4LqkD+BZ@4ZmA8R)HptKXxZ=0o1sTJ(mn$CBjJ&H&AJ|`p)gI zy9a~=eA}b1h1QCZWR&RR4^6e7+LpF73nch`@k4|bjk3vg*#H|lRw#stnVpT9ikgOo z6{Q;~!hb2sJsurlkflat1}4vv2d333i6;B?HS#*$g@1ft;4r^t^Fk>$D>#Rmx4A&4 z++qAD6^oj*GnMPHB+3MMvA)eonj_4+8wUH7CS&d}NVpB_k+GP-Bte1G#jM5SbZ_CF zNu5okeA^7IvHcqNNUkW{lPmDg?doq`=KqyV{O=#t|LF_=@9r^+iNvEUo&|eG;j0)T zzeOFFhswv&0P54-tL@C~927lLW3>VEf($s|veAzSw-^ls-;`P{f;%V1)KtXT{5)IjvcE%h`#8f+$m}J_yJSw)E3soc zJ78UtiQ|Zj(sjP^L_D<0wRx|qIVWgh8x=qXv_i?nU@OlmY(g!iu3L`g z`e#PBM2E$%xdw@4^K0ak)Q>hH^_p@mYcGiW2CbI+Ip5vQOI_RAo<`xC9}*f= z!Ks#fK!rR?J2q3wU>m0T;PY%!7a!1k%0lw$cPlF&{Jz+qdjXHC{y$A;9uD>T|NrlC zC|eqgHHm4AF^15PB-zbiEW?aF+4t;QRL8z=W8brkeF+T>(II54*+cRnOGu6+M~?jN z{`k4fzpl$%#{0fsujljeV8*g0v4dkDJ~=rliRD_4^#GUhf2Zd))h}9Tv`$**%a)ef zinDoVPrbZ!2w$F1{=q**nh5Ur%M0%pGY9|F+s#a4x5Qeo zfCfX$-n^r7PlZz$V2}8roSn`sFwf;KBZ4Vu{{6!H_rQ5?+7MGv5}5i$#ql|)&(!?= z^6$Kt5p6LgzR{%dwAp%~93#wRcaea~!s{C@w^Z6l^<{p`&1yrQC`KHn?u*j~D@oci zotzG8F4x`1+*J;9?qIeFaU9pUxS+t!26OEtc*)h}h3>J=e)*8zB0*gDm~Yg39q@)} zAw1!xmO;LX^|s#tR&gppq9ef7vOZ{UcfR>OsO_A2MM^7}(pzLj%Y0WWq|gYos7S~x zIeNRzXYXr4?0Eb5=HkxU$B%JuLPZzua{eEGT$)wAmPuLw0P!HUQSLGxZ{FfrLa?_smZ0|-LyOV z<0rc*$Can%gPgH9-R8o+E}VYt)Lh!67s80G5)}(?bdlKAva)A?{}0W3nL+KDs)!I& zSjM8Cg0M_2x%-YloSrAWpPMtAo+dm#STY!u;F0zD9{sFx=;gb2?`DQxy{Jh}=u%*T zIIT>NUAl`81Oe)e;sFW;KS9SX)lU?lR~=yX4i&4A8b_1>dvxTdH|1-C_?{itJ-yv| zN9QV+YWvJd=E{Cu1NWn!M=C4UX1?FbLud!0qG05y#t9T$!-77EjUuVx!7JZC&sNwM z>&o9d{G~8$W3=pI8IZJ2S22wc{~*%0PJkBwN|MYCr)QQUwD9qTa-N0FO^wIaIDjM> zyu=HYkrDCnd0zHLn+1dsxTw|ehjj=VX1vugpX@l^ZZjp-T5zU)H`=MrR|*})2LCo9 zb6lbp%|De#xZmICzV?08z)He8vr&)Gaia45HST+}*|*M<`fVNitJ=P-vKK@)kmG(W z`OMR`HrzKnNEVWq9I|cUcKFvZXMg?*e&1N32YpOJb~=icoD0)(r=cFa39VhtO=>2RbP^%(2a|tXOoJeCk*pHYW&FXlP4vKiT)v9q$*lIo?A`NJ>f5`M zfqsmvV1P0i<(!-=XPTg0Y^Uk7)Lb)3Bb_Y>T=_;etK3`cQh!|(OcvsMn1lBDoL=+n z?{|Oy{l8~`?+)8cc=c1Vy7>vA)_HabWO)f(ezzQ@&8P+`!jGn{-z^8_KR%96FB4Cd zv3pzMrcKtPJ6QMAaQ39i78du;?ux4mis#+7yRG#Nor~*4rvTyv;V?#OUu2pc0=85o zB7nvmLko5B*gLjDYp6*$?{Js>hR64kO?cpYZT&dH(uJy+U;FkLk+wdf#ldBjlYU zDYrX_6_)Wlc>@^UC~TBed2Hd?ndRIKtMsl%+5=rr=T`nR-LPj&>FU_01N8z+!caW3 zkb-@S9JI6J==57oU3q)oU}sNHQ#m(xcR`RKELuq4U^32%*;dX)~UV@N=RYy z^_A>OIHKC#807Tt@}+96W#ASHS#B`R{J3mL|0Ai}#k*&ER-EBdF_ib$)g+Y0d> z0P0$_Ighz#{W^Ujqd_u_OojC7h+Yz+9IOk#fGxmdB z&UX8O5Rfv0fYl&V^d&(IHfmTtno{2v_TT(h03W-*Hx_<=Z^bXgSo?Ez_UY`?na$2O z)`T$xG_V@S3Ab+Xzo0LG@{Ax{R;(LpOyTgDq=DRV@~f==qK4uHrS|i(!Iw>R*dXKR zUo}OqUcTyo)nD`cS<2yHQZ`JhIoLKJOFr;uE~uT> zmJ9sw7OeuYHERng2cw+-CEnFu2%M_a%}1FoaC`RTUl5Wx z&}PO=C5lb>2^C8!t#sU?uD|LiY4NyAAy0k(SK11fK-a#`S`J+oNcEG=p5|j*{OFwm z&!ChfR(0Mn2uqe|0;jzcWuM;O>v1t`th{KuYW^u=)vv;JY(g=Q03UNw^gtgBApeIm zug`9#N3tW0S|XyNH2T!kB6=etM6dU=VdiMKmHMh}IdNNVxqxkBv(fCwWaLrk-1q*M zlp-}iTE#0?jLP(l$X7ofB)4V_mc?uP{rC0Nk`{5rv~ztZ({toTPZzS{-#NiGy&H5c zlxuIsp9~lA*U$lFa*c|e2@YG(&x_61{lFH+l4Pk(I$NSjo~;NtR-|#snClNckAL>^ zuU?!mmjYM-SregrEBo6sEzV;=VZ6|2@-ytP={9QmA+LyIe&1#a4`6Qa6}gE)=2MYj zggAV$fC>tM@YK)GUBFw;P2nyS01{>7+hlfH7{SjtjRyDT82$L<@bo6iWUBCO#d6)oyuS_@iOyL zt{cLg3%3ljrD6IfCQ^l3GpB{amTS7`Ph-j!kp_xu{rvn6khy_7MW0fQwr=-LcZbsu zSUmmVqq>b7moElWsIm}FneuqWHBBLlOMO;_|XK-clMw33BQ*O=0ATd|T zGha@!smVJ`i*YN@JhDFlf;@=S6qb1t&2M+{1({dZbmO2PsKZdAe6gVuKUFybFuwHG z4gF61k0-CDg<6S0A4T+Q%lj@�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 KmWM~}Df$oW6`<(= delta 1361 zcmXApdr(w$6vw~2_qF#f>H4zvFD4u2gX_tJLmfK79PJe5)TMhfqfqA3k%Zr=m2FBUec~YTKzIgkcITx z4S@F`vKMNAY15HgcY*}ol=Bq&_#_S9xVwT1Z^7o14@s8|Tegv&&|qwzw;RYE#gl)| z084*Uh>y%bLWg2eWj-*YM^Vt;39PisPK92ID^ZNs#R9L@$d42jUFK0`M}$b~cPJhA z?*kr1%Bv1NMcXImDE;k>+rdog?I@Gua|>AWGt*!%r&f70%`%n3Tz8QlW1Jh40aF+= z+DO4-##!~w4ragw-|_=4ER zo<777uM>G1F-uetO>CEOfF@MPc7;%5md}<=OjvZ*Fnjs(RKq`D2a6U0UjFRRPRbU1 zjQwL>G`S>+W2gQ@-s1eu`H;#VIKOdHpD;^4!5Eabd$N-W^US`&tM1UQ)Lee?vSuKy zU!KWE>0;X+IsIosAO#N4K^`Jzy`W)rY>LMXj4NZCh)ogAgfPZcV5 zuLQ!{h1wOXXn5X2?|*tAT@;*8o>D%u@L+f!d@-v{c>F~G5Xh^%y#EDOPgBJ zawtcp$yAk=H33?a>d>A)DPFPaNQRwElA>~W-2?37qR>EQiMk+$Kciy-$Hap9Ujgc0 z#q!VZP!}TP!<@m>Vi5QD^Z|2DxR_ZiJGptelLr7XQT#jm8K4_+(YIfmsLBU&%ET7} zWe6W|F@BR2eT*_E?v>0MnwF>4#mF(q>Prgqj!Vf8PSG_{$vJ$Oa%G=f%^RxnmP_4% z6m8XXsi%GiS>UeZn2aMw)=1y=`q8WKvGlN$`k`%9a}A3~MXg$RTt-g&UTumci!Av` zZP5jh7cA-$ zAVZQ(fb8?@s z%w|t^|I54`2=dVQh9uDSj?q+S(6*(gG#9S~lP!ZZj;v~uct<;JPBGvwYjp*q^gg9) hqhInvZE}EvF4ZnMM`h$lT>g>nI@L8(_sH5I FilterTable - + Add filter Filter hinzufügen @@ -897,6 +897,130 @@ Change the file permissions or copy the file to the config directory. Include (Konfigurationsdatei einbinden) + + LoudnessCorrectionFilterGUI + + + Attenuation: + Abschwächung: + + + + Reference offset: + Referenzversatz: + + + + Reference level: + Referenzpegel: + + + dB + dB + + + + Master volume: + Gesamtlautstärke: + + + + contributed by +Alexander Walch + beigetragen durch +Alexander Walch + + + + Loudness correction: + Lautheitskorrektur: + + + + Calibrate + Kalibrieren + + + + LoudnessCorrectionFilterGUIDialog + + + Calibration + Kalibrierung + + + <html><head/><body><p>For a correct calibration, a sound level meter is required.<br/>These are the steps for calibration of a stereo system:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Disable loudness equalization during measurement<br/>(done automatically while this dialog is open)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enable only one speaker (e.g. select left or right channel below)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Play pink noise at full application volume:</li></ul></body></html> + <html><head/><body><p>Für eine korrekte Kalibrierung wird ein Schallpegelmesser benötigt.<br/>Dies sind die Schritte für die Kalibrierung eines Stereosystems:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Lautheitskorrektur während der Messung deaktivieren<br/>(geschieht automatisch, während dieser Dialog geöffnet ist)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nur einen Lautsprecher aktivieren (z.B. linken oder rechten Kanal unten wählen)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Rosa Rauschen bei voller Anwendungslautstärke abspielen:</li></ul></body></html> + + + + <html><head/><body><p>For a correct calibration, a sound level meter is required.<br/>These are the steps for calibration of a stereo system:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Disable loudness correction during measurement<br/>(done automatically while this dialog is open)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enable only one speaker (e.g. select left or right channel below)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Play pink noise at full application volume:</li></ul></body></html> + <html><head/><body><p>Für eine korrekte Kalibrierung wird ein Schallpegelmesser benötigt.<br/>Dies sind die Schritte für die Kalibrierung eines Stereosystems:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Lautheitskorrektur während der Messung deaktivieren<br/>(geschieht automatisch, während dieser Dialog geöffnet ist)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nur einen Lautsprecher aktivieren (z.B. linken oder rechten Kanal unten wählen)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Rosa Rauschen bei voller Anwendungslautstärke abspielen:</li></ul></body></html> + + + + Channel + Kanal + + + + Left + Links + + + + Right + Rechts + + + + Both + Beide + + + + Stop + Stopp + + + + Play + Abspielen + + + + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Adjust volume via Windows volume control or external volume control (amplifier, etc.) so that the sound level meter shows 75 dB at listening position. If that volume can not be reached, please adjust the following setting:</li></ul></body></html> + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Lautstärke über die Windows-Lautstärkeregelung oder externe Lautstärkeregelung (Verstärker, usw.) anpassen, sodass der Schallpegelmesser am Hörplatz 75 dB anzeigt. Wenn diese Lautstärke nicht erreicht werden kann, bitte die folgende Einstellung anpassen:</li></ul></body></html> + + + + Level at listening position: + Pegel am Hörplatz: + + + + dB + dB + + + + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The reference level will be set to the Windows volume for which the level at the listening position is 75 dB.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">After calibration, volume should only be adjusted via Windows volume control</li></ul></body></html> + <html><head/><body><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Der Referenzpegel wird auf die Windows-Lautstärke gesetzt, bei der der Pegel am Hörplatz 75 dB beträgt.</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Nach der Kalibrierung sollte die Lautstärke nur noch über die Windows-Lautstärkeregelung verändert werden</li></ul></body></html> + + + + LoudnessCorrectionFilterGUIFactory + + + Loudness correction + Lautheitskorrektur + + + + Advanced filters + Fortgeschrittene Filter + + MainWindow diff --git a/EqualizerAPO/EqualizerAPO.vcxproj b/EqualizerAPO/EqualizerAPO.vcxproj index c825c76..a8e5532 100644 --- a/EqualizerAPO/EqualizerAPO.vcxproj +++ b/EqualizerAPO/EqualizerAPO.vcxproj @@ -182,8 +182,8 @@ + - diff --git a/EqualizerAPO/EqualizerAPO.vcxproj.filters b/EqualizerAPO/EqualizerAPO.vcxproj.filters index a53bfdd..d5eeee4 100644 --- a/EqualizerAPO/EqualizerAPO.vcxproj.filters +++ b/EqualizerAPO/EqualizerAPO.vcxproj.filters @@ -25,7 +25,7 @@ - + diff --git a/FilterEngine.cpp b/FilterEngine.cpp index 7bb7c58..0d32eb0 100644 --- a/FilterEngine.cpp +++ b/FilterEngine.cpp @@ -55,6 +55,7 @@ #include "filters/ConvolutionFilterFactory.h" #include "filters/GraphicEQFilterFactory.h" #include "filters/VSTPluginFilterFactory.h" +#include "filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h" using namespace std; using namespace mup; @@ -91,6 +92,7 @@ FilterEngine::FilterEngine() factories.push_back(new ConvolutionFilterFactory()); factories.push_back(new GraphicEQFilterFactory()); factories.push_back(new VSTPluginFilterFactory()); + factories.push_back(new LoudnessCorrectionFilterFactory()); } FilterEngine::~FilterEngine() diff --git a/VoicemeeterClient/VoicemeeterClient.vcxproj.filters b/VoicemeeterClient/VoicemeeterClient.vcxproj.filters index 30f52c3..3f7d590 100644 --- a/VoicemeeterClient/VoicemeeterClient.vcxproj.filters +++ b/VoicemeeterClient/VoicemeeterClient.vcxproj.filters @@ -19,12 +19,12 @@ - - - {eb32c27d-e4c2-4cad-a72d-b798d4514714} + + + \ No newline at end of file diff --git a/filters/BiQuad.h b/filters/BiQuad.h index a3a960e..e6a208a 100644 --- a/filters/BiQuad.h +++ b/filters/BiQuad.h @@ -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: diff --git a/filters/loudnessCorrection/LoudnessCorrectionFilter.cpp b/filters/loudnessCorrection/LoudnessCorrectionFilter.cpp new file mode 100644 index 0000000..22661fe --- /dev/null +++ b/filters/loudnessCorrection/LoudnessCorrectionFilter.cpp @@ -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 + +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 LoudnessCorrectionFilter::initialize(float sampleRate, unsigned maxFrameCount, std::vector 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, ¶meterUpdateThread, 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(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 diff --git a/filters/loudnessCorrection/LoudnessCorrectionFilter.h b/filters/loudnessCorrection/LoudnessCorrectionFilter.h new file mode 100644 index 0000000..6429491 --- /dev/null +++ b/filters/loudnessCorrection/LoudnessCorrectionFilter.h @@ -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 +#include + +#include +#include + +#pragma AVRT_VTABLES_BEGIN +class LoudnessCorrectionFilter : public IFilter +{ +public: + struct FilterParameters + { + bool state; + float referenceLevel; + float referenceOffset; + float attenuation; + std::vector 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 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 FilterParameters(T input) + { + _isInitialized = !deSerialize(input); + } + bool isInitialized() + { + return _isInitialized; + } +private: + bool _isInitialized; + }; + + LoudnessCorrectionFilter(const FilterParameters& fParameters); + virtual ~LoudnessCorrectionFilter(); + 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: + 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 _lowShelfBiquads; + std::vector _highShelfBiquads; + + bool _neutral; + bool _neutralUpDate; + double _tempResult; + double _aLS[4]; + double _a0LS; + double _aHS[4]; + double _a0HS; +}; +#pragma AVRT_VTABLES_END diff --git a/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.cpp b/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.cpp new file mode 100644 index 0000000..1e2cea6 --- /dev/null +++ b/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.cpp @@ -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 +#include +#include + +#include "helpers/MemoryHelper.h" +#include "helpers/StringHelper.h" +#include "helpers/LogHelper.h" +#include "LoudnessCorrectionFilter.h" +#include "LoudnessCorrectionFilterFactory.h" + +using namespace std; + +LoudnessCorrectionFilterFactory::LoudnessCorrectionFilterFactory() +{ +} + +vector LoudnessCorrectionFilterFactory::createFilter(const wstring& configPath, wstring& command, wstring& parameters) +{ + vector 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; +} diff --git a/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h b/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h new file mode 100644 index 0000000..ca68531 --- /dev/null +++ b/filters/loudnessCorrection/LoudnessCorrectionFilterFactory.h @@ -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 +#include + +#include "IFilterFactory.h" +#include "IFilter.h" + +class LoudnessCorrectionFilterFactory : public IFilterFactory +{ +public: + LoudnessCorrectionFilterFactory(); + virtual std::vector createFilter(const std::wstring& configPath, std::wstring& command, std::wstring& parameters); +}; diff --git a/filters/loudnessCorrection/ParameterArchive.h b/filters/loudnessCorrection/ParameterArchive.h new file mode 100644 index 0000000..a65211d --- /dev/null +++ b/filters/loudnessCorrection/ParameterArchive.h @@ -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 +#include +#include +#include + +template struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(type& input) {return std::wstring();} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const float& input) {return std::to_wstring((long double)input);} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const double& input) {return std::to_wstring((long double)input);} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const int& input) {return std::to_wstring((_Longlong)input);} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const bool& input) {return std::to_wstring((_Longlong)input);} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const std::string& input) {return StringHelper::toWString(input, CP_ACP);} +}; + +template<> struct to_WString_type_traits +{ + static inline std::wstring cast_ToWString(const std::wstring& input) {return input;} +}; + +template 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 +{ + static inline float cast_fromWString(const std::wstring& input) {return (float)wcstod(input.c_str(), NULL);} +}; + +template<> struct from_WString_type_traits +{ + static inline double cast_fromWString(const std::wstring& input) {return wcstod(input.c_str(), NULL);} +}; + +template<> struct from_WString_type_traits +{ + static inline int cast_fromWString(const std::wstring& input) {return (int)wcstol(input.c_str(), NULL, 10);} +}; + +template<> struct from_WString_type_traits +{ + 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 +{ + static inline std::wstring cast_fromWString(const std::wstring& input) {return input;} +}; + +template struct constructor_traits +{ + static inline std::wstring initFrom(type input) {return std::wstring();} +}; + +template<> struct constructor_traits +{ + static inline std::wstring initFrom(std::wstring input) {return input;} +}; + +template<> struct constructor_traits > +{ + static inline std::wstring initFrom(std::vector input) + { + input.push_back(0); + std::string tempParams(&input[0]); + return to_WString_type_traits::cast_ToWString(tempParams); + } +}; + +class ParameterArchive +{ +public: + ParameterArchive() + : _serializedParamters(std::wstring()) + { + } + template ParameterArchive(T input) + { + _serializedParamters = constructor_traits::initFrom(input); + } + + std::vector getSerializedParameters() + { + size_t noBytes = _serializedParamters.length() * sizeof(std::wstring); + std::vector outputParameter(noBytes); + CopyMemory(&outputParameter[0], reinterpret_cast(&_serializedParamters[0]), noBytes); + return outputParameter; + } + + template void add(const T& parameter, const U& name) + { + std::wstring nameStr = to_WString_type_traits::cast_ToWString(name); + if (!nameStr.empty()) + { + _serializedParamters += nameStr; + _serializedParamters += L" "; + } + _serializedParamters += to_WString_type_traits::cast_ToWString(parameter); + _serializedParamters += L" "; + }; + + template int get(T& parameter, const std::wregex& searchArgument) + { + std::wsmatch match; + bool found = regex_search(_serializedParamters, match, searchArgument); + if (!found) + { + return 1; + } + std::vector splittedStrings = StringHelper::split(match.str(1), L' '); + if (splittedStrings.empty()) + { + return 1; + } + parameter = from_WString_type_traits::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; +}; diff --git a/filters/loudnessCorrection/VolumeController.cpp b/filters/loudnessCorrection/VolumeController.cpp new file mode 100644 index 0000000..78c1db0 --- /dev/null +++ b/filters/loudnessCorrection/VolumeController.cpp @@ -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 + +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(¤tVolume); + return res; +} + +HRESULT VolumeController::setVolume(float volume) +{ + volume = min(volume, _maxVol); + volume = max(volume, _minVol); + return _endpointVolume->SetMasterVolumeLevel(volume, NULL); +} \ No newline at end of file diff --git a/filters/loudnessCorrection/VolumeController.h b/filters/loudnessCorrection/VolumeController.h new file mode 100644 index 0000000..6205984 --- /dev/null +++ b/filters/loudnessCorrection/VolumeController.h @@ -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 +#include + +class VolumeController +{ +public: + VolumeController(); + HRESULT getVolume(float& currentVolume); + HRESULT setVolume(float volume); +private: + IAudioEndpointVolume* _endpointVolume; + float _minVol; + float _maxVol; +};