Fixed: With some sound card drivers, unwanted sounds were heard when audio got silent (e.g. by pausing). The modification of silent buffers, which seems to cause this issue, is now optionally available via the troubleshooting options in the Configurator. Fixed: In the Configuration Editor the peak gain display in the analysis panel was sometimes showing incorrect values. Improved: The GUI of the GraphicEQ command can now import the measurement text file format from Room EQ Wizard.
278 lines
6.5 KiB
C++
278 lines
6.5 KiB
C++
/*
|
|
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 <QElapsedTimer>
|
|
|
|
#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 / 2; 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();
|
|
}
|
|
}
|