mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-07-30 14:04:18 -04:00
git clone git://git.osmocom.org/sdrangelove.git
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
#include "dsp/channelizer.h"
|
||||
#include "dsp/inthalfbandfilter.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
Channelizer::Channelizer(SampleSink* sampleSink) :
|
||||
m_sampleSink(sampleSink),
|
||||
m_inputSampleRate(100000),
|
||||
m_requestedOutputSampleRate(100000),
|
||||
m_requestedCenterFrequency(0),
|
||||
m_currentOutputSampleRate(100000),
|
||||
m_currentCenterFrequency(0)
|
||||
{
|
||||
}
|
||||
|
||||
Channelizer::~Channelizer()
|
||||
{
|
||||
freeFilterChain();
|
||||
}
|
||||
|
||||
void Channelizer::configure(MessageQueue* messageQueue, int sampleRate, int centerFrequency)
|
||||
{
|
||||
Message* cmd = DSPConfigureChannelizer::create(sampleRate, centerFrequency);
|
||||
cmd->submit(messageQueue, this);
|
||||
}
|
||||
|
||||
void Channelizer::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
for(SampleVector::const_iterator sample = begin; sample != end; ++sample) {
|
||||
Sample s(*sample);
|
||||
FilterStages::iterator stage = m_filterStages.begin();
|
||||
while(stage != m_filterStages.end()) {
|
||||
if(!(*stage)->work(&s))
|
||||
break;
|
||||
++stage;
|
||||
}
|
||||
if(stage == m_filterStages.end())
|
||||
m_sampleBuffer.push_back(s);
|
||||
}
|
||||
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst);
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
void Channelizer::start()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->start();
|
||||
}
|
||||
|
||||
void Channelizer::stop()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->stop();
|
||||
}
|
||||
|
||||
bool Channelizer::handleMessage(Message* cmd)
|
||||
{
|
||||
if(DSPSignalNotification::match(cmd)) {
|
||||
DSPSignalNotification* signal = (DSPSignalNotification*)cmd;
|
||||
m_inputSampleRate = signal->getSampleRate();
|
||||
applyConfiguration();
|
||||
cmd->completed();
|
||||
if(m_sampleSink != NULL) {
|
||||
signal = DSPSignalNotification::create(m_currentOutputSampleRate, m_currentCenterFrequency);
|
||||
if(!m_sampleSink->handleMessage(signal))
|
||||
signal->completed();
|
||||
}
|
||||
return true;
|
||||
} else if(DSPConfigureChannelizer::match(cmd)) {
|
||||
DSPConfigureChannelizer* chan = (DSPConfigureChannelizer*)cmd;
|
||||
m_requestedOutputSampleRate = chan->getSampleRate();
|
||||
m_requestedCenterFrequency = chan->getCenterFrequency();
|
||||
applyConfiguration();
|
||||
cmd->completed();
|
||||
if(m_sampleSink != NULL) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_currentOutputSampleRate, m_currentCenterFrequency);
|
||||
if(!m_sampleSink->handleMessage(signal))
|
||||
signal->completed();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if(m_sampleSink != NULL)
|
||||
return m_sampleSink->handleMessage(cmd);
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Channelizer::applyConfiguration()
|
||||
{
|
||||
freeFilterChain();
|
||||
m_currentCenterFrequency = createFilterChain(
|
||||
m_inputSampleRate / -2, m_inputSampleRate / 2,
|
||||
m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2);
|
||||
m_currentOutputSampleRate = m_inputSampleRate / (1 << m_filterStages.size());
|
||||
}
|
||||
|
||||
Channelizer::FilterStage::FilterStage(Mode mode) :
|
||||
m_filter(new IntHalfbandFilter),
|
||||
m_workFunction(NULL)
|
||||
{
|
||||
switch(mode) {
|
||||
case ModeCenter:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateCenter;
|
||||
break;
|
||||
|
||||
case ModeLowerHalf:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateLowerHalf;
|
||||
break;
|
||||
|
||||
case ModeUpperHalf:
|
||||
m_workFunction = &IntHalfbandFilter::workDecimateUpperHalf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Channelizer::FilterStage::~FilterStage()
|
||||
{
|
||||
delete m_filter;
|
||||
}
|
||||
|
||||
bool Channelizer::signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const
|
||||
{
|
||||
//qDebug(" testing signal [%f, %f], channel [%f, %f]", sigStart, sigEnd, chanStart, chanEnd);
|
||||
if(sigEnd <= sigStart)
|
||||
return false;
|
||||
if(chanEnd <= chanStart)
|
||||
return false;
|
||||
return (sigStart <= chanStart) && (sigEnd >= chanEnd);
|
||||
}
|
||||
|
||||
Real Channelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd)
|
||||
{
|
||||
Real sigBw = sigEnd - sigStart;
|
||||
Real safetyMargin = sigBw / 20;
|
||||
Real rot = sigBw / 4;
|
||||
|
||||
safetyMargin = 0;
|
||||
|
||||
//qDebug("Signal [%f, %f] (BW %f), Channel [%f, %f], Rot %f, Safety %f", sigStart, sigEnd, sigBw, chanStart, chanEnd, rot, safetyMargin);
|
||||
#if 1
|
||||
// check if it fits into the left half
|
||||
if(signalContainsChannel(sigStart + safetyMargin, sigStart + sigBw / 2.0 - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take left half (rotate by +1/4 and decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
|
||||
return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd);
|
||||
}
|
||||
|
||||
// check if it fits into the right half
|
||||
if(signalContainsChannel(sigEnd - sigBw / 2.0f + safetyMargin, sigEnd - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take right half (rotate by -1/4 and decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
|
||||
return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd);
|
||||
}
|
||||
|
||||
// check if it fits into the center
|
||||
if(signalContainsChannel(sigStart + rot + safetyMargin, sigStart + rot + sigBw / 2.0f - safetyMargin, chanStart, chanEnd)) {
|
||||
//qDebug("-> take center half (decimate by 2)");
|
||||
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
|
||||
return createFilterChain(sigStart + rot, sigStart + sigBw / 2.0f + rot, chanStart, chanEnd);
|
||||
}
|
||||
#endif
|
||||
Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart);
|
||||
qDebug("-> complete (final BW %f, frequency offset %f)", sigBw, ofs);
|
||||
return ofs;
|
||||
}
|
||||
|
||||
void Channelizer::freeFilterChain()
|
||||
{
|
||||
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
|
||||
delete *it;
|
||||
m_filterStages.clear();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QTime>
|
||||
#include <stdio.h>
|
||||
#include "channelizer.h"
|
||||
#include "hardware/audiooutput.h"
|
||||
|
||||
Channelizer::Channelizer()
|
||||
{
|
||||
#if 0
|
||||
m_spectrum.configure(128 , 25, FFTWindow::Bartlett);
|
||||
m_buffer.resize(2048);
|
||||
m_bufferFill = 0;
|
||||
m_nco.setFreq(-100000, 500000);
|
||||
m_interpolator.create(51, 32, 32 * 500000, 150000 / 2);
|
||||
m_distance = 500000.0 / 176400.0;
|
||||
|
||||
m_interpolator2.create(19, 8, 8 * 176400, 15000 / 2);
|
||||
m_distance2 = 4;
|
||||
|
||||
m_audioFifo.setSize(4, 44100 / 2 * 4);
|
||||
m_audioOutput = new AudioOutput;
|
||||
m_audioOutput->start(0, 44100, &m_audioFifo);
|
||||
m_resampler = 1.0;
|
||||
|
||||
m_resamplerCtrl.setup(0.00001, 0, -0.00001);
|
||||
#endif
|
||||
}
|
||||
|
||||
Channelizer::~Channelizer()
|
||||
{
|
||||
#if 0
|
||||
m_audioOutput->stop();
|
||||
delete m_audioOutput;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Channelizer::setGLSpectrum(GLSpectrum* glSpectrum)
|
||||
{
|
||||
m_spectrum.setGLSpectrum(glSpectrum);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t Channelizer::workUnitSize()
|
||||
{
|
||||
#if 0
|
||||
return m_buffer.size();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Channelizer::work(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
#if 0
|
||||
int buffered = m_audioOutput->bufferedSamples();
|
||||
|
||||
if(m_audioFifo.fill() < (m_audioFifo.size() / 6)) {
|
||||
while(m_audioFifo.fill() < (m_audioFifo.size() / 2)) {
|
||||
quint32 d = 0;
|
||||
m_audioFifo.write((quint8*)&d, 4);
|
||||
}
|
||||
qDebug("underflow - fill %d (vs %d)", m_audioFifo.fill(), m_audioFifo.size() / 4 / 2);
|
||||
}
|
||||
|
||||
buffered = m_audioOutput->bufferedSamples();
|
||||
int fill = m_audioFifo.fill() / 4 + buffered;
|
||||
float err = (float)fill / ((m_audioFifo.size() / 4) / 2);
|
||||
|
||||
float ctrl = m_resamplerCtrl.feed(err);
|
||||
//float resamplerRate = (ctrl / 1.0);
|
||||
float resamplerRate = err;
|
||||
|
||||
if(resamplerRate < 0.9999)
|
||||
resamplerRate = 0.9999;
|
||||
else if(resamplerRate > 1.0001)
|
||||
resamplerRate = 1.0001;
|
||||
m_resampler = m_resampler * 0.99 + resamplerRate * 0.01;
|
||||
//m_resampler = resamplerRate;
|
||||
|
||||
if(m_resampler < 0.995)
|
||||
m_resampler = 0.995;
|
||||
else if(m_resampler > 1.005)
|
||||
m_resampler = 1.005;
|
||||
|
||||
//qDebug("%lld %5d %f %f %f", QDateTime::currentMSecsSinceEpoch(), fill, ctrl, m_resampler, err);
|
||||
|
||||
struct AudioSample {
|
||||
qint16 l;
|
||||
qint16 r;
|
||||
};
|
||||
|
||||
size_t count = end - begin;
|
||||
Complex ci;
|
||||
bool consumed;
|
||||
bool consumed2;
|
||||
|
||||
for(SampleVector::const_iterator it = begin; it < end; it++) {
|
||||
Complex c(it->real() / 32768.0, it->imag() / 32768.0);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
consumed = false;
|
||||
if(m_interpolator.interpolate(&m_distance, c, &consumed, &ci)) {
|
||||
|
||||
Complex d = ci * conj(m_lastSample);
|
||||
m_lastSample = ci;
|
||||
//Complex demod(atan2(d.imag(), d.real()) * 0.5, 0);
|
||||
Real demod = atan2(d.imag(), d.real()) / M_PI;
|
||||
|
||||
consumed2 = false;
|
||||
c = Complex(demod, 0);
|
||||
while(!consumed2) {
|
||||
if(m_interpolator2.interpolate(&m_distance2, c, &consumed2, &ci)) {
|
||||
m_buffer[m_bufferFill++] = Sample(ci.real() * 32767.0, 0.0);
|
||||
|
||||
AudioSample s;
|
||||
s.l = ci.real() * 32767.0;
|
||||
s.r = s.l;
|
||||
m_audioFifo.write((quint8*)&s, 4, 1);
|
||||
|
||||
if(m_bufferFill >= m_buffer.size()) {
|
||||
m_spectrum.feed(m_buffer.begin(), m_buffer.end());
|
||||
m_bufferFill = 0;
|
||||
}
|
||||
m_distance2 += 4.0 * m_resampler;
|
||||
}
|
||||
}
|
||||
m_distance += 500000 / 176400.0;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "dsp/channelmarker.h"
|
||||
|
||||
QRgb ChannelMarker::m_colorTable[] = {
|
||||
qRgb(0xc0, 0x00, 0x00),
|
||||
qRgb(0x00, 0xc0, 0x00),
|
||||
qRgb(0x00, 0x00, 0xc0),
|
||||
|
||||
qRgb(0xc0, 0xc0, 0x00),
|
||||
qRgb(0xc0, 0x00, 0xc0),
|
||||
qRgb(0x00, 0xc0, 0xc0),
|
||||
|
||||
qRgb(0xc0, 0x60, 0x00),
|
||||
qRgb(0xc0, 0x00, 0x60),
|
||||
qRgb(0x60, 0x00, 0xc0),
|
||||
|
||||
qRgb(0x60, 0x00, 0x00),
|
||||
qRgb(0x00, 0x60, 0x00),
|
||||
qRgb(0x00, 0x00, 0x60),
|
||||
|
||||
qRgb(0x60, 0x60, 0x00),
|
||||
qRgb(0x60, 0x00, 0x60),
|
||||
qRgb(0x00, 0x60, 0x60),
|
||||
|
||||
0
|
||||
};
|
||||
int ChannelMarker::m_nextColor = 0;
|
||||
|
||||
ChannelMarker::ChannelMarker(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_centerFrequency(0),
|
||||
m_bandwidth(0),
|
||||
m_visible(false),
|
||||
m_color(m_colorTable[m_nextColor])
|
||||
{
|
||||
++m_nextColor;
|
||||
if(m_colorTable[m_nextColor] == 0)
|
||||
m_nextColor = 0;
|
||||
}
|
||||
|
||||
void ChannelMarker::setTitle(const QString& title)
|
||||
{
|
||||
m_title = title;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setCenterFrequency(int centerFrequency)
|
||||
{
|
||||
m_centerFrequency = centerFrequency;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setBandwidth(int bandwidth)
|
||||
{
|
||||
m_bandwidth = bandwidth;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setVisible(bool visible)
|
||||
{
|
||||
m_visible = visible;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void ChannelMarker::setColor(const QColor& color)
|
||||
{
|
||||
m_color = color;
|
||||
emit changed();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DSPPing, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPExit, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStart, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAcquisitionStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPGetDeviceDescription, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPGetErrorMessage, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPSetSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAddSink, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPRemoveSink, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPAddAudioSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPRemoveAudioSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureSpectrumVis, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureCorrection, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPEngineReport, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureScopeVis, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message)
|
||||
@@ -0,0 +1,517 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/channelizer.h"
|
||||
#include "dsp/samplefifo.h"
|
||||
#include "dsp/samplesink.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/samplesource/samplesource.h"
|
||||
|
||||
DSPEngine::DSPEngine(MessageQueue* reportQueue, QObject* parent) :
|
||||
QThread(parent),
|
||||
m_messageQueue(),
|
||||
m_reportQueue(reportQueue),
|
||||
m_state(StNotStarted),
|
||||
m_sampleSource(NULL),
|
||||
m_sampleSinks(),
|
||||
m_sampleRate(0),
|
||||
m_centerFrequency(0),
|
||||
m_dcOffsetCorrection(false),
|
||||
m_iqImbalanceCorrection(false),
|
||||
m_iOffset(0),
|
||||
m_qOffset(0),
|
||||
m_iRange(1 << 16),
|
||||
m_qRange(1 << 16),
|
||||
m_imbalance(65536)
|
||||
{
|
||||
moveToThread(this);
|
||||
}
|
||||
|
||||
DSPEngine::~DSPEngine()
|
||||
{
|
||||
wait();
|
||||
}
|
||||
|
||||
void DSPEngine::start()
|
||||
{
|
||||
DSPPing cmd;
|
||||
QThread::start();
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::stop()
|
||||
{
|
||||
DSPExit cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
bool DSPEngine::startAcquisition()
|
||||
{
|
||||
DSPAcquisitionStart cmd;
|
||||
return cmd.execute(&m_messageQueue) == StRunning;
|
||||
}
|
||||
|
||||
void DSPEngine::stopAcquistion()
|
||||
{
|
||||
DSPAcquisitionStop cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::setSource(SampleSource* source)
|
||||
{
|
||||
DSPSetSource cmd(source);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::addSink(SampleSink* sink)
|
||||
{
|
||||
DSPAddSink cmd(sink);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::removeSink(SampleSink* sink)
|
||||
{
|
||||
DSPRemoveSink cmd(sink);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::addAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
DSPAddAudioSource cmd(audioFifo);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::removeAudioSource(AudioFifo* audioFifo)
|
||||
{
|
||||
DSPRemoveAudioSource cmd(audioFifo);
|
||||
cmd.execute(&m_messageQueue);
|
||||
}
|
||||
|
||||
void DSPEngine::configureCorrections(bool dcOffsetCorrection, bool iqImbalanceCorrection)
|
||||
{
|
||||
Message* cmd = DSPConfigureCorrection::create(dcOffsetCorrection, iqImbalanceCorrection);
|
||||
cmd->submit(&m_messageQueue);
|
||||
}
|
||||
|
||||
QString DSPEngine::errorMessage()
|
||||
{
|
||||
DSPGetErrorMessage cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
return cmd.getErrorMessage();
|
||||
}
|
||||
|
||||
QString DSPEngine::deviceDescription()
|
||||
{
|
||||
DSPGetDeviceDescription cmd;
|
||||
cmd.execute(&m_messageQueue);
|
||||
return cmd.getDeviceDescription();
|
||||
}
|
||||
|
||||
void DSPEngine::run()
|
||||
{
|
||||
connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()), Qt::QueuedConnection);
|
||||
|
||||
m_state = StIdle;
|
||||
|
||||
handleMessages();
|
||||
exec();
|
||||
}
|
||||
|
||||
void DSPEngine::dcOffset(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
int count = end - begin;
|
||||
int io = 0;
|
||||
int qo = 0;
|
||||
|
||||
// sum all sample components
|
||||
for(SampleVector::iterator it = begin; it < end; it++) {
|
||||
io += it->real();
|
||||
qo += it->imag();
|
||||
}
|
||||
|
||||
// build a sliding average (el cheapo style)
|
||||
m_iOffset = (m_iOffset * 3 + io / count) >> 2;
|
||||
m_qOffset = (m_qOffset * 3 + qo / count) >> 2;
|
||||
|
||||
// correct samples
|
||||
Sample corr(m_iOffset, m_qOffset);
|
||||
for(SampleVector::iterator it = begin; it < end; it++)
|
||||
*it -= corr;
|
||||
}
|
||||
|
||||
void DSPEngine::imbalance(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
int iMin = 0;
|
||||
int iMax = 0;
|
||||
int qMin = 0;
|
||||
int qMax = 0;
|
||||
|
||||
// find value ranges for both I and Q
|
||||
// both intervals should be same same size (for a perfect circle)
|
||||
for(SampleVector::iterator it = begin; it < end; it++) {
|
||||
if(it != begin) {
|
||||
if(it->real() < iMin)
|
||||
iMin = it->real();
|
||||
else if(it->real() > iMax)
|
||||
iMax = it->real();
|
||||
if(it->imag() < qMin)
|
||||
qMin = it->imag();
|
||||
else if(it->imag() > qMax)
|
||||
qMax = it->imag();
|
||||
|
||||
} else {
|
||||
iMin = it->real();
|
||||
iMax = it->real();
|
||||
qMin = it->imag();
|
||||
qMax = it->imag();
|
||||
}
|
||||
}
|
||||
|
||||
// sliding average (el cheapo again)
|
||||
m_iRange = (m_iRange * 15 + (iMax - iMin)) >> 4;
|
||||
m_qRange = (m_qRange * 15 + (qMax - qMin)) >> 4;
|
||||
|
||||
// calculate imbalance as Q15.16
|
||||
if(m_qRange != 0)
|
||||
m_imbalance = ((uint)m_iRange << 16) / (uint)m_qRange;
|
||||
|
||||
// correct imbalance and convert back to signed int 16
|
||||
for(SampleVector::iterator it = begin; it < end; it++)
|
||||
it->m_imag = (it->m_imag * m_imbalance) >> 16;
|
||||
}
|
||||
|
||||
void DSPEngine::work()
|
||||
{
|
||||
SampleFifo* sampleFifo = m_sampleSource->getSampleFifo();
|
||||
size_t samplesDone = 0;
|
||||
bool firstOfBurst = true;
|
||||
|
||||
while((sampleFifo->fill() > 0) && (m_messageQueue.countPending() == 0) && (samplesDone < m_sampleRate)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = sampleFifo->readBegin(sampleFifo->fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// correct stuff
|
||||
if(m_dcOffsetCorrection)
|
||||
dcOffset(part1begin, part1end);
|
||||
if(m_iqImbalanceCorrection)
|
||||
imbalance(part1begin, part1end);
|
||||
// feed data to handlers
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// correct stuff
|
||||
if(m_dcOffsetCorrection)
|
||||
dcOffset(part2begin, part2end);
|
||||
if(m_iqImbalanceCorrection)
|
||||
imbalance(part2begin, part2end);
|
||||
// feed data to handlers
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part2begin, part2end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
sampleFifo->readCommit(count);
|
||||
samplesDone += count;
|
||||
}
|
||||
|
||||
#if 0
|
||||
size_t wus;
|
||||
size_t maxWorkUnitSize = 0;
|
||||
size_t samplesDone = 0;
|
||||
|
||||
wus = m_spectrum.workUnitSize();
|
||||
if(wus > maxWorkUnitSize)
|
||||
maxWorkUnitSize = wus;
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
wus = (*it)->workUnitSize();
|
||||
if(wus > maxWorkUnitSize)
|
||||
maxWorkUnitSize = wus;
|
||||
}
|
||||
|
||||
while((m_sampleFifo.fill() > maxWorkUnitSize) && (m_commandQueue.countPending() == 0) && (samplesDone < m_sampleRate)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// correct stuff
|
||||
if(m_settings.dcOffsetCorrection())
|
||||
dcOffset(part1begin, part1end);
|
||||
if(m_settings.iqImbalanceCorrection())
|
||||
imbalance(part1begin, part1end);
|
||||
// feed data to handlers
|
||||
m_spectrum.feed(part1begin, part1end);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end);
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// correct stuff
|
||||
if(m_settings.dcOffsetCorrection())
|
||||
dcOffset(part2begin, part2end);
|
||||
if(m_settings.iqImbalanceCorrection())
|
||||
imbalance(part2begin, part2end);
|
||||
// feed data to handlers
|
||||
m_spectrum.feed(part2begin, part2end);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->feed(part1begin, part1end);
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
m_sampleFifo.readCommit(count);
|
||||
samplesDone += count;
|
||||
}
|
||||
|
||||
// check if the center frequency has changed (has to be responsive)
|
||||
if(m_settings.isModifiedCenterFreq())
|
||||
m_sampleSource->setCenterFrequency(m_settings.centerFreq());
|
||||
// check if decimation has changed (needed to be done here, because to high a sample rate can clog the switch)
|
||||
if(m_settings.isModifiedDecimation()) {
|
||||
m_sampleSource->setDecimation(m_settings.decimation());
|
||||
m_sampleRate = 4000000 / (1 << m_settings.decimation());
|
||||
qDebug("New rate: %d", m_sampleRate);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoIdle()
|
||||
{
|
||||
switch(m_state) {
|
||||
case StNotStarted:
|
||||
return StNotStarted;
|
||||
|
||||
case StIdle:
|
||||
case StError:
|
||||
return StIdle;
|
||||
|
||||
case StRunning:
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_sampleSource == NULL)
|
||||
return StIdle;
|
||||
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->stop();
|
||||
m_sampleSource->stopInput();
|
||||
m_deviceDescription.clear();
|
||||
m_audioOutput.stop();
|
||||
m_sampleRate = 0;
|
||||
|
||||
return StIdle;
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoRunning()
|
||||
{
|
||||
switch(m_state) {
|
||||
case StNotStarted:
|
||||
return StNotStarted;
|
||||
|
||||
case StRunning:
|
||||
return StRunning;
|
||||
|
||||
case StIdle:
|
||||
case StError:
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_sampleSource == NULL)
|
||||
return gotoError("No sample source configured");
|
||||
|
||||
m_iOffset = 0;
|
||||
m_qOffset = 0;
|
||||
m_iRange = 1 << 16;
|
||||
m_qRange = 1 << 16;
|
||||
|
||||
if(!m_sampleSource->startInput(0))
|
||||
return gotoError("Could not start sample source");
|
||||
m_deviceDescription = m_sampleSource->getDeviceDescription();
|
||||
|
||||
m_audioOutput.start(0, 44100);
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++)
|
||||
(*it)->start();
|
||||
m_sampleRate = 0; // make sure, report is sent
|
||||
generateReport();
|
||||
|
||||
return StRunning;
|
||||
}
|
||||
|
||||
DSPEngine::State DSPEngine::gotoError(const QString& errorMessage)
|
||||
{
|
||||
m_errorMessage = errorMessage;
|
||||
m_deviceDescription.clear();
|
||||
m_state = StError;
|
||||
return StError;
|
||||
}
|
||||
|
||||
void DSPEngine::handleSetSource(SampleSource* source)
|
||||
{
|
||||
gotoIdle();
|
||||
if(m_sampleSource != NULL)
|
||||
disconnect(m_sampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()));
|
||||
m_sampleSource = source;
|
||||
if(m_sampleSource != NULL)
|
||||
connect(m_sampleSource->getSampleFifo(), SIGNAL(dataReady()), this, SLOT(handleData()), Qt::QueuedConnection);
|
||||
generateReport();
|
||||
}
|
||||
|
||||
void DSPEngine::generateReport()
|
||||
{
|
||||
bool needReport = false;
|
||||
int sampleRate;
|
||||
quint64 centerFrequency;
|
||||
|
||||
if(m_sampleSource != NULL) {
|
||||
sampleRate = m_sampleSource->getSampleRate();
|
||||
centerFrequency = m_sampleSource->getCenterFrequency();
|
||||
} else {
|
||||
sampleRate = 100000;
|
||||
centerFrequency = 100000000;
|
||||
}
|
||||
|
||||
if(sampleRate != m_sampleRate) {
|
||||
m_sampleRate = sampleRate;
|
||||
needReport = true;
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_sampleRate, 0);
|
||||
signal->submit(&m_messageQueue, *it);
|
||||
}
|
||||
}
|
||||
if(centerFrequency != m_centerFrequency) {
|
||||
m_centerFrequency = centerFrequency;
|
||||
needReport = true;
|
||||
}
|
||||
|
||||
if(needReport) {
|
||||
Message* rep = DSPEngineReport::create(m_sampleRate, m_centerFrequency);
|
||||
rep->submit(m_reportQueue);
|
||||
}
|
||||
}
|
||||
|
||||
bool DSPEngine::distributeMessage(Message* message)
|
||||
{
|
||||
if(m_sampleSource != NULL) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == m_sampleSource)) {
|
||||
if(m_sampleSource->handleMessage(message)) {
|
||||
generateReport();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(SampleSinks::const_iterator it = m_sampleSinks.begin(); it != m_sampleSinks.end(); it++) {
|
||||
if((message->getDestination() == NULL) || (message->getDestination() == *it)) {
|
||||
if((*it)->handleMessage(message))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DSPEngine::handleData()
|
||||
{
|
||||
if(m_state == StRunning)
|
||||
work();
|
||||
}
|
||||
|
||||
void DSPEngine::handleMessages()
|
||||
{
|
||||
Message* message;
|
||||
while((message = m_messageQueue.accept()) != NULL) {
|
||||
qDebug("Message: %s", message->getIdentifier());
|
||||
|
||||
if(DSPPing::match(message)) {
|
||||
message->completed(m_state);
|
||||
} else if(DSPExit::match(message)) {
|
||||
gotoIdle();
|
||||
m_state = StNotStarted;
|
||||
exit();
|
||||
message->completed(m_state);
|
||||
} else if(DSPAcquisitionStart::match(message)) {
|
||||
m_state = gotoIdle();
|
||||
if(m_state == StIdle)
|
||||
m_state = gotoRunning();
|
||||
message->completed(m_state);
|
||||
} else if(DSPAcquisitionStop::match(message)) {
|
||||
m_state = gotoIdle();
|
||||
message->completed(m_state);
|
||||
} else if(DSPGetDeviceDescription::match(message)) {
|
||||
((DSPGetDeviceDescription*)message)->setDeviceDescription(m_deviceDescription);
|
||||
message->completed();
|
||||
} else if(DSPGetErrorMessage::match(message)) {
|
||||
((DSPGetErrorMessage*)message)->setErrorMessage(m_errorMessage);
|
||||
message->completed();
|
||||
} else if(DSPSetSource::match(message)) {
|
||||
handleSetSource(((DSPSetSource*)message)->getSampleSource());
|
||||
message->completed();
|
||||
} else if(DSPAddSink::match(message)) {
|
||||
SampleSink* sink = ((DSPAddSink*)message)->getSampleSink();
|
||||
if(m_state == StRunning) {
|
||||
DSPSignalNotification* signal = DSPSignalNotification::create(m_sampleRate, 0);
|
||||
signal->submit(&m_messageQueue, sink);
|
||||
sink->start();
|
||||
}
|
||||
m_sampleSinks.push_back(sink);
|
||||
message->completed();
|
||||
} else if(DSPRemoveSink::match(message)) {
|
||||
SampleSink* sink = ((DSPAddSink*)message)->getSampleSink();
|
||||
if(m_state == StRunning)
|
||||
sink->stop();
|
||||
m_sampleSinks.remove(sink);
|
||||
message->completed();
|
||||
} else if(DSPAddAudioSource::match(message)) {
|
||||
m_audioOutput.addFifo(((DSPAddAudioSource*)message)->getAudioFifo());
|
||||
message->completed();
|
||||
} else if(DSPRemoveAudioSource::match(message)) {
|
||||
m_audioOutput.removeFifo(((DSPAddAudioSource*)message)->getAudioFifo());
|
||||
message->completed();
|
||||
} else if(DSPConfigureCorrection::match(message)) {
|
||||
DSPConfigureCorrection* conf = (DSPConfigureCorrection*)message;
|
||||
m_iqImbalanceCorrection = conf->getIQImbalanceCorrection();
|
||||
if(m_dcOffsetCorrection != conf->getDCOffsetCorrection()) {
|
||||
m_dcOffsetCorrection = conf->getDCOffsetCorrection();
|
||||
m_iOffset = 0;
|
||||
m_qOffset = 0;
|
||||
}
|
||||
if(m_iqImbalanceCorrection != conf->getIQImbalanceCorrection()) {
|
||||
m_iqImbalanceCorrection = conf->getIQImbalanceCorrection();
|
||||
m_iRange = 1 << 16;
|
||||
m_qRange = 1 << 16;
|
||||
m_imbalance = 65536;
|
||||
}
|
||||
message->completed();
|
||||
} else {
|
||||
if(!distributeMessage(message))
|
||||
message->completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "dsp/fftengine.h"
|
||||
#ifdef USE_KISSFFT
|
||||
#include "dsp/kissengine.h"
|
||||
#endif
|
||||
#ifdef USE_FFTW
|
||||
#include "dsp/fftwengine.h"
|
||||
#endif // USE_FFTW
|
||||
|
||||
FFTEngine::~FFTEngine()
|
||||
{
|
||||
}
|
||||
|
||||
FFTEngine* FFTEngine::create()
|
||||
{
|
||||
#ifdef USE_FFTW
|
||||
qDebug("FFT: using FFTW engine");
|
||||
return new FFTWEngine;
|
||||
#endif // USE_FFTW
|
||||
#ifdef USE_KISSFFT
|
||||
qDebug("FFT: using KissFFT engine");
|
||||
return new KissEngine;
|
||||
#endif // USE_KISSFFT
|
||||
|
||||
qCritical("FFT: no engine built");
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <QTime>
|
||||
#include "dsp/fftwengine.h"
|
||||
|
||||
FFTWEngine::FFTWEngine() :
|
||||
m_plans(),
|
||||
m_currentPlan(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
FFTWEngine::~FFTWEngine()
|
||||
{
|
||||
freeAll();
|
||||
}
|
||||
|
||||
void FFTWEngine::configure(int n, bool inverse)
|
||||
{
|
||||
for(Plans::const_iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
if(((*it)->n == n) && ((*it)->inverse == inverse)) {
|
||||
m_currentPlan = *it;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_currentPlan = new Plan;
|
||||
m_currentPlan->n = n;
|
||||
m_currentPlan->inverse = inverse;
|
||||
m_currentPlan->in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
|
||||
m_currentPlan->out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * n);
|
||||
QTime t;
|
||||
t.start();
|
||||
m_globalPlanMutex.lock();
|
||||
m_currentPlan->plan = fftwf_plan_dft_1d(n, m_currentPlan->in, m_currentPlan->out, inverse ? FFTW_BACKWARD : FFTW_FORWARD, FFTW_PATIENT);
|
||||
m_globalPlanMutex.unlock();
|
||||
qDebug("FFT: creating FFTW plan (n=%d,%s) took %dms", n, inverse ? "inverse" : "forward", t.elapsed());
|
||||
m_plans.push_back(m_currentPlan);
|
||||
}
|
||||
|
||||
void FFTWEngine::transform()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
fftwf_execute(m_currentPlan->plan);
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::in()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->in);
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
Complex* FFTWEngine::out()
|
||||
{
|
||||
if(m_currentPlan != NULL)
|
||||
return reinterpret_cast<Complex*>(m_currentPlan->out);
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
QMutex FFTWEngine::m_globalPlanMutex;
|
||||
|
||||
void FFTWEngine::freeAll()
|
||||
{
|
||||
for(Plans::iterator it = m_plans.begin(); it != m_plans.end(); ++it) {
|
||||
fftwf_destroy_plan((*it)->plan);
|
||||
fftwf_free((*it)->in);
|
||||
fftwf_free((*it)->out);
|
||||
delete *it;
|
||||
}
|
||||
m_plans.clear();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/fftwindow.h"
|
||||
|
||||
void FFTWindow::create(Function function, int n)
|
||||
{
|
||||
Real (*wFunc)(Real n, Real i);
|
||||
|
||||
m_window.clear();
|
||||
|
||||
switch(function) {
|
||||
case Flattop:
|
||||
wFunc = flatTop;
|
||||
break;
|
||||
|
||||
case Bartlett:
|
||||
wFunc = bartlett;
|
||||
break;
|
||||
|
||||
case BlackmanHarris:
|
||||
wFunc = blackmanHarris;
|
||||
break;
|
||||
|
||||
case Hamming:
|
||||
wFunc = hamming;
|
||||
break;
|
||||
|
||||
case Hanning:
|
||||
wFunc = hanning;
|
||||
break;
|
||||
|
||||
case Rectangle:
|
||||
default:
|
||||
wFunc = rectangle;
|
||||
break;
|
||||
}
|
||||
|
||||
for(int i = 0; i < n; i++)
|
||||
m_window.push_back(wFunc(n, i));
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const std::vector<Real>& in, std::vector<Real>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const std::vector<Complex>& in, std::vector<Complex>* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
(*out)[i] = in[i] * m_window[i];
|
||||
}
|
||||
|
||||
void FFTWindow::apply(const Complex* in, Complex* out)
|
||||
{
|
||||
for(size_t i = 0; i < m_window.size(); i++)
|
||||
out[i] = in[i] * m_window[i];
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include "dsp/interpolator.h"
|
||||
|
||||
static std::vector<Real> createPolyphaseLowPass(
|
||||
int phaseSteps,
|
||||
double gain,
|
||||
double sampleRateHz,
|
||||
double cutoffFreqHz,
|
||||
double transitionWidthHz,
|
||||
double oobAttenuationdB)
|
||||
{
|
||||
int ntaps = (int)(oobAttenuationdB * sampleRateHz / (22.0 * transitionWidthHz));
|
||||
if((ntaps % 2) != 0)
|
||||
ntaps++;
|
||||
ntaps *= phaseSteps;
|
||||
|
||||
std::vector<float> taps(ntaps);
|
||||
std::vector<float> window(ntaps);
|
||||
|
||||
for(int n = 0; n < ntaps; n++)
|
||||
window[n] = 0.54 - 0.46 * cos ((2 * M_PI * n) / (ntaps - 1));
|
||||
|
||||
int M = (ntaps - 1) / 2;
|
||||
double fwT0 = 2 * M_PI * cutoffFreqHz / sampleRateHz;
|
||||
for(int n = -M; n <= M; n++) {
|
||||
if(n == 0) taps[n + M] = fwT0 / M_PI * window[n + M];
|
||||
else taps[n + M] = sin (n * fwT0) / (n * M_PI) * window[n + M];
|
||||
}
|
||||
|
||||
double max = taps[0 + M];
|
||||
for(int n = 1; n <= M; n++)
|
||||
max += 2.0 * taps[n + M];
|
||||
|
||||
gain /= max;
|
||||
|
||||
for(int i = 0; i < ntaps; i++)
|
||||
taps[i] *= gain;
|
||||
|
||||
return taps;
|
||||
}
|
||||
|
||||
Interpolator::Interpolator() :
|
||||
m_taps(NULL),
|
||||
m_alignedTaps(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Interpolator::~Interpolator()
|
||||
{
|
||||
free();
|
||||
}
|
||||
|
||||
void Interpolator::create(int phaseSteps, double sampleRate, double cutoff)
|
||||
{
|
||||
free();
|
||||
|
||||
std::vector<Real> taps = createPolyphaseLowPass(
|
||||
phaseSteps, // number of polyphases
|
||||
1.0, // gain
|
||||
phaseSteps * sampleRate, // sampling frequency
|
||||
cutoff, // hz beginning of transition band
|
||||
sampleRate / 5.0, // hz width of transition band
|
||||
20.0); // out of band attenuation
|
||||
|
||||
// init state
|
||||
m_ptr = 0;
|
||||
m_nTaps = taps.size() / phaseSteps;
|
||||
m_phaseSteps = phaseSteps;
|
||||
m_samples.resize(m_nTaps + 2);
|
||||
for(int i = 0; i < m_nTaps + 2; i++)
|
||||
m_samples[i] = 0;
|
||||
|
||||
// reorder into polyphase
|
||||
std::vector<Real> polyphase(taps.size());
|
||||
for(int phase = 0; phase < phaseSteps; phase++) {
|
||||
for(int i = 0; i < m_nTaps; i++)
|
||||
polyphase[phase * m_nTaps + i] = taps[i * phaseSteps + phase];
|
||||
}
|
||||
|
||||
// normalize phase filters
|
||||
for(int phase = 0; phase < phaseSteps; phase++) {
|
||||
Real sum = 0;
|
||||
for(int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++)
|
||||
sum += polyphase[i];
|
||||
for(int i = phase * m_nTaps; i < phase * m_nTaps + m_nTaps; i++)
|
||||
polyphase[i] /= sum;
|
||||
}
|
||||
|
||||
// move taps around to match sse storage requirements
|
||||
m_taps = new float[2 * taps.size() + 8];
|
||||
for(int i = 0; i < 2 * taps.size() + 8; ++i)
|
||||
m_taps[i] = 0;
|
||||
m_alignedTaps = (float*)((((quint64)m_taps) + 15) & ~15);
|
||||
for(int i = 0; i < taps.size(); ++i) {
|
||||
m_alignedTaps[2 * i + 0] = polyphase[i];
|
||||
m_alignedTaps[2 * i + 1] = polyphase[i];
|
||||
}
|
||||
m_taps2 = new float[2 * taps.size() + 8];
|
||||
for(int i = 0; i < 2 * taps.size() + 8; ++i)
|
||||
m_taps2[i] = 0;
|
||||
m_alignedTaps2 = (float*)((((quint64)m_taps2) + 15) & ~15);
|
||||
for(int i = 1; i < taps.size(); ++i) {
|
||||
m_alignedTaps2[2 * (i - 1) + 0] = polyphase[i];
|
||||
m_alignedTaps2[2 * (i - 1) + 1] = polyphase[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Interpolator::free()
|
||||
{
|
||||
if(m_taps != NULL) {
|
||||
delete[] m_taps;
|
||||
m_taps = NULL;
|
||||
m_alignedTaps = NULL;
|
||||
delete[] m_taps2;
|
||||
m_taps2 = NULL;
|
||||
m_alignedTaps2 = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "dsp/inthalfbandfilter.h"
|
||||
|
||||
IntHalfbandFilter::IntHalfbandFilter()
|
||||
{
|
||||
for(int i = 0; i < HB_FILTERORDER + 1; i++) {
|
||||
m_samples[i][0] = 0;
|
||||
m_samples[i][1] = 0;
|
||||
}
|
||||
m_ptr = 0;
|
||||
m_state = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "dsp/kissengine.h"
|
||||
|
||||
void KissEngine::configure(int n, bool inverse)
|
||||
{
|
||||
m_fft.configure(n, inverse);
|
||||
if(n > m_in.size())
|
||||
m_in.resize(n);
|
||||
if(n > m_out.size())
|
||||
m_out.resize(n);
|
||||
}
|
||||
|
||||
void KissEngine::transform()
|
||||
{
|
||||
m_fft.transform(&m_in[0], &m_out[0]);
|
||||
}
|
||||
|
||||
Complex* KissEngine::in()
|
||||
{
|
||||
return &m_in[0];
|
||||
}
|
||||
|
||||
Complex* KissEngine::out()
|
||||
{
|
||||
return &m_out[0];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include <QtGlobal>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include "dsp/lowpass.h"
|
||||
@@ -0,0 +1 @@
|
||||
#include "dsp/movingaverage.h"
|
||||
@@ -0,0 +1,70 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <stdio.h>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include "dsp/nco.h"
|
||||
|
||||
Real NCO::m_table[NCO::TableSize];
|
||||
bool NCO::m_tableInitialized = false;
|
||||
|
||||
void NCO::initTable()
|
||||
{
|
||||
if(m_tableInitialized)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < TableSize; i++)
|
||||
m_table[i] = cos((2.0 * M_PI * i) / TableSize);
|
||||
|
||||
m_tableInitialized = true;
|
||||
}
|
||||
|
||||
NCO::NCO()
|
||||
{
|
||||
initTable();
|
||||
m_phase = 0;
|
||||
}
|
||||
|
||||
void NCO::setFreq(Real freq, Real sampleRate)
|
||||
{
|
||||
m_phaseIncrement = (freq * TableSize) / sampleRate;
|
||||
qDebug("NCO phase inc %d", m_phaseIncrement);
|
||||
}
|
||||
|
||||
float NCO::next()
|
||||
{
|
||||
m_phase += m_phaseIncrement;
|
||||
while(m_phase >= TableSize)
|
||||
m_phase -= TableSize;
|
||||
while(m_phase < 0)
|
||||
m_phase += TableSize;
|
||||
|
||||
return m_table[m_phase];
|
||||
}
|
||||
|
||||
Complex NCO::nextIQ()
|
||||
{
|
||||
m_phase += m_phaseIncrement;
|
||||
while(m_phase >= TableSize)
|
||||
m_phase -= TableSize;
|
||||
while(m_phase < 0)
|
||||
m_phase += TableSize;
|
||||
|
||||
return Complex(m_table[m_phase], -m_table[(m_phase + TableSize / 4) % TableSize]);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "pidcontroller.h"
|
||||
|
||||
PIDController::PIDController() :
|
||||
m_p(0.0),
|
||||
m_i(0.0),
|
||||
m_d(0.0),
|
||||
m_int(0.0),
|
||||
m_diff(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void PIDController::setup(Real p, Real i, Real d)
|
||||
{
|
||||
m_p = p;
|
||||
m_i = i;
|
||||
m_d = d;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef INCLUDE_PIDCONTROLLER_H
|
||||
#define INCLUDE_PIDCONTROLLER_H
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
|
||||
class PIDController {
|
||||
private:
|
||||
Real m_p;
|
||||
Real m_i;
|
||||
Real m_d;
|
||||
Real m_int;
|
||||
Real m_diff;
|
||||
|
||||
public:
|
||||
PIDController();
|
||||
|
||||
void setup(Real p, Real i, Real d);
|
||||
|
||||
Real feed(Real v)
|
||||
{
|
||||
m_int += v * m_i;
|
||||
Real d = m_d * (m_diff - v);
|
||||
m_diff = v;
|
||||
return (v * m_p) + m_int + d;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PIDCONTROLLER_H
|
||||
@@ -0,0 +1,231 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/samplefifo.h"
|
||||
|
||||
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
||||
|
||||
void SampleFifo::create(uint s)
|
||||
{
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
|
||||
m_data.resize(s);
|
||||
m_size = m_data.size();
|
||||
|
||||
if(m_size != s)
|
||||
qCritical("SampleFifo: out of memory");
|
||||
}
|
||||
|
||||
SampleFifo::SampleFifo(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_data()
|
||||
{
|
||||
m_suppressed = -1;
|
||||
m_size = 0;
|
||||
m_fill = 0;
|
||||
m_head = 0;
|
||||
m_tail = 0;
|
||||
}
|
||||
|
||||
SampleFifo::SampleFifo(int size, QObject* parent) :
|
||||
QObject(parent),
|
||||
m_data()
|
||||
{
|
||||
m_suppressed = -1;
|
||||
|
||||
create(size);
|
||||
}
|
||||
|
||||
SampleFifo::~SampleFifo()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
bool SampleFifo::setSize(int size)
|
||||
{
|
||||
create(size);
|
||||
|
||||
return m_data.size() == (uint)size;
|
||||
}
|
||||
|
||||
uint SampleFifo::write(const quint8* data, uint count)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
const Sample* begin = (const Sample*)data;
|
||||
count /= 4;
|
||||
|
||||
total = MIN(count, m_size - m_fill);
|
||||
if(total < count) {
|
||||
if(m_suppressed < 0) {
|
||||
m_suppressed = 0;
|
||||
m_msgRateTimer.start();
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
} else {
|
||||
if(m_msgRateTimer.elapsed() > 2500) {
|
||||
qCritical("SampleFifo: %u messages dropped", m_suppressed);
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
m_suppressed = -1;
|
||||
} else {
|
||||
m_suppressed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_tail);
|
||||
std::copy(begin, begin + len, m_data.begin() + m_tail);
|
||||
m_tail += len;
|
||||
m_tail %= m_size;
|
||||
m_fill += len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
if(m_fill > 0)
|
||||
emit dataReady();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::write(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint count = end - begin;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
|
||||
total = MIN(count, m_size - m_fill);
|
||||
if(total < count) {
|
||||
if(m_suppressed < 0) {
|
||||
m_suppressed = 0;
|
||||
m_msgRateTimer.start();
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
} else {
|
||||
if(m_msgRateTimer.elapsed() > 2500) {
|
||||
qCritical("SampleFifo: %u messages dropped", m_suppressed);
|
||||
qCritical("SampleFifo: overflow - dropping %u samples", count - total);
|
||||
m_suppressed = -1;
|
||||
} else {
|
||||
m_suppressed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_tail);
|
||||
std::copy(begin, begin + len, m_data.begin() + m_tail);
|
||||
m_tail += len;
|
||||
m_tail %= m_size;
|
||||
m_fill += len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
if(m_fill > 0)
|
||||
emit dataReady();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::read(SampleVector::iterator begin, SampleVector::iterator end)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint count = end - begin;
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
|
||||
total = MIN(count, m_fill);
|
||||
if(total < count)
|
||||
qCritical("SampleFifo: underflow - missing %u samples", count - total);
|
||||
|
||||
remaining = total;
|
||||
while(remaining > 0) {
|
||||
len = MIN(remaining, m_size - m_head);
|
||||
std::copy(m_data.begin() + m_head, m_data.begin() + m_head + len, begin);
|
||||
m_head += len;
|
||||
m_head %= m_size;
|
||||
m_fill -= len;
|
||||
begin += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::readBegin(uint count,
|
||||
SampleVector::iterator* part1Begin, SampleVector::iterator* part1End,
|
||||
SampleVector::iterator* part2Begin, SampleVector::iterator* part2End)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
uint total;
|
||||
uint remaining;
|
||||
uint len;
|
||||
uint head = m_head;
|
||||
|
||||
total = MIN(count, m_fill);
|
||||
if(total < count)
|
||||
qCritical("SampleFifo: underflow - missing %u samples", count - total);
|
||||
|
||||
remaining = total;
|
||||
if(remaining > 0) {
|
||||
len = MIN(remaining, m_size - head);
|
||||
*part1Begin = m_data.begin() + head;
|
||||
*part1End = m_data.begin() + head + len;
|
||||
head += len;
|
||||
head %= m_size;
|
||||
remaining -= len;
|
||||
} else {
|
||||
*part1Begin = m_data.end();
|
||||
*part1End = m_data.end();
|
||||
}
|
||||
if(remaining > 0) {
|
||||
len = MIN(remaining, m_size - head);
|
||||
*part2Begin = m_data.begin() + head;
|
||||
*part2End = m_data.begin() + head + len;
|
||||
} else {
|
||||
*part2Begin = m_data.end();
|
||||
*part2End = m_data.end();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
uint SampleFifo::readCommit(uint count)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if(count > m_fill) {
|
||||
qCritical("SampleFifo: cannot commit more than available samples");
|
||||
count = m_fill;
|
||||
}
|
||||
m_head = (m_head + count) % m_size;
|
||||
m_fill -= count;
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "dsp/samplesink.h"
|
||||
|
||||
SampleSink::SampleSink()
|
||||
{
|
||||
}
|
||||
|
||||
SampleSink::~SampleSink()
|
||||
{
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include "samplesink.h"
|
||||
|
||||
SampleSink::SampleSink() :
|
||||
m_sinkBuffer(),
|
||||
m_sinkBufferFill(0)
|
||||
{
|
||||
}
|
||||
|
||||
void SampleSink::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end)
|
||||
{
|
||||
size_t wus = workUnitSize();
|
||||
|
||||
// make sure our buffer is big enough for at least one work unit
|
||||
if(m_sinkBuffer.size() < wus)
|
||||
m_sinkBuffer.resize(wus);
|
||||
|
||||
while(begin < end) {
|
||||
// if the buffer contains something, keep filling it until it contains one complete work unit
|
||||
if((m_sinkBufferFill > 0) && (m_sinkBufferFill < wus)) {
|
||||
// check number if missing samples, but don't copy more than we have
|
||||
size_t len = wus - m_sinkBufferFill;
|
||||
if(len > (size_t)(end - begin))
|
||||
len = end - begin;
|
||||
// copy
|
||||
std::copy(begin, begin + len, m_sinkBuffer.begin() + m_sinkBufferFill);
|
||||
// adjust pointers
|
||||
m_sinkBufferFill += len;
|
||||
begin += len;
|
||||
}
|
||||
|
||||
// if one complete work unit is in the buffer, feed it to the worker
|
||||
if(m_sinkBufferFill >= wus) {
|
||||
size_t done = 0;
|
||||
while(m_sinkBufferFill - done >= wus)
|
||||
done += work(m_sinkBuffer.begin() + done, m_sinkBuffer.begin() + done + wus);
|
||||
// now copy the remaining data to the front of the buffer (should be zero)
|
||||
if(m_sinkBufferFill - done > 0) {
|
||||
qDebug("error in SampleSink buffer management");
|
||||
std::copy(m_sinkBuffer.begin() + done, m_sinkBuffer.begin() + m_sinkBufferFill, m_sinkBuffer.begin());
|
||||
}
|
||||
m_sinkBufferFill -= done;
|
||||
}
|
||||
|
||||
// if no remainder from last run is buffered and we have at least one work unit left, feed the data to the worker
|
||||
if(m_sinkBufferFill == 0) {
|
||||
while((size_t)(end - begin) > wus)
|
||||
begin += work(begin, begin + wus);
|
||||
// copy any remaining data to the buffer
|
||||
std::copy(begin, end, m_sinkBuffer.begin());
|
||||
m_sinkBufferFill = end - begin;
|
||||
begin += m_sinkBufferFill;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
||||
// written by Christian Daniel //
|
||||
// //
|
||||
// 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 as version 3 of the License, or //
|
||||
// //
|
||||
// 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 V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/samplesource/samplesource.h"
|
||||
#include "util/simpleserializer.h"
|
||||
|
||||
SampleSource::GeneralSettings::GeneralSettings() :
|
||||
m_centerFrequency(100000000)
|
||||
{
|
||||
}
|
||||
|
||||
void SampleSource::GeneralSettings::resetToDefaults()
|
||||
{
|
||||
m_centerFrequency = 100000000;
|
||||
}
|
||||
|
||||
QByteArray SampleSource::GeneralSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeU64(1, m_centerFrequency);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool SampleSource::GeneralSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if(!d.isValid()) {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(d.getVersion() == 1) {
|
||||
d.readU64(1, &m_centerFrequency, 100000000);
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SampleSource::SampleSource(MessageQueue* guiMessageQueue) :
|
||||
m_sampleFifo(),
|
||||
m_guiMessageQueue(guiMessageQueue)
|
||||
{
|
||||
}
|
||||
|
||||
SampleSource::~SampleSource()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "dsp/scopevis.h"
|
||||
#include "gui/glscope.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
ScopeVis::ScopeVis(GLScope* glScope) :
|
||||
m_glScope(glScope),
|
||||
m_trace(100000),
|
||||
m_fill(0),
|
||||
m_triggerState(Untriggered),
|
||||
m_triggerChannel(TriggerFreeRun),
|
||||
m_triggerLevelHigh(0.01 * 32768),
|
||||
m_triggerLevelLow(0.01 * 32768 - 1024),
|
||||
m_sampleRate(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ScopeVis::configure(MessageQueue* msgQueue, TriggerChannel triggerChannel, Real triggerLevelHigh, Real triggerLevelLow)
|
||||
{
|
||||
Message* cmd = DSPConfigureScopeVis::create(triggerChannel, triggerLevelHigh, triggerLevelLow);
|
||||
cmd->submit(msgQueue, this);
|
||||
}
|
||||
|
||||
void ScopeVis::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
while(begin < end) {
|
||||
if(m_triggerChannel == TriggerChannelI) {
|
||||
if(m_triggerState == Untriggered) {
|
||||
while(begin < end) {
|
||||
if(begin->real() >= m_triggerLevelHigh) {
|
||||
m_triggerState = Triggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == Triggered) {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
m_triggerState = WaitForReset;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == WaitForReset) {
|
||||
while(begin < end) {
|
||||
if(begin->real() < m_triggerLevelLow) {
|
||||
m_triggerState = Untriggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
} else if(m_triggerChannel == TriggerChannelQ) {
|
||||
if(m_triggerState == Untriggered) {
|
||||
while(begin < end) {
|
||||
if(begin->imag() >= m_triggerLevelHigh) {
|
||||
m_triggerState = Triggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == Triggered) {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
m_triggerState = WaitForReset;
|
||||
}
|
||||
}
|
||||
if(m_triggerState == WaitForReset) {
|
||||
while(begin < end) {
|
||||
if(begin->imag() < m_triggerLevelLow) {
|
||||
m_triggerState = Untriggered;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int count = end - begin;
|
||||
if(count > (int)(m_trace.size() - m_fill))
|
||||
count = m_trace.size() - m_fill;
|
||||
std::vector<Complex>::iterator it = m_trace.begin() + m_fill;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
++begin;
|
||||
}
|
||||
m_fill += count;
|
||||
if(m_fill >= m_trace.size()) {
|
||||
m_glScope->newTrace(m_trace, m_sampleRate);
|
||||
m_fill = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScopeVis::start()
|
||||
{
|
||||
}
|
||||
|
||||
void ScopeVis::stop()
|
||||
{
|
||||
}
|
||||
|
||||
bool ScopeVis::handleMessage(Message* message)
|
||||
{
|
||||
if(DSPSignalNotification::match(message)) {
|
||||
DSPSignalNotification* signal = (DSPSignalNotification*)message;
|
||||
m_sampleRate = signal->getSampleRate();
|
||||
message->completed();
|
||||
return true;
|
||||
} else if(DSPConfigureScopeVis::match(message)) {
|
||||
DSPConfigureScopeVis* conf = (DSPConfigureScopeVis*)message;
|
||||
m_triggerState = Untriggered;
|
||||
m_triggerChannel = (TriggerChannel)conf->getTriggerChannel();
|
||||
m_triggerLevelHigh = conf->getTriggerLevelHigh() * 32767;
|
||||
m_triggerLevelLow = conf->getTriggerLevelLow() * 32767;
|
||||
message->completed();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#define MAX_FFT_SIZE 4096
|
||||
|
||||
#ifdef _WIN32
|
||||
double log2f(double n)
|
||||
{
|
||||
return log(n) / log(2.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
SpectrumVis::SpectrumVis(GLSpectrum* glSpectrum) :
|
||||
SampleSink(),
|
||||
m_fft(FFTEngine::create()),
|
||||
m_fftBuffer(MAX_FFT_SIZE),
|
||||
m_logPowerSpectrum(MAX_FFT_SIZE),
|
||||
m_fftBufferFill(0),
|
||||
m_glSpectrum(glSpectrum)
|
||||
{
|
||||
handleConfigure(1024, 10, FFTWindow::BlackmanHarris);
|
||||
}
|
||||
|
||||
SpectrumVis::~SpectrumVis()
|
||||
{
|
||||
delete m_fft;
|
||||
}
|
||||
|
||||
void SpectrumVis::configure(MessageQueue* msgQueue, int fftSize, int overlapPercent, FFTWindow::Function window)
|
||||
{
|
||||
Message* cmd = DSPConfigureSpectrumVis::create(fftSize, overlapPercent, window);
|
||||
cmd->submit(msgQueue, this);
|
||||
}
|
||||
|
||||
void SpectrumVis::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
// if no visualisation is set, send the samples to /dev/null
|
||||
if(m_glSpectrum == NULL)
|
||||
return;
|
||||
|
||||
while(begin < end) {
|
||||
size_t todo = end - begin;
|
||||
size_t samplesNeeded = m_refillSize - m_fftBufferFill;
|
||||
|
||||
if(todo >= samplesNeeded) {
|
||||
// fill up the buffer
|
||||
std::vector<Complex>::iterator it = m_fftBuffer.begin() + m_fftBufferFill;
|
||||
for(size_t i = 0; i < samplesNeeded; ++i, ++begin)
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
|
||||
// apply fft window (and copy from m_fftBuffer to m_fftIn)
|
||||
m_window.apply(&m_fftBuffer[0], m_fft->in());
|
||||
|
||||
// calculate FFT
|
||||
m_fft->transform();
|
||||
|
||||
// extract power spectrum and reorder buckets
|
||||
Real ofs = 20.0f * log10f(1.0f / m_fftSize);
|
||||
Real mult = (10.0f / log2f(10.0f));
|
||||
const Complex* fftOut = m_fft->out();
|
||||
for(size_t i = 0; i < m_fftSize; i++) {
|
||||
Complex c = fftOut[((i + (m_fftSize >> 1)) & (m_fftSize - 1))];
|
||||
Real v = c.real() * c.real() + c.imag() * c.imag();
|
||||
v = mult * log2f(v) + ofs;
|
||||
m_logPowerSpectrum[i] = v;
|
||||
}
|
||||
|
||||
// send new data to visualisation
|
||||
m_glSpectrum->newSpectrum(m_logPowerSpectrum, m_fftSize);
|
||||
|
||||
// advance buffer respecting the fft overlap factor
|
||||
std::copy(m_fftBuffer.begin() + m_refillSize, m_fftBuffer.end(), m_fftBuffer.begin());
|
||||
|
||||
// start over
|
||||
m_fftBufferFill = m_overlapSize;
|
||||
} else {
|
||||
// not enough samples for FFT - just fill in new data and return
|
||||
for(std::vector<Complex>::iterator it = m_fftBuffer.begin() + m_fftBufferFill; begin < end; ++begin)
|
||||
*it++ = Complex(begin->real() / 32768.0, begin->imag() / 32768.0);
|
||||
m_fftBufferFill += todo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::start()
|
||||
{
|
||||
}
|
||||
|
||||
void SpectrumVis::stop()
|
||||
{
|
||||
}
|
||||
|
||||
bool SpectrumVis::handleMessage(Message* message)
|
||||
{
|
||||
if(DSPConfigureSpectrumVis::match(message)) {
|
||||
DSPConfigureSpectrumVis* conf = (DSPConfigureSpectrumVis*)message;
|
||||
handleConfigure(conf->getFFTSize(), conf->getOverlapPercent(), conf->getWindow());
|
||||
message->completed();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumVis::handleConfigure(int fftSize, int overlapPercent, FFTWindow::Function window)
|
||||
{
|
||||
if(fftSize > MAX_FFT_SIZE)
|
||||
fftSize = MAX_FFT_SIZE;
|
||||
else if(fftSize < 64)
|
||||
fftSize = 64;
|
||||
if(overlapPercent > 100)
|
||||
m_overlapPercent = 100;
|
||||
else if(overlapPercent < 0)
|
||||
m_overlapPercent = 0;
|
||||
|
||||
m_fftSize = fftSize;
|
||||
m_overlapPercent = overlapPercent;
|
||||
m_fft->configure(m_fftSize, false);
|
||||
m_window.create(window, m_fftSize);
|
||||
m_overlapSize = (m_fftSize * m_overlapPercent) / 100;
|
||||
m_refillSize = m_fftSize - m_overlapSize;
|
||||
m_fftBufferFill = m_overlapSize;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#include <QThread>
|
||||
#include "dsp/threadedsamplesink.h"
|
||||
#include "util/message.h"
|
||||
|
||||
ThreadedSampleSink::ThreadedSampleSink(SampleSink* sampleSink) :
|
||||
m_thread(new QThread),
|
||||
m_sampleSink(sampleSink)
|
||||
{
|
||||
moveToThread(m_thread);
|
||||
connect(m_thread, SIGNAL(started()), this, SLOT(threadStarted()));
|
||||
connect(m_thread, SIGNAL(finished()), this, SLOT(threadFinished()));
|
||||
|
||||
m_messageQueue.moveToThread(m_thread);
|
||||
connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()));
|
||||
|
||||
m_sampleFifo.moveToThread(m_thread);
|
||||
connect(&m_sampleFifo, SIGNAL(dataReady()), this, SLOT(handleData()));
|
||||
m_sampleFifo.setSize(262144);
|
||||
|
||||
sampleSink->moveToThread(m_thread);
|
||||
}
|
||||
|
||||
ThreadedSampleSink::~ThreadedSampleSink()
|
||||
{
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
|
||||
{
|
||||
Q_UNUSED(firstOfBurst);
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::start()
|
||||
{
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::stop()
|
||||
{
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
m_sampleFifo.readCommit(m_sampleFifo.fill());
|
||||
}
|
||||
|
||||
bool ThreadedSampleSink::handleMessage(Message* cmd)
|
||||
{
|
||||
// called from other thread
|
||||
m_messageQueue.submit(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::handleData()
|
||||
{
|
||||
bool firstOfBurst = true;
|
||||
|
||||
while((m_sampleFifo.fill() > 0) && (m_messageQueue.countPending() == 0)) {
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if(part1begin != part1end) {
|
||||
// handle data
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
// handle data
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->feed(part1begin, part1end, firstOfBurst);
|
||||
firstOfBurst = false;
|
||||
}
|
||||
|
||||
// adjust FIFO pointers
|
||||
m_sampleFifo.readCommit(count);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::handleMessages()
|
||||
{
|
||||
Message* message;
|
||||
while((message = m_messageQueue.accept()) != NULL) {
|
||||
qDebug("CMD: %s", message->getIdentifier());
|
||||
if(m_sampleSink != NULL) {
|
||||
if(!m_sampleSink->handleMessage(message))
|
||||
message->completed();
|
||||
} else {
|
||||
message->completed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::threadStarted()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->start();
|
||||
}
|
||||
|
||||
void ThreadedSampleSink::threadFinished()
|
||||
{
|
||||
if(m_sampleSink != NULL)
|
||||
m_sampleSink->stop();
|
||||
}
|
||||
Reference in New Issue
Block a user