diff --git a/Editor/AnalysisPlotScene.cpp b/Editor/AnalysisPlotScene.cpp new file mode 100644 index 0000000..ce2c80a --- /dev/null +++ b/Editor/AnalysisPlotScene.cpp @@ -0,0 +1,51 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include "AnalysisPlotScene.h" + +using namespace std; + +AnalysisPlotScene::AnalysisPlotScene(QObject* parent) + : FrequencyPlotScene(parent) +{ + +} + +void AnalysisPlotScene::setFreqData(fftwf_complex* freqData, int frameCount, unsigned sampleRate) +{ + nodes.clear(); + for(int i = 0; i < frameCount / 2; i++) + { + float freq = (i * 1.0f / frameCount) * sampleRate; + // GainIterator can't handle 0 Hz node + if(freq == 0.0f) + freq = 0.001f; + float gain = sqrt(freqData[i][0] * freqData[i][0] + freqData[i][1] * freqData[i][1]); + float dbGain = log10(gain) * 20; + nodes.push_back(FilterNode(freq, dbGain)); + } + + update(); +} + +vector AnalysisPlotScene::getNodes() const +{ + return nodes; +} diff --git a/Editor/AnalysisPlotScene.h b/Editor/AnalysisPlotScene.h new file mode 100644 index 0000000..93f43ef --- /dev/null +++ b/Editor/AnalysisPlotScene.h @@ -0,0 +1,41 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "Editor/widgets/FrequencyPlotScene.h" +#include "helpers/GainIterator.h" + +class AnalysisPlotScene : public FrequencyPlotScene +{ + Q_OBJECT + +public: + AnalysisPlotScene(QObject* parent = 0); + + void setFreqData(fftwf_complex* freqData, int frameCount, unsigned sampleRate); + + std::vector getNodes() const; + +private: + std::vector nodes; +}; diff --git a/Editor/AnalysisPlotView.cpp b/Editor/AnalysisPlotView.cpp new file mode 100644 index 0000000..8afaa47 --- /dev/null +++ b/Editor/AnalysisPlotView.cpp @@ -0,0 +1,80 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "helpers/GainIterator.h" +#include "AnalysisPlotScene.h" +#include "AnalysisPlotView.h" + +using namespace std; + +AnalysisPlotView::AnalysisPlotView(QWidget* parent) + : FrequencyPlotView(parent) +{ +} + +void AnalysisPlotView::drawBackground(QPainter* painter, const QRectF& rect) +{ + FrequencyPlotView::drawBackground(painter, rect); + + painter->setRenderHint(QPainter::Antialiasing, true); + AnalysisPlotScene* s = qobject_cast(scene()); + std::vector& nodes = s->getNodes(); + GainIterator gainIterator(nodes); + QPainterPath path; + bool first = true; + double lastDb = -1000; + for(int x = rect.left() - 1; x <= rect.right() + 1; x++) + { + double hz = s->xToHz(x); + double db = gainIterator.gainAt(hz); + double y = s->dbToY(db); + if(db == lastDb) + y = floor(y) + 0.5; + lastDb = db; + if(first) + { + path.moveTo(x, y); + first = false; + } + else + { + path.lineTo(x, y); + } + } + + path.lineTo(rect.right() + 1, rect.bottom() + 1); + path.lineTo(rect.left() - 1, rect.bottom() + 1); + + double thresholdY = s->dbToY(0); + if(rect.top() < thresholdY) + { + QPainterPath rectPath; + rectPath.addRect(rect.left(), rect.top(), rect.width(), min(thresholdY - rect.top(), rect.height())); + QPainterPath clippingPath = path.intersected(rectPath); + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::red); + painter->drawPath(clippingPath); + painter->setBrush(Qt::NoBrush); + } + + painter->setPen(Qt::black); + painter->drawPath(path); +} diff --git a/Editor/AnalysisPlotView.h b/Editor/AnalysisPlotView.h new file mode 100644 index 0000000..d838beb --- /dev/null +++ b/Editor/AnalysisPlotView.h @@ -0,0 +1,33 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 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 "Editor/widgets/FrequencyPlotView.h" + +class AnalysisPlotView : public FrequencyPlotView +{ + Q_OBJECT + +public: + AnalysisPlotView(QWidget* parent = NULL); + +protected: + void drawBackground(QPainter* painter, const QRectF& rect) override; +}; diff --git a/Editor/AnalysisThread.cpp b/Editor/AnalysisThread.cpp new file mode 100644 index 0000000..b44c895 --- /dev/null +++ b/Editor/AnalysisThread.cpp @@ -0,0 +1,278 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "FilterEngine.h" +#include "AnalysisThread.h" + +AnalysisThread::AnalysisThread() +{ + +} + +AnalysisThread::~AnalysisThread() +{ + mutex.lock(); + quit = true; + condition.wakeAll(); + mutex.unlock(); + + wait(); + + if(resultFreqData != NULL) + fftwf_free(resultFreqData); + + if(buf != NULL) + delete buf; + if(buf2 != NULL) + delete buf2; + if(timeData != NULL) + fftwf_free(timeData); + if(freqData != NULL) + fftwf_free(freqData); + if(planForward != NULL) + fftwf_destroy_plan(planForward); +} + +void AnalysisThread::setParameters(DeviceAPOInfo* device, int channelMask, int channelIndex, QString configPath, int frameCount) +{ + QMutexLocker mutexLocker(&mutex); + this->device = *device; + this->channelMask = channelMask; + this->channelIndex = channelIndex; + this->configPath = configPath; + this->frameCount = frameCount; + + condition.wakeAll(); +} + +void AnalysisThread::beginGetResult() +{ + mutex.lock(); +} + +void AnalysisThread::endGetResult() +{ + mutex.unlock(); +} + +fftwf_complex* AnalysisThread::getFreqData() const +{ + return resultFreqData; +} + +int AnalysisThread::getFreqDataLength() const +{ + return freqDataLength; +} + +int AnalysisThread::getFreqDataSampleRate() const +{ + return freqDataSampleRate; +} + +double AnalysisThread::getPeakGain() const +{ + return peakGain; +} + +int AnalysisThread::getLatency() const +{ + return latency; +} + +double AnalysisThread::getInitializationTime() const +{ + return initializationTime; +} + +double AnalysisThread::getProcessingTime() const +{ + return processingTime; +} + +int AnalysisThread::getProcessedFrames() const +{ + return processedFrames; +} + +void AnalysisThread::run() +{ + while(true) + { + mutex.lock(); + if(!quit && this->frameCount == 0) + condition.wait(&mutex); + if(quit) + { + mutex.unlock(); + break; + } + + DeviceAPOInfo device = this->device; + int channelMask = this->channelMask; + int channelIndex = this->channelIndex; + QString configPath = this->configPath; + int frameCount = this->frameCount; + this->frameCount = 0; + mutex.unlock(); + + QElapsedTimer timer; + timer.start(); + + unsigned channelCount = device.channelCount; + if(channelMask != 0 && channelMask != device.channelMask) + { + channelCount = 0; + for(int i = 0; i < 31; i++) + { + int channelPos = 1 << i; + if(channelMask & channelPos) + channelCount++; + } + } + + unsigned sampleRate = device.sampleRate; + + qint64 startTime = timer.nsecsElapsed(); + FilterEngine engine; + engine.setDeviceInfo(device.isInput, true, device.deviceName, device.connectionName, device.deviceGuid); + engine.initialize(sampleRate, channelCount, channelCount, channelCount, channelMask, frameCount, configPath.toStdWString()); + double initializationTime = (timer.nsecsElapsed() - startTime) / 1e6; + + if(frameCount != lastFrameCount || channelCount != lastChannelCount) + { + if(buf != NULL) + delete buf; + buf = new float[frameCount * channelCount]; + memset(buf, 0, frameCount * channelCount * sizeof(float)); + + if(buf2 != NULL) + delete buf2; + buf2 = new float[frameCount * channelCount]; + } + for(unsigned i = 0; i < channelCount; i++) + buf[i] = 1.0f; + + if(frameCount != lastFrameCount) + { + if(timeData != NULL) + fftwf_free(timeData); + timeData = fftwf_alloc_real(frameCount); + + if(freqData != NULL) + fftwf_free(freqData); + freqData = fftwf_alloc_complex(frameCount); + + if(planForward != NULL) + fftwf_destroy_plan(planForward); + planForward = fftwf_plan_dft_r2c_1d(frameCount, timeData, freqData, FFTW_ESTIMATE); + } + + lastFrameCount = frameCount; + lastChannelCount = channelCount; + + int latency = 0; + int startFrame = -1; + double processingTime = 0.0; + int processedFrames = 0; + while(true) + { + qint64 startTime = timer.nsecsElapsed(); + engine.process(buf2, buf, frameCount); + processingTime += (timer.nsecsElapsed() - startTime) / 1e6; + processedFrames += frameCount; + + if(startFrame != -1) + { + for(int i = 0; i < startFrame; i++) + { + timeData[frameCount - startFrame + i] = buf2[i * channelCount + channelIndex]; + } + break; + } + + for(int i = 0; i < frameCount; i++) + { + float s = buf2[i * channelCount + channelIndex]; + if(abs(s) > 1e-5f) + { + startFrame = i; + break; + } + } + + if(startFrame != -1) + { + for(int i = 0; i < frameCount - startFrame; i++) + { + timeData[i] = buf2[(startFrame + i) * channelCount + channelIndex]; + } + + if(startFrame == 0) + break; + } + + if(latency == 0) + { + for(unsigned i = 0; i < channelCount; i++) + buf[i] = 0.0f; + } + + if(startFrame == -1) + latency += frameCount; + } + latency += startFrame; + + fftwf_execute(planForward); + + double peakGain = -DBL_MAX; + + for(int i = 0; i < frameCount; i++) + { + float sqrGain = freqData[i][0] * freqData[i][0] + freqData[i][1] * freqData[i][1]; + if(sqrGain > peakGain) + peakGain = sqrGain; + } + peakGain = sqrt(peakGain); + peakGain = log10(peakGain) * 20.0; + + mutex.lock(); + if(this->freqDataLength != frameCount) + { + if(resultFreqData != NULL) + fftwf_free(resultFreqData); + resultFreqData = fftwf_alloc_complex(frameCount); + } + memcpy(resultFreqData, freqData, frameCount * sizeof(fftwf_complex)); + this->freqDataLength = frameCount; + this->freqDataSampleRate = sampleRate; + this->latency = latency; + this->peakGain = peakGain; + this->initializationTime = initializationTime; + this->processingTime = processingTime; + this->processedFrames = processedFrames; + mutex.unlock(); + + qDebug("Analysis took %.1f ms", timer.nsecsElapsed() / 1e6); + + emit analysisFinished(); + } +} diff --git a/Editor/AnalysisThread.h b/Editor/AnalysisThread.h new file mode 100644 index 0000000..ecfa017 --- /dev/null +++ b/Editor/AnalysisThread.h @@ -0,0 +1,84 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include + +#include "DeviceAPOInfo.h" + +class AnalysisThread : public QThread +{ + Q_OBJECT + +public: + AnalysisThread(); + ~AnalysisThread(); + void setParameters(DeviceAPOInfo* device, int channelMask, int channelIndex, QString configPath, int frameCount); + void beginGetResult(); + void endGetResult(); + + fftwf_complex* getFreqData() const; + int getFreqDataLength() const; + int getFreqDataSampleRate() const; + double getPeakGain() const; + int getLatency() const; + double getInitializationTime() const; + double getProcessingTime() const; + int getProcessedFrames() const; + +signals: + void analysisFinished(); + +protected: + void run() override; + +private: + QMutex mutex; + QWaitCondition condition; + bool quit = false; + + // input + DeviceAPOInfo device; + int channelMask; + int channelIndex; + QString configPath; + int frameCount = 0; + + // output + fftwf_complex* resultFreqData = NULL; + int freqDataLength = 0; + int freqDataSampleRate; + double peakGain; + int latency; + double initializationTime; + double processingTime; + int processedFrames; + + // internal (not protected by mutex) + int lastFrameCount = -1; + int lastChannelCount = -1; + float* buf = NULL; + float* buf2 = NULL; + float* timeData = NULL; + fftwf_complex* freqData = NULL; + fftwf_plan planForward = NULL; +}; diff --git a/Editor/Editor.pro b/Editor/Editor.pro index d1690a2..4639f62 100644 --- a/Editor/Editor.pro +++ b/Editor/Editor.pro @@ -13,8 +13,9 @@ TEMPLATE = app PRECOMPILED_HEADER = stable.h QMAKE_CXXFLAGS_WARN_ON -= -w34100 +QMAKE_LFLAGS += /STACK:32000000 -DEFINES += NO_FILTERENGINE +DEFINES += _UNICODE SOURCES += main.cpp\ ../helpers/LogHelper.cpp \ @@ -88,9 +89,29 @@ SOURCES += main.cpp\ helpers/DisableWheelFilter.cpp \ widgets/EscapableLineEdit.cpp \ MainWindow.cpp \ - guis/StageFilterGUI.cpp \ - guis/StageFilterGUIFactory.cpp \ - guis/ExpressionFilterGUIFactory.cpp + guis/StageFilterGUI.cpp \ + guis/StageFilterGUIFactory.cpp \ + guis/ExpressionFilterGUIFactory.cpp \ + widgets/ResizeCorner.cpp \ + AnalysisPlotView.cpp \ + AnalysisPlotScene.cpp \ + ../FilterEngine.cpp \ + ../FilterConfiguration.cpp \ + ../filters/ChannelFilterFactory.cpp \ + ../filters/ExpressionFilterFactory.cpp \ + ../filters/IfFilterFactory.cpp \ + ../filters/StageFilterFactory.cpp \ + ../filters/ConvolutionFilterFactory.cpp \ + ../filters/IIRFilter.cpp \ + ../filters/IIRFilterFactory.cpp \ + ../filters/IncludeFilterFactory.cpp \ + ../filters/ChannelFilter.cpp \ + ../filters/ConvolutionFilter.cpp \ + ../parser/RegexFunctions.cpp \ + ../parser/RegistryFunctions.cpp \ + ../parser/StringOperators.cpp \ + AnalysisThread.cpp \ + widgets/ExponentialSpinBox.cpp HEADERS += \ ../helpers/LogHelper.h \ @@ -168,9 +189,29 @@ HEADERS += \ ../version.h \ ../stdafx.h \ MainWindow.h \ - guis/StageFilterGUI.h \ - guis/StageFilterGUIFactory.h \ - guis/ExpressionFilterGUIFactory.h + guis/StageFilterGUI.h \ + guis/StageFilterGUIFactory.h \ + guis/ExpressionFilterGUIFactory.h \ + widgets/ResizeCorner.h \ + AnalysisPlotView.h \ + AnalysisPlotScene.h \ + ../FilterEngine.h \ + ../FilterConfiguration.h \ + ../filters/ChannelFilterFactory.h \ + ../filters/ExpressionFilterFactory.h \ + ../filters/IfFilterFactory.h \ + ../filters/StageFilterFactory.h \ + ../filters/ConvolutionFilterFactory.h \ + ../filters/IIRFilter.h \ + ../filters/IIRFilterFactory.h \ + ../filters/IncludeFilterFactory.h \ + ../filters/ChannelFilter.h \ + ../filters/ConvolutionFilter.h \ + ../parser/RegexFunctions.h \ + ../parser/RegistryFunctions.h \ + ../parser/StringOperators.h \ + AnalysisThread.h \ + widgets/ExponentialSpinBox.h FORMS += \ guis/PreampFilterGUI.ui \ @@ -188,14 +229,21 @@ FORMS += \ guis/GraphicEQFilterGUI.ui \ guis/ConvolutionFilterGUI.ui \ MainWindow.ui \ - guis/StageFilterGUI.ui + guis/StageFilterGUI.ui -INCLUDEPATH += $$PWD/.. "C:/Program Files/libsndfile/include" "C:/Program Files/fftw3" -LIBS += advapi32.lib version.lib ole32.lib libsndfile-1.lib libfftw3f-3.lib -contains(QT_ARCH, x86_64) { - QMAKE_LIBDIR += "C:/Program Files/libsndfile/lib" "C:/Program Files/fftw3" +INCLUDEPATH += $$PWD/.. "C:/Program Files/libsndfile/include" "C:/Program Files/fftw3" "C:/Program Files/muparserx_v3_0_1/parser" +LIBS += advapi32.lib version.lib ole32.lib Shlwapi.lib libsndfile-1.lib libfftw3f-3.lib + +build_pass:CONFIG(debug, debug|release) { + LIBS += muparserxd.lib } else { - QMAKE_LIBDIR += "C:/Program Files (x86)/libsndfile/lib" "C:/Program Files (x86)/fftw3" + LIBS += muparserx.lib +} + +contains(QT_ARCH, x86_64) { + QMAKE_LIBDIR += "C:/Program Files/libsndfile/lib" "C:/Program Files/fftw3" "C:/Program Files/muparserx_v3_0_1/lib64" +} else { + QMAKE_LIBDIR += "C:/Program Files (x86)/libsndfile/lib" "C:/Program Files (x86)/fftw3" "C:/Program Files/muparserx_v3_0_1/lib" } RESOURCES += \ diff --git a/Editor/Editor.qrc b/Editor/Editor.qrc index e64f1f9..8ef7765 100644 --- a/Editor/Editor.qrc +++ b/Editor/Editor.qrc @@ -12,7 +12,7 @@ icons/16x16/accessories-text-editor.png icons/16x16/list-add-green.png icons/16x16/list-remove-red.png - icons/mirror_response.png + icons/invert_response.png icons/normalize_response.png icons/power_off.png icons/power_on.png @@ -22,5 +22,6 @@ translations/qtbase_de.qm icons/16x16/go-up.png icons/reset_response.png + icons/resize_corner.png diff --git a/Editor/FilterTable.cpp b/Editor/FilterTable.cpp index c7371f1..1ab3622 100644 --- a/Editor/FilterTable.cpp +++ b/Editor/FilterTable.cpp @@ -436,6 +436,38 @@ void FilterTable::openConfig(QString path) mainWindow->load(path); } +int FilterTable::getPreferredWidth() +{ + if(scrollArea == NULL) + return width(); + + return scrollArea->viewport()->width(); +} + +void FilterTable::updateSizeHints() +{ + for(int i = 0; i < items.size(); i++) + { + FilterTableRow* tableRow = qobject_cast(gridLayout->itemAtPosition(i, 0)->widget()); + tableRow->updateGeometry(); + } +} + +QSize FilterTable::minimumSizeHint() const +{ + QSize size = QWidget::minimumSizeHint(); + if(size.height() < minimumHeightHint) + size.setHeight(minimumHeightHint); + + return size; +} + +void FilterTable::setMinimumHeightHint(int height) +{ + minimumHeightHint = height; + updateGeometry(); +} + void FilterTable::mousePressEvent(QMouseEvent* event) { if(event->buttons() & Qt::LeftButton ) @@ -723,9 +755,9 @@ void FilterTable::wheelEvent(QWheelEvent * event) bool FilterTable::eventFilter(QObject* obj, QEvent* event) { + QEvent::Type type = event->type(); if(scrollingNow) { - QEvent::Type type = event->type(); if(type == QEvent::Wheel) { QWidget* widget = qobject_cast(obj); @@ -747,6 +779,11 @@ bool FilterTable::eventFilter(QObject* obj, QEvent* event) } } + if(obj == scrollArea && type == QEvent::Resize) + { + updateSizeHints(); + } + return false; } diff --git a/Editor/FilterTable.h b/Editor/FilterTable.h index e4366e6..0f0950c 100644 --- a/Editor/FilterTable.h +++ b/Editor/FilterTable.h @@ -79,6 +79,12 @@ public: void openConfig(QString path); + int getPreferredWidth(); + void updateSizeHints(); + + QSize minimumSizeHint() const override; + void setMinimumHeightHint(int height); + signals: void linesChanged(); @@ -123,6 +129,7 @@ private: DeviceAPOInfo* selectedDevice; int selectedChannelMask; QString configPath; + int minimumHeightHint = 0; }; template inline uint qHash(QList list) diff --git a/Editor/FilterTableRow.cpp b/Editor/FilterTableRow.cpp index 4b38527..ca09c24 100644 --- a/Editor/FilterTableRow.cpp +++ b/Editor/FilterTableRow.cpp @@ -74,6 +74,15 @@ void FilterTableRow::editText() ui->actionEditText->trigger(); } +QSize FilterTableRow::sizeHint() const +{ + QSize size = QWidget::minimumSizeHint(); + int preferredWidth = table->getPreferredWidth(); + if(size.width() < preferredWidth) + size = QSize(preferredWidth, size.height()); + return size; +} + void FilterTableRow::mouseDoubleClickEvent(QMouseEvent*) { if(gui == NULL && ui->stackedWidget->currentIndex() == 1) diff --git a/Editor/FilterTableRow.h b/Editor/FilterTableRow.h index 93fff79..bcef0d8 100644 --- a/Editor/FilterTableRow.h +++ b/Editor/FilterTableRow.h @@ -38,6 +38,7 @@ public: QRect getHeaderRect(); void editText(); + QSize sizeHint() const override; protected: void mouseDoubleClickEvent(QMouseEvent*) override; diff --git a/Editor/FilterTableRow.ui b/Editor/FilterTableRow.ui index 1a93e19..1407704 100644 --- a/Editor/FilterTableRow.ui +++ b/Editor/FilterTableRow.ui @@ -10,6 +10,12 @@ 300 + + + 0 + 0 + + Form diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index 4dbc945..04053b9 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -29,9 +29,9 @@ #include #include #include +#include #define WIN32_LEAN_AND_MEAN -#include #include #include "helpers/StringHelper.h" @@ -63,6 +63,8 @@ MainWindow::MainWindow(QDir configDir, QWidget *parent) : ui->setupUi(this); + LogHelper::set(stderr, true, false, false); + QString version = QString("%0.%1").arg(MAJOR).arg(MINOR); if(REVISION != 0) version += QString(".%0").arg(REVISION); @@ -123,11 +125,20 @@ MainWindow::MainWindow(QDir configDir, QWidget *parent) : deviceComboBox->addItem(QString::fromStdWString(apoInfo.connectionName) + " - " + QString::fromStdWString(apoInfo.deviceName), QVariant::fromValue(&apoInfo)); connect(channelConfigurationComboBox, SIGNAL(activated(int)), this, SLOT(channelConfigurationSelected(int))); + + analysisPlotScene = new AnalysisPlotScene(ui->graphicsView); + ui->graphicsView->setScene(analysisPlotScene); + + analysisThread = new AnalysisThread; + analysisThread->start(); + connect(analysisThread, SIGNAL(analysisFinished()), this, SLOT(updateAnalysisPanel())); } MainWindow::~MainWindow() { delete ui; + + delete analysisThread; } void MainWindow::doChecks() @@ -304,6 +315,8 @@ void MainWindow::save(FilterTable* filterTable, QString path) CloseHandle(hFile); qDebug("Saving took %.1f ms", timer.nsecsElapsed() / 1e6); + + startAnalysis(); } void MainWindow::closeEvent(QCloseEvent* event) @@ -366,6 +379,29 @@ void MainWindow::channelConfigurationSelected(int index) FilterTable* filterTable = qobject_cast(scrollArea->widget()); filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask); } + + ui->analysisChannelComboBox->clear(); + + if(selectedDevice != NULL) + { + unsigned channelCount = selectedDevice->channelCount; + if(channelMask != 0 && channelMask != selectedDevice->channelMask) + { + channelCount = 0; + for(int i = 0; i < 31; i++) + { + int channelPos = 1 << i; + if(channelMask & channelPos) + channelCount++; + } + } + + vector channelNames = ChannelHelper::getChannelNames(channelCount, channelMask); + for(wstring channelName : channelNames) + { + ui->analysisChannelComboBox->addItem(QString::fromStdWString(channelName)); + } + } } void MainWindow::linesChanged() @@ -400,22 +436,6 @@ void MainWindow::linesChanged() } } -void MainWindow::getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask) -{ - *selectedDevice = deviceComboBox->currentData().value(); - if(*selectedDevice == NULL) - *selectedDevice = defaultOutputDevice; - - *channelMask = channelConfigurationComboBox->currentData().toInt(); - if(*channelMask == 0 && selectedDevice != NULL) - { - *channelMask = (*selectedDevice)->channelMask; - - if(*channelMask == 0) - *channelMask = ChannelHelper::getDefaultChannelMask((*selectedDevice)->channelCount); - } -} - bool MainWindow::on_tabWidget_tabCloseRequested(int index) { if(ui->tabWidget->tabText(index).endsWith('*')) @@ -549,27 +569,6 @@ void MainWindow::on_actionNew_triggered() ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); } -FilterTable* MainWindow::addTab(QString title, QString tooltip) -{ - QScrollArea* scrollArea = new QScrollArea(ui->tabWidget); - scrollArea->setWidgetResizable(true); - FilterTable* filterTable = new FilterTable(this); - scrollArea->setWidget(filterTable); - filterTable->setAcceptDrops(true); - filterTable->setFocusPolicy(Qt::WheelFocus); - - int tabIndex = ui->tabWidget->addTab(scrollArea, title); - ui->tabWidget->setTabToolTip(tabIndex, tooltip); - - DeviceAPOInfo* selectedDevice; - int channelMask; - getDeviceAndChannelMask(&selectedDevice, &channelMask); - filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask); - filterTable->initialize(scrollArea, outputDevices, inputDevices); - - return filterTable; -} - void MainWindow::on_actionCut_triggered() { QScrollArea* scrollArea = qobject_cast(ui->tabWidget->currentWidget()); @@ -630,3 +629,133 @@ void MainWindow::instantModeEnabled(bool enabled) } } } + +void MainWindow::on_tabWidget_currentChanged(int index) +{ + startAnalysis(); +} + +void MainWindow::on_startFromComboBox_activated(int index) +{ + startAnalysis(); +} + +void MainWindow::on_analysisChannelComboBox_activated(int index) +{ + startAnalysis(); +} + +void MainWindow::on_resolutionSpinBox_valueChanged(int value) +{ + startAnalysis(); +} + +void MainWindow::updateAnalysisPanel() +{ + analysisThread->beginGetResult(); + int sampleRate = analysisThread->getFreqDataSampleRate(); + int latency = analysisThread->getLatency(); + analysisPlotScene->setFreqData(analysisThread->getFreqData(), analysisThread->getFreqDataLength(), sampleRate); + + double peakGain = analysisThread->getPeakGain(); + ui->peakGainValueLabel->setText(tr("%0 dB").arg(peakGain, 0, 'f', 1)); + ui->peakGainValueLabel->setForegroundRole(peakGain > 0 ? QPalette::Dark : QPalette::WindowText); + + ui->latencyValueLabel->setText(tr("%0 ms (%1 samples)").arg(latency * 1000.0 / sampleRate).arg(latency)); + + ui->initTimeValueLabel->setText(tr("%0 ms").arg(analysisThread->getInitializationTime(), 0, 'f', 1)); + + double cpuUsage = analysisThread->getProcessingTime() * 100.0 / (analysisThread->getProcessedFrames() * 1000.0 / sampleRate); + ui->cpuUsageValueLabel->setText(tr("%0 % (one core)").arg(cpuUsage, 0, 'f', 1)); + ui->cpuUsageValueLabel->setForegroundRole(cpuUsage >= 20 ? (cpuUsage >= 50 ? QPalette::Dark : QPalette::Midlight) : QPalette::WindowText); + + analysisThread->endGetResult(); +} + +void MainWindow::on_mainToolBar_visibilityChanged(bool visible) +{ + ui->actionToolbar->setChecked(visible); +} + +void MainWindow::on_analysisDockWidget_visibilityChanged(bool visible) +{ + ui->actionAnalysisPanel->setChecked(visible); +} + +void MainWindow::on_actionToolbar_triggered(bool checked) +{ + ui->mainToolBar->setVisible(checked); +} + +void MainWindow::on_actionAnalysisPanel_triggered(bool checked) +{ + ui->analysisDockWidget->setVisible(checked); +} + +FilterTable* MainWindow::addTab(QString title, QString tooltip) +{ + QScrollArea* scrollArea = new QScrollArea(ui->tabWidget); + scrollArea->setWidgetResizable(true); + FilterTable* filterTable = new FilterTable(this); + scrollArea->setWidget(filterTable); + filterTable->setAcceptDrops(true); + filterTable->setFocusPolicy(Qt::WheelFocus); + + int tabIndex = ui->tabWidget->addTab(scrollArea, title); + ui->tabWidget->setTabToolTip(tabIndex, tooltip); + + DeviceAPOInfo* selectedDevice; + int channelMask; + getDeviceAndChannelMask(&selectedDevice, &channelMask); + filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask); + filterTable->initialize(scrollArea, outputDevices, inputDevices); + + return filterTable; +} + +void MainWindow::getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask) +{ + *selectedDevice = deviceComboBox->currentData().value(); + if(*selectedDevice == NULL) + *selectedDevice = defaultOutputDevice; + + *channelMask = channelConfigurationComboBox->currentData().toInt(); + if(*channelMask == 0 && selectedDevice != NULL) + { + *channelMask = (*selectedDevice)->channelMask; + + if(*channelMask == 0) + *channelMask = ChannelHelper::getDefaultChannelMask((*selectedDevice)->channelCount); + } +} + +void MainWindow::startAnalysis() +{ + DeviceAPOInfo* selectedDevice; + + int channelMask; + getDeviceAndChannelMask(&selectedDevice, &channelMask); + + if(selectedDevice != NULL) + { + QString configPath; + + if(ui->startFromComboBox->currentIndex() == 1) + { + QScrollArea* scrollArea = qobject_cast(ui->tabWidget->currentWidget()); + if(scrollArea != NULL) + { + FilterTable* filterTable = qobject_cast(scrollArea->widget()); + + if(filterTable->getConfigPath().length() > 0) + configPath = filterTable->getConfigPath(); + } + } + + if(configPath.isEmpty()) + configPath = configDir.absoluteFilePath("config.txt"); + configPath = QDir::toNativeSeparators(configPath); + + analysisThread->setParameters(selectedDevice, channelMask, ui->analysisChannelComboBox->currentIndex(), configPath, ui->resolutionSpinBox->value()); + } +} diff --git a/Editor/MainWindow.h b/Editor/MainWindow.h index 1e7eb31..cc1c663 100644 --- a/Editor/MainWindow.h +++ b/Editor/MainWindow.h @@ -28,6 +28,8 @@ #include "FilterTable.h" #include "DeviceAPOInfo.h" +#include "Editor/AnalysisPlotScene.h" +#include "Editor/AnalysisThread.h" namespace Ui { class MainWindow; @@ -64,10 +66,21 @@ private slots: void on_actionCopy_triggered(); void instantModeEnabled(bool enabled); + void on_tabWidget_currentChanged(int index); + void on_startFromComboBox_activated(int index); + void on_analysisChannelComboBox_activated(int index); + void on_resolutionSpinBox_valueChanged(int value); + void updateAnalysisPanel(); + + void on_mainToolBar_visibilityChanged(bool visible); + void on_analysisDockWidget_visibilityChanged(bool visible); + void on_actionToolbar_triggered(bool checked); + void on_actionAnalysisPanel_triggered(bool checked); private: FilterTable* addTab(QString title, QString tooltip); void getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask); + void startAnalysis(); Ui::MainWindow *ui; @@ -78,6 +91,8 @@ private: QList outputDevices; QList inputDevices; DeviceAPOInfo* defaultOutputDevice; + AnalysisPlotScene* analysisPlotScene; + AnalysisThread* analysisThread; }; Q_DECLARE_METATYPE(DeviceAPOInfo*) diff --git a/Editor/MainWindow.ui b/Editor/MainWindow.ui index 814ea5c..7caa3c1 100644 --- a/Editor/MainWindow.ui +++ b/Editor/MainWindow.ui @@ -71,10 +71,21 @@ + + + View + + + + + + + Toolbar + TopToolBarArea @@ -85,6 +96,452 @@ + + + Analysis panel + + + 8 + + + + + 1 + + + 0 + + + 1 + + + 1 + + + 1 + + + 3 + + + + + Settings + + + + 3 + + + 3 + + + 1 + + + + + FFT resolution: + + + + + + + Channel: + + + + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + false + + + 128 + + + 8388608 + + + 65536 + + + + + + + Start from: + + + + + + + + config.txt + + + + + Current file + + + + + + + + + + + Estimated properties + + + + 6 + + + + + + + + + + + + CPU usage: + + + + + + + Init. time: + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 127 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 127 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + Latency: + + + + + + + Peak gain: + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + + + + + + + + + + + + true + + + QPainter::Antialiasing|QPainter::TextAntialiasing + + + + + + @@ -192,8 +649,36 @@ Delete + + + true + + + Toolbar + + + + + true + + + Analysis panel + + + + + AnalysisPlotView + QGraphicsView +
Editor/AnalysisPlotView.h
+
+ + ExponentialSpinBox + QSpinBox +
Editor/widgets/ExponentialSpinBox.h
+
+
diff --git a/Editor/guis/CopyFilterGUI.cpp b/Editor/guis/CopyFilterGUI.cpp index c99753f..7f7b56b 100644 --- a/Editor/guis/CopyFilterGUI.cpp +++ b/Editor/guis/CopyFilterGUI.cpp @@ -17,6 +17,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "Editor/widgets/ResizeCorner.h" #include "helpers/ChannelHelper.h" #include "CopyFilterGUIForm.h" #include "CopyFilterGUI.h" @@ -24,7 +25,7 @@ using namespace std; -CopyFilterGUI::CopyFilterGUI(CopyFilter* filter) : +CopyFilterGUI::CopyFilterGUI(CopyFilter* filter, FilterTable* filterTable) : ui(new Ui::CopyFilterGUI) { ui->setupUi(this); @@ -35,6 +36,18 @@ CopyFilterGUI::CopyFilterGUI(CopyFilter* filter) : ui->form->load(filter->getAssignments()); + ResizeCorner* cornerWidget = new ResizeCorner(filterTable, + QSize(0, 50), QSize(0, INT_MAX), + [this]() { + return QSize(0, ui->scrollArea->height()); + }, + [this](QSize size) { + ui->scrollArea->setMinimumHeight(size.height()); + }, ui->scrollArea); + cornerWidget->setCursor(Qt::SizeVerCursor); + cornerWidget->setAutoFillBackground(true); + ui->scrollArea->setCornerWidget(cornerWidget); + connect(scene, SIGNAL(updateModel()), this, SIGNAL(updateModel())); connect(scene, SIGNAL(updateChannels()), this, SIGNAL(updateChannels())); diff --git a/Editor/guis/CopyFilterGUI.h b/Editor/guis/CopyFilterGUI.h index f8c2267..16d3ea3 100644 --- a/Editor/guis/CopyFilterGUI.h +++ b/Editor/guis/CopyFilterGUI.h @@ -22,6 +22,7 @@ #include "filters/CopyFilter.h" #include "Editor/IFilterGUI.h" #include "CopyFilterGUIScene.h" +#include "Editor/FilterTable.h" namespace Ui { class CopyFilterGUI; @@ -32,7 +33,7 @@ class CopyFilterGUI : public IFilterGUI Q_OBJECT public: - explicit CopyFilterGUI(CopyFilter* filter); + explicit CopyFilterGUI(CopyFilter* filter, FilterTable* filterTable); ~CopyFilterGUI(); void configureChannels(std::vector& channelNames) override; diff --git a/Editor/guis/CopyFilterGUI.ui b/Editor/guis/CopyFilterGUI.ui index f2b0f20..43afff0 100644 --- a/Editor/guis/CopyFilterGUI.ui +++ b/Editor/guis/CopyFilterGUI.ui @@ -51,7 +51,7 @@ false - true + false @@ -79,6 +79,9 @@ + + QFrame::NoFrame + QAbstractScrollArea::AdjustToContents @@ -121,6 +124,12 @@ + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOn + true @@ -129,8 +138,8 @@ 0 0 - 366 - 92 + 348 + 88 diff --git a/Editor/guis/CopyFilterGUIFactory.cpp b/Editor/guis/CopyFilterGUIFactory.cpp index 3a96bb6..a1a06a4 100644 --- a/Editor/guis/CopyFilterGUIFactory.cpp +++ b/Editor/guis/CopyFilterGUIFactory.cpp @@ -26,6 +26,11 @@ CopyFilterGUIFactory::CopyFilterGUIFactory() { } +void CopyFilterGUIFactory::initialize(FilterTable* filterTable) +{ + this->filterTable = filterTable; +} + QList CopyFilterGUIFactory::createFilterTemplates() { QList list; @@ -44,7 +49,7 @@ IFilterGUI* CopyFilterGUIFactory::createFilterGUI(QString& command, QString& par if(!filters.empty()) { CopyFilter* filter = (CopyFilter*)filters[0]; - result = new CopyFilterGUI(filter); + result = new CopyFilterGUI(filter, filterTable); } for(IFilter* f : filters) diff --git a/Editor/guis/CopyFilterGUIFactory.h b/Editor/guis/CopyFilterGUIFactory.h index 32b3bb2..efd96dc 100644 --- a/Editor/guis/CopyFilterGUIFactory.h +++ b/Editor/guis/CopyFilterGUIFactory.h @@ -28,6 +28,11 @@ class CopyFilterGUIFactory : public IFilterGUIFactory public: CopyFilterGUIFactory(); + void initialize(FilterTable* filterTable) override; + QList createFilterTemplates() override; IFilterGUI* createFilterGUI(QString& command, QString& parameters) override; + +private: + FilterTable* filterTable; }; diff --git a/Editor/guis/GraphicEQFilterGUI.cpp b/Editor/guis/GraphicEQFilterGUI.cpp index 8b02311..d2ba35c 100644 --- a/Editor/guis/GraphicEQFilterGUI.cpp +++ b/Editor/guis/GraphicEQFilterGUI.cpp @@ -23,6 +23,8 @@ #include #include "helpers/GainIterator.h" +#include "Editor/widgets/ResizeCorner.h" +#include "Editor/FilterTable.h" #include "GraphicEQFilterGUIView.h" #include "GraphicEQFilterGUI.h" #include "ui_GraphicEQFilterGUI.h" @@ -31,19 +33,29 @@ using namespace std; QRegularExpression GraphicEQFilterGUI::numberRegEx("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?"); -GraphicEQFilterGUI::GraphicEQFilterGUI(GraphicEQFilter* filter, QString configPath) : +GraphicEQFilterGUI::GraphicEQFilterGUI(GraphicEQFilter* filter, QString configPath, FilterTable* filterTable) : ui(new Ui::GraphicEQFilterGUI), configPath(configPath) { ui->setupUi(this); - setFixedHeight(150); - scene = new GraphicEQFilterGUIScene(ui->graphicsView); ui->graphicsView->setScene(scene); + ResizeCorner* cornerWidget = new ResizeCorner(filterTable, + QSize(0, ui->graphicsView->minimumHeight()), QSize(10000 - ui->tableWidget->minimumWidth(), INT_MAX), + [this]() { + return QSize(10000 - ui->tableWidget->width(), ui->graphicsView->height()); + }, + [this](QSize size) { + ui->tableWidget->setFixedWidth(10000 - size.width()); + ui->graphicsView->setFixedHeight(size.height()); + }, ui->graphicsView); + ui->graphicsView->setCornerWidget(cornerWidget); + ui->graphicsView->setFixedHeight(150); + ui->toolBar->addAction(ui->actionImport); ui->toolBar->addAction(ui->actionExport); - ui->toolBar->addAction(ui->actionMirrorResponse); + ui->toolBar->addAction(ui->actionInvertResponse); ui->toolBar->addAction(ui->actionNormalizeResponse); ui->toolBar->addAction(ui->actionResetResponse); @@ -69,7 +81,7 @@ GraphicEQFilterGUI::GraphicEQFilterGUI(GraphicEQFilter* filter, QString configPa ui->radioButtonVar->click(); } - ui->tableWidget->resizeColumnsToContents(); + ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); } GraphicEQFilterGUI::~GraphicEQFilterGUI() @@ -77,7 +89,7 @@ GraphicEQFilterGUI::~GraphicEQFilterGUI() delete ui; } -void GraphicEQFilterGUI::store(QString& command, QString& parameters) +void GraphicEQFilterGUI::store(QString & command, QString & parameters) { command = "GraphicEQ"; @@ -305,8 +317,6 @@ void GraphicEQFilterGUI::on_actionImport_triggered() { emit updateModel(); } - - ui->tableWidget->resizeColumnsToContents(); } } @@ -339,7 +349,7 @@ void GraphicEQFilterGUI::on_actionExport_triggered() } } -void GraphicEQFilterGUI::on_actionMirrorResponse_triggered() +void GraphicEQFilterGUI::on_actionInvertResponse_triggered() { vector newNodes = scene->getNodes(); for(FilterNode& node : newNodes) diff --git a/Editor/guis/GraphicEQFilterGUI.h b/Editor/guis/GraphicEQFilterGUI.h index 00f07e5..f026490 100644 --- a/Editor/guis/GraphicEQFilterGUI.h +++ b/Editor/guis/GraphicEQFilterGUI.h @@ -25,6 +25,8 @@ #include "filters/GraphicEQFilter.h" #include "GraphicEQFilterGUIScene.h" +class FilterTable; + namespace Ui { class GraphicEQFilterGUI; } @@ -34,7 +36,7 @@ class GraphicEQFilterGUI : public IFilterGUI Q_OBJECT public: - explicit GraphicEQFilterGUI(GraphicEQFilter* filter, QString configPath); + explicit GraphicEQFilterGUI(GraphicEQFilter* filter, QString configPath, FilterTable* filterTable); ~GraphicEQFilterGUI(); void store(QString& command, QString& parameters) override; @@ -54,13 +56,9 @@ private slots: void on_radioButtonVar_toggled(bool checked); void on_actionImport_triggered(); - void on_actionExport_triggered(); - - void on_actionMirrorResponse_triggered(); - + void on_actionInvertResponse_triggered(); void on_actionNormalizeResponse_triggered(); - void on_actionResetResponse_triggered(); private: diff --git a/Editor/guis/GraphicEQFilterGUI.ui b/Editor/guis/GraphicEQFilterGUI.ui index e48ab7a..d13da35 100644 --- a/Editor/guis/GraphicEQFilterGUI.ui +++ b/Editor/guis/GraphicEQFilterGUI.ui @@ -6,14 +6,17 @@ 0 0 - 442 - 170 + 623 + 124 - + + + 3 + 0 @@ -26,134 +29,24 @@ 0 - - 3 - - - 0 - - - - - - 0 - 0 - - - - Graphic EQ: - - - - - - - - 0 - 0 - - - - - - - - - 51 - 153 - 255 - - - - - - - - - 122 - 189 - 255 - - - - - - - - - 51 - 153 - 255 - - - - - - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::ScrollPerPixel - - - false - - - 2 - - - false - - - 40 - - - false - - - 40 - - - 20 - - - - Freq. - - - - - Gain - - - - - - - - - 16 - 16 - - - - - - - - QPainter::Antialiasing|QPainter::TextAntialiasing - - - QGraphicsView::RubberBandDrag - - - - + 6 + + + + + 0 + 0 + + + + Graphic EQ: + + + @@ -180,6 +73,141 @@ + + + + + 0 + 0 + + + + + 0 + 100 + + + + true + + + QPainter::Antialiasing|QPainter::TextAntialiasing + + + QGraphicsView::RubberBandDrag + + + + + + + 0 + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + + + + + 51 + 153 + 255 + + + + + + + + + 122 + 189 + 255 + + + + + + + + + 51 + 153 + 255 + + + + + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::ScrollPerPixel + + + false + + + 2 + + + false + + + 10 + + + false + + + 10 + + + 20 + + + 23 + + + + Freq. + + + + + Gain + + + + + + + + + 16 + 16 + + + + + + @@ -199,13 +227,13 @@ Export - + - :/icons/mirror_response.png:/icons/mirror_response.png + :/icons/invert_response.png:/icons/invert_response.png - Mirror response + Invert response diff --git a/Editor/guis/GraphicEQFilterGUIFactory.cpp b/Editor/guis/GraphicEQFilterGUIFactory.cpp index 988c427..e58e60e 100644 --- a/Editor/guis/GraphicEQFilterGUIFactory.cpp +++ b/Editor/guis/GraphicEQFilterGUIFactory.cpp @@ -26,6 +26,11 @@ GraphicEQFilterGUIFactory::GraphicEQFilterGUIFactory() { } +void GraphicEQFilterGUIFactory::initialize(FilterTable* filterTable) +{ + this->filterTable = filterTable; +} + QList GraphicEQFilterGUIFactory::createFilterTemplates() { QList list; @@ -51,7 +56,7 @@ IFilterGUI* GraphicEQFilterGUIFactory::createFilterGUI(QString& command, QString if(!filters.empty()) { GraphicEQFilter* filter = (GraphicEQFilter*)filters[0]; - result = new GraphicEQFilterGUI(filter, configPath); + result = new GraphicEQFilterGUI(filter, configPath, filterTable); } for(IFilter* f : filters) diff --git a/Editor/guis/GraphicEQFilterGUIFactory.h b/Editor/guis/GraphicEQFilterGUIFactory.h index b8f5435..950b36d 100644 --- a/Editor/guis/GraphicEQFilterGUIFactory.h +++ b/Editor/guis/GraphicEQFilterGUIFactory.h @@ -28,6 +28,8 @@ class GraphicEQFilterGUIFactory : public IFilterGUIFactory public: GraphicEQFilterGUIFactory(); + void initialize(FilterTable* filterTable) override; + QList createFilterTemplates() override; void startOfFile(const QString& configPath) override; @@ -35,4 +37,5 @@ public: private: QString configPath; + FilterTable* filterTable; }; diff --git a/Editor/guis/GraphicEQFilterGUIScene.cpp b/Editor/guis/GraphicEQFilterGUIScene.cpp index 7525753..117efa9 100644 --- a/Editor/guis/GraphicEQFilterGUIScene.cpp +++ b/Editor/guis/GraphicEQFilterGUIScene.cpp @@ -191,7 +191,7 @@ void GraphicEQFilterGUIScene::itemMoved(int index) hz = round(hz * 10) / 10; item->setHz(hz); } - if(getZoom() < 4) + if(getZoomY() < 4) db = round(db * 10) / 10; else db = round(db * 100) / 100; diff --git a/Editor/icons/mirror_response.png b/Editor/icons/invert_response.png similarity index 100% rename from Editor/icons/mirror_response.png rename to Editor/icons/invert_response.png diff --git a/Editor/icons/reset_response.png b/Editor/icons/reset_response.png index 7ada6eb..5e5edb1 100644 Binary files a/Editor/icons/reset_response.png and b/Editor/icons/reset_response.png differ diff --git a/Editor/icons/reset_response.svg b/Editor/icons/reset_response.svg index 29afdc3..584610f 100644 --- a/Editor/icons/reset_response.svg +++ b/Editor/icons/reset_response.svg @@ -15,8 +15,8 @@ version="1.1" inkscape:version="0.91 r13725" viewBox="0 0 16 16" - sodipodi:docname="flat_response.svg" - inkscape:export-filename="C:\Eigene Dateien\Entwickl\EqualizerAPO\trunk\Editor\guis\mirror_response.png" + sodipodi:docname="reset_response.svg" + inkscape:export-filename="C:\Eigene Dateien\Entwickl\EqualizerAPO\trunk\Editor\icons\reset_response.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"> image/svg+xml - + @@ -157,7 +157,7 @@ inkscape:groupmode="layer"> diff --git a/Editor/icons/resize_corner.png b/Editor/icons/resize_corner.png new file mode 100644 index 0000000..fc9c0c5 Binary files /dev/null and b/Editor/icons/resize_corner.png differ diff --git a/Editor/icons/resize_corner.svg b/Editor/icons/resize_corner.svg new file mode 100644 index 0000000..4f5f677 --- /dev/null +++ b/Editor/icons/resize_corner.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/Editor/widgets/ExponentialSpinBox.cpp b/Editor/widgets/ExponentialSpinBox.cpp new file mode 100644 index 0000000..e200afc --- /dev/null +++ b/Editor/widgets/ExponentialSpinBox.cpp @@ -0,0 +1,55 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include "ExponentialSpinBox.h" + +using namespace std; + +ExponentialSpinBox::ExponentialSpinBox(QWidget* parent) + : QSpinBox(parent) +{ + +} + +void ExponentialSpinBox::stepBy(int steps) +{ + qint64 v = value(); + if(steps > 0) + { + v <<= steps; + if(v > maximum()) + v = maximum(); + } + else + { + v >>= -steps; + if(v < minimum()) + v = minimum(); + } + + setValue((int)v); +} + +int ExponentialSpinBox::valueFromText(const QString& text) const +{ + int value = QSpinBox::valueFromText(text); + value = exp2(round(log2(value))); + return value; +} diff --git a/Editor/widgets/ExponentialSpinBox.h b/Editor/widgets/ExponentialSpinBox.h new file mode 100644 index 0000000..e4ebd27 --- /dev/null +++ b/Editor/widgets/ExponentialSpinBox.h @@ -0,0 +1,32 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 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 + +class ExponentialSpinBox : public QSpinBox +{ +public: + ExponentialSpinBox(QWidget* parent = 0); + +public: + void stepBy(int steps) override; + +protected: + int valueFromText(const QString& text) const override; +}; diff --git a/Editor/widgets/FrequencyPlotHRuler.cpp b/Editor/widgets/FrequencyPlotHRuler.cpp index 5ef839c..889f71a 100644 --- a/Editor/widgets/FrequencyPlotHRuler.cpp +++ b/Editor/widgets/FrequencyPlotHRuler.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "FrequencyPlotView.h" #include "FrequencyPlotHRuler.h" @@ -33,6 +34,7 @@ FrequencyPlotHRuler::FrequencyPlotHRuler(QWidget* parent) : void FrequencyPlotHRuler::paintEvent(QPaintEvent*) { QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); FrequencyPlotView* view = qobject_cast(parentWidget()); FrequencyPlotScene* s = view->scene(); QFontMetrics metrics = painter.fontMetrics(); @@ -40,6 +42,7 @@ void FrequencyPlotHRuler::paintEvent(QPaintEvent*) QPointF topLeft = view->mapToScene(0, 0); QPointF bottomRight = view->mapToScene(view->viewport()->width(), view->viewport()->height()); + int offsetLeft = view->viewportMargins().left(); double fromHz = s->xToHz(topLeft.x()); double toHz = s->xToHz(bottomRight.x()); const vector& bands = s->getBands(); @@ -61,7 +64,7 @@ void FrequencyPlotHRuler::paintEvent(QPaintEvent*) else text = QString("%0k").arg(hz / 1000); if(metrics.width(text) + 2 < s->hzToX(hz + hzBase) - x) - painter.drawText(x - topLeft.x() + height() + 1, 0, 0, height(), Qt::TextDontClip | Qt::AlignCenter, text); + painter.drawText(x - topLeft.x() + offsetLeft + 1, 0, 0, height(), Qt::TextDontClip | Qt::AlignCenter, text); } hz += hzBase; @@ -83,8 +86,50 @@ void FrequencyPlotHRuler::paintEvent(QPaintEvent*) text = QString("%0").arg(hz); else text = QString("%0k").arg(hz / 1000); - painter.drawText(x - topLeft.x() + height() + 1, 0, 0, height(), Qt::TextDontClip | Qt::AlignCenter, text); + painter.drawText(x - topLeft.x() + offsetLeft + 1, 0, 0, height(), Qt::TextDontClip | Qt::AlignCenter, text); } } } + + QPoint mousePos = view->getLastMousePos(); + if(!mousePos.isNull()) + { + QPointF mouseScenePos = view->mapToScene(mousePos); + + double hz = s->xToHz(mouseScenePos.x()); + double x = s->hzToX(hz); + if(x != -1) + { + QString text = QString("%0").arg(hz, 0, 'f', 1); + QFontMetrics metrics = painter.fontMetrics(); + QRectF rect = metrics.boundingRect(text); + float center = x - topLeft.x() + offsetLeft + 1; + rect = QRectF(center - ceil(rect.width() / 2) - 2 + 0.5, ceil(height() / 2) - ceil(rect.height() / 2) + 1.5, rect.width() + 3, rect.height()); + QPainterPath path; + path.addRect(rect); + QPainterPath topTriangle; + topTriangle.moveTo(QPoint(center - 3, rect.top() + 1)); + topTriangle.lineTo(QPoint(center + 3, rect.top() + 1)); + topTriangle.lineTo(QPoint(center, rect.top() - 3)); + path = path.united(topTriangle); + + painter.setPen(Qt::black); + painter.setBrush(Qt::white); + painter.drawPath(path); + painter.setPen(Qt::blue); + painter.drawText(center, 0, 0, height(), Qt::TextDontClip | Qt::AlignCenter, text); + } + } +} + +void FrequencyPlotHRuler::wheelEvent(QWheelEvent* event) +{ + FrequencyPlotView* view = qobject_cast(parentWidget()); + view->zoom(event->angleDelta().y(), 0, event->x() - view->viewportMargins().left(), 0); +} + +void FrequencyPlotHRuler::mouseMoveEvent(QMouseEvent* event) +{ + FrequencyPlotView* view = qobject_cast(parentWidget()); + view->setLastMousePos(QPoint(event->x() - view->viewportMargins().left(), view->viewport()->height() - 1)); } diff --git a/Editor/widgets/FrequencyPlotHRuler.h b/Editor/widgets/FrequencyPlotHRuler.h index 849681b..e4b685c 100644 --- a/Editor/widgets/FrequencyPlotHRuler.h +++ b/Editor/widgets/FrequencyPlotHRuler.h @@ -29,4 +29,6 @@ public: protected: void paintEvent(QPaintEvent*) override; + void wheelEvent(QWheelEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; }; diff --git a/Editor/widgets/FrequencyPlotScene.cpp b/Editor/widgets/FrequencyPlotScene.cpp index 75e9428..aae0cb4 100644 --- a/Editor/widgets/FrequencyPlotScene.cpp +++ b/Editor/widgets/FrequencyPlotScene.cpp @@ -48,7 +48,7 @@ void FrequencyPlotScene::addItem(FrequencyPlotItem* item) double FrequencyPlotScene::xToHz(double x) { - return pow(maxHz / minHz, x / zoom / maxX) * minHz; + return pow(maxHz / minHz, x / zoomX / maxX) * minHz; } double FrequencyPlotScene::hzToX(double hz) @@ -56,12 +56,12 @@ double FrequencyPlotScene::hzToX(double hz) if(hz < minHz || hz > maxHz) return -1; - return log(hz / minHz) / log(maxHz / minHz) * maxX * zoom; + return log(hz / minHz) / log(maxHz / minHz) * maxX * zoomX; } double FrequencyPlotScene::yToDb(double y) { - return (minDb + (1 - y / zoom / maxY) * (maxDb - minDb)); + return (minDb + (1 - y / zoomY / maxY) * (maxDb - minDb)); } double FrequencyPlotScene::dbToY(double db) @@ -69,17 +69,23 @@ double FrequencyPlotScene::dbToY(double db) if(db < minDb || db > maxDb) return -1; - return (1 - (db - minDb) / (maxDb - minDb)) * maxY * zoom; + return (1 - (db - minDb) / (maxDb - minDb)) * maxY * zoomY; } -double FrequencyPlotScene::getZoom() const +double FrequencyPlotScene::getZoomX() const { - return zoom; + return zoomX; } -void FrequencyPlotScene::setZoom(double value) +double FrequencyPlotScene::getZoomY() const { - zoom = value; + return zoomY; +} + +void FrequencyPlotScene::setZoom(double zoomX, double zoomY) +{ + this->zoomX = zoomX; + this->zoomY = zoomY; updateSceneRect(); for(QGraphicsItem* item : items()) @@ -91,7 +97,7 @@ void FrequencyPlotScene::setZoom(double value) void FrequencyPlotScene::updateSceneRect() { - setSceneRect(0, 0, maxX * zoom, maxY * zoom); + setSceneRect(0, 0, maxX * zoomX, maxY * zoomY); } int FrequencyPlotScene::getBandCount() const diff --git a/Editor/widgets/FrequencyPlotScene.h b/Editor/widgets/FrequencyPlotScene.h index 18c705a..9b0264d 100644 --- a/Editor/widgets/FrequencyPlotScene.h +++ b/Editor/widgets/FrequencyPlotScene.h @@ -37,8 +37,9 @@ public: double yToDb(double y); double dbToY(double db); - double getZoom() const; - void setZoom(double value); + double getZoomX() const; + double getZoomY() const; + void setZoom(double zoomX, double zoomY); int getBandCount() const; virtual void setBandCount(int value); @@ -54,6 +55,7 @@ protected: private: void updateSceneRect(); - double zoom = 1.0; + double zoomX = 1.0; + double zoomY = 1.0; int bandCount = -1; }; diff --git a/Editor/widgets/FrequencyPlotVRuler.cpp b/Editor/widgets/FrequencyPlotVRuler.cpp index 1510b3e..d55e1e8 100644 --- a/Editor/widgets/FrequencyPlotVRuler.cpp +++ b/Editor/widgets/FrequencyPlotVRuler.cpp @@ -17,6 +17,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include + #include "FrequencyPlotView.h" #include "FrequencyPlotScene.h" #include "FrequencyPlotVRuler.h" @@ -29,13 +31,13 @@ FrequencyPlotVRuler::FrequencyPlotVRuler(QWidget *parent) : void FrequencyPlotVRuler::paintEvent(QPaintEvent*) { QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); FrequencyPlotView* view = qobject_cast(parentWidget()); - FrequencyPlotScene* scene = view->scene(); + FrequencyPlotScene* s = view->scene(); QPointF topLeft = view->mapToScene(0, 0); QPointF bottomRight = view->mapToScene(view->viewport()->width(), view->viewport()->height()); - double dbHeight = abs(scene->yToDb(topLeft.y()) - scene->yToDb(bottomRight.y())); - double dbStep = dbHeight / 4; + double dbStep = abs(s->yToDb(0) - s->yToDb(30)); double dbBase = pow(10, floor(log10(dbStep))); if(dbStep >= 5 * dbBase) @@ -45,16 +47,58 @@ void FrequencyPlotVRuler::paintEvent(QPaintEvent*) else dbStep = dbBase; - double fromDb = floor(scene->yToDb(topLeft.y()) / dbStep) * dbStep; - double toDb = ceil(scene->yToDb(bottomRight.y()) / dbStep) * dbStep; + double fromDb = floor(s->yToDb(topLeft.y()) / dbStep) * dbStep; + double toDb = ceil(s->yToDb(bottomRight.y()) / dbStep) * dbStep; painter.setPen(QColor(50, 50, 50)); for(double db = toDb; db <= fromDb; db += dbStep) { if(abs(db) < 1e-6) db = 0; - double y = scene->dbToY(db); + double y = s->dbToY(db); if(y != -1) painter.drawText(0, y - topLeft.y() - 1, width(), 0, Qt::TextDontClip | Qt::AlignCenter, QString("%0").arg(db)); } + + QPoint mousePos = view->getLastMousePos(); + if(!mousePos.isNull()) + { + QPointF mouseScenePos = view->mapToScene(mousePos); + + double db = s->yToDb(mouseScenePos.y()); + double y = s->dbToY(db); + if(y != -1) + { + QString text = QString("%0").arg(db, 0, 'f', 1); + QFontMetrics metrics = painter.fontMetrics(); + QRectF rect = metrics.boundingRect(text); + float center = y - topLeft.y() - 1; + rect = QRectF(ceil(width() / 2) - ceil(rect.width() / 2) - 2.5, center - rect.height() / 2 + 1, rect.width() + 3, rect.height()); + QPainterPath path; + path.addRect(rect); + QPainterPath rightTriangle; + rightTriangle.moveTo(QPoint(rect.right(), center - 2)); + rightTriangle.lineTo(QPoint(rect.right(), center + 4)); + rightTriangle.lineTo(QPoint(rect.right() + 4, center + 1)); + path = path.united(rightTriangle); + + painter.setPen(Qt::black); + painter.setBrush(Qt::white); + painter.drawPath(path); + painter.setPen(Qt::blue); + painter.drawText(0, center, width() - 2, 0, Qt::TextDontClip | Qt::AlignCenter, text); + } + } +} + +void FrequencyPlotVRuler::wheelEvent(QWheelEvent* event) +{ + FrequencyPlotView* view = qobject_cast(parentWidget()); + view->zoom(0, event->angleDelta().y(), 0, event->y()); +} + +void FrequencyPlotVRuler::mouseMoveEvent(QMouseEvent* event) +{ + FrequencyPlotView* view = qobject_cast(parentWidget()); + view->setLastMousePos(QPoint(0, min(event->y(), view->viewport()->height() - 1))); } diff --git a/Editor/widgets/FrequencyPlotVRuler.h b/Editor/widgets/FrequencyPlotVRuler.h index 46cb136..b245f30 100644 --- a/Editor/widgets/FrequencyPlotVRuler.h +++ b/Editor/widgets/FrequencyPlotVRuler.h @@ -29,4 +29,6 @@ public: protected: void paintEvent(QPaintEvent*) override; + void wheelEvent(QWheelEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; }; diff --git a/Editor/widgets/FrequencyPlotView.cpp b/Editor/widgets/FrequencyPlotView.cpp index 682f9d1..b284daf 100644 --- a/Editor/widgets/FrequencyPlotView.cpp +++ b/Editor/widgets/FrequencyPlotView.cpp @@ -29,9 +29,11 @@ using namespace std; FrequencyPlotView::FrequencyPlotView(QWidget *parent) : QGraphicsView(parent) { - setViewportMargins(20, 0, 0, 20); + setViewportMargins(32, 0, 0, 20); hRuler = new FrequencyPlotHRuler(this); vRuler = new FrequencyPlotVRuler(this); + hRuler->setMouseTracking(true); + vRuler->setMouseTracking(true); } FrequencyPlotScene* FrequencyPlotView::scene() const @@ -58,8 +60,7 @@ void FrequencyPlotView::drawBackground(QPainter* painter, const QRectF& drawRect FrequencyPlotScene* s = scene(); QPointF topLeft = mapToScene(0, 0); QPointF bottomRight = mapToScene(viewport()->width(), viewport()->height()); - double dbHeight = abs(s->yToDb(topLeft.y()) - s->yToDb(bottomRight.y())); - double dbStep = dbHeight / 4; + double dbStep = abs(s->yToDb(0) - s->yToDb(30)); double dbBase = pow(10, floor(log10(dbStep))); if(dbStep >= 5 * dbBase) @@ -120,27 +121,57 @@ void FrequencyPlotView::updateHRuler() hRuler->update(); } -void FrequencyPlotView::wheelEvent(QWheelEvent* event) +QPoint FrequencyPlotView::getLastMousePos() const +{ + return lastMousePos; +} + +void FrequencyPlotView::setLastMousePos(QPoint pos) +{ + lastMousePos = pos; + hRuler->update(); + vRuler->update(); +} + +void FrequencyPlotView::zoom(int deltaX, int deltaY, int mouseX, int mouseY) { FrequencyPlotScene* s = scene(); - QPointF scenePos = mapToScene(event->pos()); + QPointF scenePos = mapToScene(mouseX, mouseY); double hz = s->xToHz(scenePos.x()); double db = s->yToDb(scenePos.y()); - qreal zoomFactor = pow(1.001, event->angleDelta().y()); - s->setZoom(max(0.5, min(30, s->getZoom() * zoomFactor))); + qreal zoomFactorX = pow(1.001, deltaX); + qreal zoomFactorY = pow(1.001, deltaY); + double zoomX = max(0.5, min(30.0, s->getZoomX() * zoomFactorX)); + double zoomY = max(0.5, min(30.0, s->getZoomY() * zoomFactorY)); + s->setZoom(zoomX, zoomY); - double x = s->hzToX(hz); - if(x != -1) - horizontalScrollBar()->setValue(horizontalScrollBar()->value() + round(x - scenePos.x())); - double y = s->dbToY(db); - if(y != -1) - verticalScrollBar()->setValue(verticalScrollBar()->value() + round(y - scenePos.y())); + if(deltaX != 0) + { + double x = s->hzToX(hz); + if(x != -1) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + round(x - scenePos.x())); + } + + if(deltaY != 0) + { + double y = s->dbToY(db); + if(y != -1) + verticalScrollBar()->setValue(verticalScrollBar()->value() + round(y - scenePos.y())); + } resetCachedContent(); viewport()->update(); - hRuler->update(); - vRuler->update(); + if(deltaX != 0) + hRuler->update(); + if(deltaY != 0) + vRuler->update(); +} + +void FrequencyPlotView::wheelEvent(QWheelEvent* event) +{ + int delta = event->angleDelta().y(); + zoom(delta, delta, event->x(), event->y()); } void FrequencyPlotView::scrollContentsBy(int dx, int dy) @@ -158,15 +189,16 @@ void FrequencyPlotView::resizeEvent(QResizeEvent* event) QGraphicsView::resizeEvent(event); const QRect rect = viewport()->geometry(); - hRuler->setGeometry(rect.x() - 20, rect.y() + rect.height(), rect.width() + 20, 20); - vRuler->setGeometry(rect.x() - 20, rect.y(), 20, rect.height() + 20); + QMargins margins = viewportMargins(); + hRuler->setGeometry(rect.x() - margins.left(), rect.y() + rect.height(), rect.width() + margins.left(), margins.bottom()); + vRuler->setGeometry(rect.x() - margins.left(), rect.y(), margins.left(), rect.height() + margins.bottom()); } void FrequencyPlotView::mousePressEvent(QMouseEvent* event) { QGraphicsView::mousePressEvent(event); - lastMousePos = event->pos(); + setLastMousePos(event->pos()); } void FrequencyPlotView::mouseMoveEvent(QMouseEvent* event) @@ -180,7 +212,12 @@ void FrequencyPlotView::mouseMoveEvent(QMouseEvent* event) { QGraphicsView::mouseMoveEvent(event); } - lastMousePos = event->pos(); + setLastMousePos(event->pos()); +} + +void FrequencyPlotView::leaveEvent(QEvent*) +{ + setLastMousePos(QPoint()); } void FrequencyPlotView::showEvent(QShowEvent* event) diff --git a/Editor/widgets/FrequencyPlotView.h b/Editor/widgets/FrequencyPlotView.h index c80f972..d766c43 100644 --- a/Editor/widgets/FrequencyPlotView.h +++ b/Editor/widgets/FrequencyPlotView.h @@ -38,12 +38,19 @@ public: void updateHRuler(); + QPoint getLastMousePos() const; + void setLastMousePos(QPoint pos); + using QGraphicsView::viewportMargins; + + void zoom(int deltaX, int deltaY, int mouseX, int mouseY); + protected: void wheelEvent(QWheelEvent* event) override; void scrollContentsBy(int dx, int dy) override; void resizeEvent(QResizeEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; + void leaveEvent(QEvent*) override; void showEvent(QShowEvent* event) override; private: diff --git a/Editor/widgets/ResizeCorner.cpp b/Editor/widgets/ResizeCorner.cpp new file mode 100644 index 0000000..be78412 --- /dev/null +++ b/Editor/widgets/ResizeCorner.cpp @@ -0,0 +1,52 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include "ResizeCorner.h" + +ResizeCorner::ResizeCorner(FilterTable* filterTable, QSize minimumSize, QSize maximumSize, std::function getFunc, std::function setFunc, QWidget *parent) + : QLabel(parent), minimumSize(minimumSize), maximumSize(maximumSize), getFunc(getFunc), setFunc(setFunc), filterTable(filterTable) +{ + setPixmap(QPixmap(":/icons/resize_corner.png")); + setCursor(Qt::SizeFDiagCursor); +} + +void ResizeCorner::mousePressEvent(QMouseEvent* event) +{ + filterTable->setMinimumHeightHint(filterTable->height()); + QSize size = getFunc(); + offsetX = size.width() - event->globalX(); + offsetY = size.height() - event->globalY(); +} + +void ResizeCorner::mouseMoveEvent(QMouseEvent* event) +{ + QSize size; + size.setWidth(offsetX + event->globalPos().x()); + size.setHeight(offsetY + event->globalPos().y()); + size = size.expandedTo(minimumSize); + size = size.boundedTo(maximumSize); + setFunc(size); +} + +void ResizeCorner::mouseReleaseEvent(QMouseEvent*) +{ + filterTable->setMinimumHeightHint(0); +} diff --git a/Editor/widgets/ResizeCorner.h b/Editor/widgets/ResizeCorner.h new file mode 100644 index 0000000..74e703a --- /dev/null +++ b/Editor/widgets/ResizeCorner.h @@ -0,0 +1,48 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2015 Jonas Thedering + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include + +#include "Editor/FilterTable.h" + +class ResizeCorner : public QLabel +{ + Q_OBJECT + +public: + explicit ResizeCorner(FilterTable* filterTable, QSize minimumSize, QSize maximumSize, + std::function getFunc, std::function setFunc, QWidget *parent = 0); + +protected: + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent*) override; + +private: + int offsetX; + int offsetY; + QSize minimumSize; + QSize maximumSize; + FilterTable* filterTable; + std::function getFunc; + std::function setFunc; +}; diff --git a/EqualizerAPO.cpp b/EqualizerAPO.cpp index 05bfd7d..2b4a857 100644 --- a/EqualizerAPO.cpp +++ b/EqualizerAPO.cpp @@ -57,6 +57,8 @@ EqualizerAPO::EqualizerAPO(IUnknown* pUnkOuter) EqualizerAPO::~EqualizerAPO() { InterlockedDecrement(&instCount); + + resetChild(); } HRESULT EqualizerAPO::QueryInterface(const IID& iid, void** ppv) @@ -156,7 +158,7 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) if(FAILED(hr)) { LogF(L"Can't convert child apo guid string to guid"); - return hr; + return S_OK; } hr = CoCreateInstance(childGuid, NULL, CLSCTX_INPROC_SERVER, __uuidof(IAudioProcessingObject), (void**)&childAPO); @@ -164,7 +166,7 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) { LogF(L"Error in CoCreateInstance for child apo"); resetChild(); - return hr; + return S_OK; } hr = childAPO->QueryInterface(__uuidof(IAudioProcessingObjectRT), (void**)&childRT); @@ -172,7 +174,7 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) { LogF(L"Error in QueryInterface for child apo RT"); resetChild(); - return hr; + return S_OK; } hr = childAPO->QueryInterface(__uuidof(IAudioProcessingObjectConfiguration), (void**)&childCfg); @@ -180,7 +182,7 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) { LogF(L"Error in QueryInterface for child apo configuration"); resetChild(); - return hr; + return S_OK; } hr = childAPO->Initialize(cbDataSize, pbyData); @@ -188,13 +190,13 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) { LogF(L"Error in Initialize of child apo"); resetChild(); - return hr; + return S_OK; } TraceF(L"Successfully created and initialized child APO"); } - return hr; + return S_OK; } HRESULT EqualizerAPO::IsInputFormatSupported(IAudioMediaType* pOutputFormat, diff --git a/FilterEngine.cpp b/FilterEngine.cpp index 4110b97..eed4e02 100644 --- a/FilterEngine.cpp +++ b/FilterEngine.cpp @@ -127,7 +127,7 @@ void FilterEngine::setDeviceInfo(bool capture, bool postMixInstalled, const wstr this->deviceGuid = deviceGuid; } -void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount) +void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount, const wstring& customPath) { EnterCriticalSection(&loadSection); @@ -184,9 +184,9 @@ void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsi if(configPath != L"") { - loadConfig(); + loadConfig(customPath); - if(threadHandle == NULL) + if(threadHandle == NULL && customPath.empty()) { shutdownEvent = CreateEventW(NULL, true, false, NULL); threadHandle = CreateThread(NULL, 0, notificationThread, this, 0, NULL); @@ -199,7 +199,7 @@ void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsi LeaveCriticalSection(&loadSection); } -void FilterEngine::loadConfig() +void FilterEngine::loadConfig(const wstring& customPath) { EnterCriticalSection(&loadSection); timer.start(); @@ -226,7 +226,10 @@ void FilterEngine::loadConfig() addFilters(newFilters); } - loadConfig(configPath + L"\\config.txt"); + if(customPath.empty()) + loadConfigFile(configPath + L"\\config.txt"); + else + loadConfigFile(customPath); for(vector::const_iterator it = factories.cbegin(); it != factories.cend(); it++) { @@ -252,7 +255,7 @@ void FilterEngine::loadConfig() LeaveCriticalSection(&loadSection); } -void FilterEngine::loadConfig(const wstring& path) +void FilterEngine::loadConfigFile(const wstring& path) { TraceF(L"Loading configuration from %s", path.c_str()); diff --git a/FilterEngine.h b/FilterEngine.h index 5605962..dc64602 100644 --- a/FilterEngine.h +++ b/FilterEngine.h @@ -40,9 +40,9 @@ public: void setPreMix(bool preMix); void setDeviceInfo(bool capture, bool postMixInstalled, const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid); - void initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount); - void loadConfig(); - void loadConfig(const std::wstring& path); + void initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount, const std::wstring& customPath = L""); + void loadConfig(const std::wstring& customPath = L""); + void loadConfigFile(const std::wstring& path); void watchRegistryKey(const std::wstring& key); void process(float *output, float *input, unsigned frameCount); diff --git a/Setup/Setup.nsi b/Setup/Setup.nsi index ba8b917..ddbdd2e 100644 --- a/Setup/Setup.nsi +++ b/Setup/Setup.nsi @@ -165,7 +165,7 @@ Section "Install" SecInstall !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts CreateDirectory "$SMPROGRAMS\$StartMenuFolder" - CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration editor.lnk" "$INSTDIR\Editor.exe" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration Editor.lnk" "$INSTDIR\Editor.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration tutorial (online).lnk" "$INSTDIR\Configuration tutorial (online).url" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configuration reference (online).lnk" "$INSTDIR\Configuration reference (online).url" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Configurator.lnk" "$INSTDIR\Configurator.exe" diff --git a/filters/IncludeFilterFactory.cpp b/filters/IncludeFilterFactory.cpp index 6295125..9a3c3aa 100644 --- a/filters/IncludeFilterFactory.cpp +++ b/filters/IncludeFilterFactory.cpp @@ -56,7 +56,7 @@ vector IncludeFilterFactory::createFilter(const wstring& configPath, w else includePath = value; - engine->loadConfig(includePath); + engine->loadConfigFile(includePath); command = L""; }