1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-08 00:44:48 -04:00

git clone git://git.osmocom.org/sdrangelove.git

This commit is contained in:
Hexameron
2014-05-18 16:52:39 +01:00
commit 7d3bfb26fc
203 changed files with 27958 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "audio/audiodeviceinfo.h"
AudioDeviceInfo::AudioDeviceInfo()
{
}
int AudioDeviceInfo::match(const QString& api, const QString device) const
{
// nothing found - fall back to default
return 0;
}
+218
View File
@@ -0,0 +1,218 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <string.h>
#include <QTime>
#include "audio/audiofifo.h"
#define MIN(x, y) ((x) < (y) ? (x) : (y))
AudioFifo::AudioFifo() :
m_fifo(NULL)
{
m_size = 0;
m_fill = 0;
m_head = 0;
m_tail = 0;
}
AudioFifo::AudioFifo(uint sampleSize, uint numSamples) :
m_fifo(NULL)
{
QMutexLocker mutexLocker(&m_mutex);
create(sampleSize, numSamples);
}
AudioFifo::~AudioFifo()
{
QMutexLocker mutexLocker(&m_mutex);
if(m_fifo != NULL) {
delete[] m_fifo;
m_fifo = NULL;
}
m_writeWaitCondition.wakeOne();
m_readWaitCondition.wakeOne();
m_size = 0;
}
bool AudioFifo::setSize(uint sampleSize, uint numSamples)
{
QMutexLocker mutexLocker(&m_mutex);
return create(sampleSize, numSamples);
}
uint AudioFifo::write(const quint8* data, uint numSamples, int timeout)
{
QTime time;
uint total;
uint remaining;
uint copyLen;
if(m_fifo == NULL)
return 0;
time.start();
m_mutex.lock();
if(timeout == 0)
total = MIN(numSamples, m_size - m_fill);
else total = numSamples;
remaining = total;
while(remaining > 0) {
if(isFull()) {
if(time.elapsed() < timeout) {
m_writeWaitLock.lock();
m_mutex.unlock();
int ms = timeout - time.elapsed();
if(ms < 1)
ms = 1;
bool ok = m_writeWaitCondition.wait(&m_writeWaitLock, ms);
m_writeWaitLock.unlock();
if(!ok)
return total - remaining;
m_mutex.lock();
if(m_fifo == NULL) {
m_mutex.unlock();
return 0;
}
} else {
m_mutex.unlock();
return total - remaining;
}
}
copyLen = MIN(remaining, m_size - m_fill);
copyLen = MIN(copyLen, m_size - m_tail);
memcpy(m_fifo + (m_tail * m_sampleSize), data, copyLen * m_sampleSize);
m_tail += copyLen;
m_tail %= m_size;
m_fill += copyLen;
data += copyLen * m_sampleSize;
remaining -= copyLen;
m_readWaitCondition.wakeOne();
}
m_mutex.unlock();
return total;
}
uint AudioFifo::read(quint8* data, uint numSamples, int timeout)
{
QTime time;
uint total;
uint remaining;
uint copyLen;
if(m_fifo == NULL)
return 0;
time.start();
m_mutex.lock();
if(timeout == 0)
total = MIN(numSamples, m_fill);
else total = numSamples;
remaining = total;
while(remaining > 0) {
if(isEmpty()) {
if(time.elapsed() < timeout) {
m_readWaitLock.lock();
m_mutex.unlock();
int ms = timeout - time.elapsed();
if(ms < 1)
ms = 1;
bool ok = m_readWaitCondition.wait(&m_readWaitLock, ms);
m_readWaitLock.unlock();
if(!ok)
return total - remaining;
m_mutex.lock();
if(m_fifo == NULL) {
m_mutex.unlock();
return 0;
}
} else {
m_mutex.unlock();
return total - remaining;
}
}
copyLen = MIN(remaining, m_fill);
copyLen = MIN(copyLen, m_size - m_head);
memcpy(data, m_fifo + (m_head * m_sampleSize), copyLen * m_sampleSize);
m_head += copyLen;
m_head %= m_size;
m_fill -= copyLen;
data += copyLen * m_sampleSize;
remaining -= copyLen;
m_writeWaitCondition.wakeOne();
}
m_mutex.unlock();
return total;
}
uint AudioFifo::drain(uint numSamples)
{
QMutexLocker mutexLocker(&m_mutex);
if(numSamples > m_fill)
numSamples = m_fill;
m_head = (m_head + numSamples) % m_size;
m_fill -= numSamples;
m_writeWaitCondition.wakeOne();
return numSamples;
}
void AudioFifo::clear()
{
QMutexLocker mutexLocker(&m_mutex);
m_fill = 0;
m_head = 0;
m_tail = 0;
m_writeWaitCondition.wakeOne();
}
bool AudioFifo::create(uint sampleSize, uint numSamples)
{
if(m_fifo != NULL) {
delete[] m_fifo;
m_fifo = NULL;
}
m_sampleSize = sampleSize;
m_size = 0;
m_fill = 0;
m_head = 0;
m_tail = 0;
if((m_fifo = new qint8[numSamples * m_sampleSize]) == NULL) {
qDebug("out of memory");
return false;
}
m_size = numSamples;
return true;
}
+172
View File
@@ -0,0 +1,172 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <string.h>
#include <QAudioFormat>
#include <QAudioDeviceInfo>
#include <QAudioOutput>
#include "audio/audiooutput.h"
#include "audio/audiofifo.h"
AudioOutput::AudioOutput() :
m_mutex(),
m_audioOutput(NULL),
m_audioFifos()
{
}
AudioOutput::~AudioOutput()
{
stop();
QMutexLocker mutexLocker(&m_mutex);
for(AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it)
delete *it;
m_audioFifos.clear();
}
bool AudioOutput::start(int device, int rate)
{
QMutexLocker mutexLocker(&m_mutex);
Q_UNUSED(device);
Q_UNUSED(rate);
QAudioFormat format;
QAudioDeviceInfo devInfo(QAudioDeviceInfo::defaultOutputDevice());
format.setSampleRate(41000);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if(!devInfo.isFormatSupported(format)) {
qDebug("default format not supported - try to use nearest");
format = devInfo.nearestFormat(format);
}
if(format.sampleSize() != 16) {
qDebug("audio device doesn't support 16 bit samples (%s)", qPrintable(devInfo.defaultOutputDevice().deviceName()));
return false;
}
m_audioOutput = new QAudioOutput(format);
QIODevice::open(QIODevice::ReadOnly);
m_audioOutput->start(this);
return true;
}
void AudioOutput::stop()
{
QMutexLocker mutexLocker(&m_mutex);
if(m_audioOutput != NULL) {
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = NULL;
}
QIODevice::close();
}
void AudioOutput::addFifo(AudioFifo* audioFifo)
{
QMutexLocker mutexLocker(&m_mutex);
m_audioFifos.push_back(audioFifo);
}
void AudioOutput::removeFifo(AudioFifo* audioFifo)
{
QMutexLocker mutexLocker(&m_mutex);
m_audioFifos.remove(audioFifo);
}
bool AudioOutput::open(OpenMode mode)
{
Q_UNUSED(mode);
return false;
}
qint64 AudioOutput::readData(char* data, qint64 maxLen)
{
if(maxLen == 0)
return 0;
QMutexLocker mutexLocker(&m_mutex);
maxLen -= maxLen % 4;
int framesPerBuffer = maxLen / 4;
if((int)m_mixBuffer.size() < framesPerBuffer * 2) {
m_mixBuffer.resize(framesPerBuffer * 2); // allocate 2 qint32 per frame (stereo)
if(m_mixBuffer.size() != framesPerBuffer * 2)
return 0;
}
memset(&m_mixBuffer[0], 0x00, 2 * framesPerBuffer * sizeof(m_mixBuffer[0])); // start with silence
// sum up a block from all fifos
for(AudioFifos::iterator it = m_audioFifos.begin(); it != m_audioFifos.end(); ++it) {
// use outputBuffer as temp - yes, one memcpy could be saved
uint samples = (*it)->read((quint8*)data, framesPerBuffer, 1);
const qint16* src = (const qint16*)data;
std::vector<qint32>::iterator dst = m_mixBuffer.begin();
for(uint i = 0; i < samples; i++) {
*dst += *src;
++src;
++dst;
*dst += *src;
++src;
++dst;
}
}
// convert to int16
std::vector<qint32>::const_iterator src = m_mixBuffer.begin();
qint16* dst = (qint16*)data;
for(int i = 0; i < framesPerBuffer; ++i) {
// left channel
qint32 s = *src++;
if(s < -32768)
s = -32768;
else if(s > 32767)
s = 32767;
*dst++ = s;
// right channel
s = *src++;
if(s < -32768)
s = -32768;
else if(s > 32767)
s = 32767;
*dst++ = s;
}
return framesPerBuffer * 4;
}
qint64 AudioOutput::writeData(const char* data, qint64 len)
{
Q_UNUSED(data);
Q_UNUSED(len);
return 0;
}
+328
View File
@@ -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
+68
View File
@@ -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();
}
+19
View File
@@ -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)
+517
View File
@@ -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();
}
}
}
+26
View File
@@ -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;
}
+69
View File
@@ -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();
}
+73
View File
@@ -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];
}
+120
View File
@@ -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;
}
}
+11
View File
@@ -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;
}
+25
View File
@@ -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];
}
+6
View File
@@ -0,0 +1,6 @@
#include <stdio.h>
#include <QtGlobal>
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>
#include "dsp/lowpass.h"
+1
View File
@@ -0,0 +1 @@
#include "dsp/movingaverage.h"
+70
View File
@@ -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]);
}
+17
View File
@@ -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;
}
+28
View File
@@ -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
+231
View File
@@ -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;
}
+66
View File
@@ -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
+64
View File
@@ -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()
{
}
+141
View File
@@ -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;
}
}
+125
View File
@@ -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;
}
+111
View File
@@ -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();
}
+14
View File
@@ -0,0 +1,14 @@
#include "gui/aboutdialog.h"
#include "ui_aboutdialog.h"
AboutDialog::AboutDialog(QWidget* parent) :
QDialog(parent),
ui(new Ui::AboutDialog)
{
ui->setupUi(this);
}
AboutDialog::~AboutDialog()
{
delete ui;
}
+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>484</width>
<height>503</height>
</rect>
</property>
<property name="windowTitle">
<string>About SDRangelove</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources/res.qrc">:/logo.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;p&gt;Copyright (C) 2013 maintech GmbH, Otto-Hahn-Str. 15, 97204 Höchberg, Germany&lt;br&gt;
Written by Christian Daniel.&lt;/p&gt;
&lt;p&gt;Many thanks to the osmocom developer team - especially horizon, Hoernchen &amp;amp; tnt.&lt;/p&gt;
&lt;p&gt;SDRangelove itself is licensed as &quot;GPL2+&quot; with the added exception, that plugins using only header files from the &quot;include&quot;-subdirectory and not from the &quot;include-gpl&quot;-subdirectory do not count as derived works.&lt;/p&gt;
&lt;p&gt;The following rules apply to the SDRangelove main application and libsdrbase:&lt;br&gt;
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.
You should have received a copy of the GNU General Public License along with this program. If not, see &lt;a href=&quot;http://www.gnu.org/licenses/&quot;&gt;http://www.gnu.org/licenses/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the license of installed plugins, look into the plugin list.&lt;/p&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+26
View File
@@ -0,0 +1,26 @@
#include "gui/addpresetdialog.h"
#include "ui_addpresetdialog.h"
AddPresetDialog::AddPresetDialog(const QStringList& groups, const QString& group, QWidget* parent) :
QDialog(parent),
ui(new Ui::AddPresetDialog)
{
ui->setupUi(this);
ui->group->addItems(groups);
ui->group->lineEdit()->setText(group);
}
AddPresetDialog::~AddPresetDialog()
{
delete ui;
}
QString AddPresetDialog::group() const
{
return ui->group->lineEdit()->text();
}
QString AddPresetDialog::description() const
{
return ui->description->text();
}
+121
View File
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddPresetDialog</class>
<widget class="QDialog" name="AddPresetDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>324</width>
<height>204</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Preset Description</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Group</string>
</property>
<property name="buddy">
<cstring>group</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="group">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Description</string>
</property>
<property name="buddy">
<cstring>description</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="description"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>description</tabstop>
<tabstop>buttonBox</tabstop>
<tabstop>group</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AddPresetDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>194</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>203</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AddPresetDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>314</x>
<y>194</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>203</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -0,0 +1,77 @@
#include <QPainter>
#include <QColorDialog>
#include "gui/basicchannelsettingswidget.h"
#include "dsp/channelmarker.h"
#include "ui_basicchannelsettingswidget.h"
BasicChannelSettingsWidget::BasicChannelSettingsWidget(ChannelMarker* marker, QWidget* parent) :
QWidget(parent),
ui(new Ui::BasicChannelSettingsWidget),
m_channelMarker(marker)
{
ui->setupUi(this);
ui->title->setText(m_channelMarker->getTitle());
paintColor();
ui->red->setValue(m_channelMarker->getColor().red());
ui->green->setValue(m_channelMarker->getColor().green());
ui->blue->setValue(m_channelMarker->getColor().blue());
}
BasicChannelSettingsWidget::~BasicChannelSettingsWidget()
{
delete ui;
}
void BasicChannelSettingsWidget::on_title_textChanged(const QString& text)
{
m_channelMarker->setTitle(text);
}
void BasicChannelSettingsWidget::on_colorBtn_clicked()
{
QColor c = m_channelMarker->getColor();
c = QColorDialog::getColor(c, this, tr("Select Color for Channel"));
if(c.isValid()) {
m_channelMarker->setColor(c);
paintColor();
ui->red->setValue(m_channelMarker->getColor().red());
ui->green->setValue(m_channelMarker->getColor().green());
ui->blue->setValue(m_channelMarker->getColor().blue());
}
}
void BasicChannelSettingsWidget::paintColor()
{
QColor c(m_channelMarker->getColor());
QPixmap pm(24, 24);
pm.fill(c);
ui->colorBtn->setIcon(pm);
ui->color->setText(tr("#%1%2%3")
.arg(c.red(), 2, 16, QChar('0'))
.arg(c.green(), 2, 16, QChar('0'))
.arg(c.blue(), 2, 16, QChar('0')));
}
void BasicChannelSettingsWidget::on_red_valueChanged(int value)
{
QColor c(m_channelMarker->getColor());
c.setRed(value);
m_channelMarker->setColor(c);
paintColor();
}
void BasicChannelSettingsWidget::on_green_valueChanged(int value)
{
QColor c(m_channelMarker->getColor());
c.setGreen(value);
m_channelMarker->setColor(c);
paintColor();
}
void BasicChannelSettingsWidget::on_blue_valueChanged(int value)
{
QColor c(m_channelMarker->getColor());
c.setBlue(value);
m_channelMarker->setColor(c);
paintColor();
}
+107
View File
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BasicChannelSettingsWidget</class>
<widget class="QWidget" name="BasicChannelSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>149</width>
<height>116</height>
</rect>
</property>
<property name="windowTitle">
<string>Title &amp; Color</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>2</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="title"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Color</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="colorBtn">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="color">
<property name="text">
<string>#ff0000</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QSlider" name="red">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QSlider" name="green">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QSlider" name="blue">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>title</tabstop>
<tabstop>colorBtn</tabstop>
<tabstop>red</tabstop>
<tabstop>green</tabstop>
<tabstop>blue</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
+21
View File
@@ -0,0 +1,21 @@
#include <QPainter>
#include "gui/buttonswitch.h"
ButtonSwitch::ButtonSwitch(QWidget* parent) :
QToolButton(parent)
{
setCheckable(true);
m_originalPalette = palette();
connect(this, SIGNAL(toggled(bool)), this, SLOT(onToggled(bool)));
}
void ButtonSwitch::onToggled(bool checked)
{
if(checked) {
QPalette p = m_originalPalette;
p.setColor(QPalette::Button, QColor(0x80, 0x46, 0x00));
setPalette(p);
} else {
setPalette(m_originalPalette);
}
}
+36
View File
@@ -0,0 +1,36 @@
#include <QBoxLayout>
#include <QSpacerItem>
#include <QPainter>
#include <QResizeEvent>
#include "gui/channelwindow.h"
#include "gui/rollupwidget.h"
ChannelWindow::ChannelWindow(QWidget* parent) :
QScrollArea(parent)
{
m_container = new QWidget(this);
m_layout = new QBoxLayout(QBoxLayout::TopToBottom, m_container);
setWidget(m_container);
setWidgetResizable(true);
setBackgroundRole(QPalette::Base);
m_layout->setMargin(3);
m_layout->setSpacing(3);
}
void ChannelWindow::addRollupWidget(QWidget* rollupWidget)
{
rollupWidget->setParent(m_container);
m_container->layout()->addWidget(rollupWidget);
}
void ChannelWindow::resizeEvent(QResizeEvent* event)
{
if(event->size().height() > event->size().width()) {
m_layout->setDirection(QBoxLayout::TopToBottom);
m_layout->setAlignment(Qt::AlignTop);
} else {
m_layout->setDirection(QBoxLayout::LeftToRight);
m_layout->setAlignment(Qt::AlignLeft);
}
QScrollArea::resizeEvent(event);
}
+478
View File
@@ -0,0 +1,478 @@
#include <QPainter>
#include <QMouseEvent>
#include "gui/glscope.h"
#include "dsp/dspengine.h"
#ifdef _WIN32
static double log2f(double n)
{
return log(n) / log(2.0);
}
#endif
GLScope::GLScope(QWidget* parent) :
QGLWidget(parent),
m_dataChanged(false),
m_configChanged(true),
m_mode(ModeIQ),
m_orientation(Qt::Horizontal),
m_displayTrace(&m_rawTrace),
m_oldTraceSize(-1),
m_sampleRate(0),
m_dspEngine(NULL),
m_scopeVis(NULL),
m_amp(1.0),
m_timeBase(1),
m_timeOfsProMill(0),
m_triggerChannel(ScopeVis::TriggerFreeRun)
{
setAttribute(Qt::WA_OpaquePaintEvent);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(50);
}
GLScope::~GLScope()
{
if(m_dspEngine != NULL) {
m_dspEngine->removeSink(m_scopeVis);
delete m_scopeVis;
}
}
void GLScope::setDSPEngine(DSPEngine* dspEngine)
{
if((m_dspEngine == NULL) && (dspEngine != NULL)) {
m_dspEngine = dspEngine;
m_scopeVis = new ScopeVis(this);
m_dspEngine->addSink(m_scopeVis);
}
}
void GLScope::setAmp(Real amp)
{
m_amp = amp;
update();
}
void GLScope::setTimeBase(int timeBase)
{
m_timeBase = timeBase;
update();
}
void GLScope::setTimeOfsProMill(int timeOfsProMill)
{
m_timeOfsProMill = timeOfsProMill;
update();
}
void GLScope::setMode(Mode mode)
{
m_mode = mode;
m_dataChanged = true;
update();
}
void GLScope::setOrientation(Qt::Orientation orientation)
{
m_orientation = orientation;
m_configChanged = true;
update();
}
void GLScope::newTrace(const std::vector<Complex>& trace, int sampleRate)
{
if(!m_mutex.tryLock(2))
return;
if(m_dataChanged) {
m_mutex.unlock();
return;
}
m_rawTrace = trace;
m_sampleRate = sampleRate;
m_dataChanged = true;
m_mutex.unlock();
}
void GLScope::initializeGL()
{
glDisable(GL_DEPTH_TEST);
}
void GLScope::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
m_configChanged = true;
}
void GLScope::paintGL()
{
if(!m_mutex.tryLock(2))
return;
if(m_configChanged)
applyConfig();
handleMode();
if(m_displayTrace->size() != m_oldTraceSize) {
m_oldTraceSize = m_displayTrace->size();
emit traceSizeChanged(m_displayTrace->size());
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glScalef(2.0, -2.0, 1.0);
glTranslatef(-0.50, -0.5, 0);
// I
// draw rect around
glPushMatrix();
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y(), 0);
glScalef(m_glScopeRect1.width(), m_glScopeRect1.height(), 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(1.0f);
glColor4f(1, 1, 1, 0.5);
glBegin(GL_LINE_LOOP);
glVertex2f(1, 1);
glVertex2f(0, 1);
glVertex2f(0, 0);
glVertex2f(1, 0);
glEnd();
glDisable(GL_BLEND);
// paint grid
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(1.0f);
glColor4f(1, 1, 1, 0.05f);
for(int i = 1; i < 10; i++) {
glBegin(GL_LINE_LOOP);
glVertex2f(0, i * 0.1);
glVertex2f(1, i * 0.1);
glEnd();
}
for(int i = 1; i < 10; i++) {
glBegin(GL_LINE_LOOP);
glVertex2f(i * 0.1, 0);
glVertex2f(i * 0.1, 1);
glEnd();
}
glPopMatrix();
if(m_triggerChannel == ScopeVis::TriggerChannelI) {
glPushMatrix();
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0);
glScalef(m_glScopeRect1.width(), -(m_glScopeRect1.height() / 2) * m_amp1, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
glColor4f(0, 1, 0, 0.3f);
glBegin(GL_LINE_LOOP);
glVertex2f(0, m_triggerLevelHigh);
glVertex2f(1, m_triggerLevelHigh);
glEnd();
glColor4f(0, 0.8f, 0.0, 0.3f);
glBegin(GL_LINE_LOOP);
glVertex2f(0, m_triggerLevelLow);
glVertex2f(1, m_triggerLevelLow);
glEnd();
glDisable(GL_LINE_SMOOTH);
glPopMatrix();
}
if(m_displayTrace->size() > 0) {
glPushMatrix();
glTranslatef(m_glScopeRect1.x(), m_glScopeRect1.y() + m_glScopeRect1.height() / 2.0, 0);
glScalef(m_glScopeRect1.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect1.height() / 2) * m_amp1, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
glColor4f(1, 1, 0, 0.4f);
int start = m_timeOfsProMill * (m_displayTrace->size() - (m_displayTrace->size() / m_timeBase)) / 1000;
int end = start + m_displayTrace->size() / m_timeBase;
if(end - start < 2)
start--;
float posLimit = 1.0 / m_amp1;
float negLimit = -1.0 / m_amp1;
glBegin(GL_LINE_STRIP);
for(int i = start; i < end; i++) {
float v = (*m_displayTrace)[i].real() + m_ofs1;
if(v > posLimit)
v = posLimit;
else if(v < negLimit)
v = negLimit;
glVertex2f(i - start, v);
}
glEnd();
glDisable(GL_LINE_SMOOTH);
glPopMatrix();
}
// Q
// draw rect around
glPushMatrix();
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y(), 0);
glScalef(m_glScopeRect2.width(), m_glScopeRect2.height(), 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(1.0f);
glColor4f(1, 1, 1, 0.5);
glBegin(GL_LINE_LOOP);
glVertex2f(1, 1);
glVertex2f(0, 1);
glVertex2f(0, 0);
glVertex2f(1, 0);
glEnd();
glDisable(GL_BLEND);
// paint grid
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glLineWidth(1.0f);
glColor4f(1, 1, 1, 0.05f);
for(int i = 1; i < 10; i++) {
glBegin(GL_LINE_LOOP);
glVertex2f(0, i * 0.1);
glVertex2f(1, i * 0.1);
glEnd();
}
for(int i = 1; i < 10; i++) {
glBegin(GL_LINE_LOOP);
glVertex2f(i * 0.1, 0);
glVertex2f(i * 0.1, 1);
glEnd();
}
glPopMatrix();
if(m_triggerChannel == ScopeVis::TriggerChannelQ) {
glPushMatrix();
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0);
glScalef(m_glScopeRect2.width(), -(m_glScopeRect2.height() / 2) * m_amp2, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
glColor4f(0, 1, 0, 0.3f);
glBegin(GL_LINE_LOOP);
glVertex2f(0, m_triggerLevelHigh);
glVertex2f(1, m_triggerLevelHigh);
glEnd();
glColor4f(0, 0.8f, 0.0, 0.3f);
glBegin(GL_LINE_LOOP);
glVertex2f(0, m_triggerLevelLow);
glVertex2f(1, m_triggerLevelLow);
glEnd();
glDisable(GL_LINE_SMOOTH);
glPopMatrix();
}
if(m_displayTrace->size() > 0) {
glPushMatrix();
glTranslatef(m_glScopeRect2.x(), m_glScopeRect2.y() + m_glScopeRect2.height() / 2.0, 0);
glScalef(m_glScopeRect2.width() * (float)m_timeBase / (float)(m_displayTrace->size() - 1), -(m_glScopeRect2.height() / 2) * m_amp2, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
glColor4f(1, 1, 0, 0.4f);
int start = m_timeOfsProMill * (m_displayTrace->size() - (m_displayTrace->size() / m_timeBase)) / 1000;
int end = start + m_displayTrace->size() / m_timeBase;
if(end - start < 2)
start--;
float posLimit = 1.0 / m_amp2;
float negLimit = -1.0 / m_amp2;
glBegin(GL_LINE_STRIP);
for(int i = start; i < end; i++) {
float v = (*m_displayTrace)[i].imag();
if(v > posLimit)
v = posLimit;
else if(v < negLimit)
v = negLimit;
glVertex2f(i - start, v);
}
glEnd();
glDisable(GL_LINE_SMOOTH);
glPopMatrix();
}
glPopMatrix();
m_dataChanged = false;
m_mutex.unlock();
}
void GLScope::mousePressEvent(QMouseEvent* event)
{
#if 0
int x = event->x() - 10;
int y = event->y() - 10;
Real time;
Real amplitude;
ScopeVis::TriggerChannel channel;
if((x < 0) || (x >= width() - 20))
return;
if((y < 0) || (y >= height() - 20))
return;
if((m_sampleRate != 0) && (m_timeBase != 0) && (width() > 20))
time = ((Real)x * (Real)m_displayTrace->size()) / ((Real)m_sampleRate * (Real)m_timeBase * (Real)(width() - 20));
else time = -1.0;
if(y < (height() - 30) / 2) {
channel = ScopeVis::TriggerChannelI;
if((m_amp != 0) && (height() > 30))
amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0);
else amplitude = -1;
} else if(y > (height() - 30) / 2 + 10) {
y -= 10 + (height() - 30) / 2;
channel = ScopeVis::TriggerChannelQ;
if((m_amp != 0) && (height() > 30))
amplitude = 2.0 * ((height() - 30) * 0.25 - (Real)y) / (m_amp * (height() - 30) / 2.0);
else amplitude = -1;
} else {
channel = ScopeVis::TriggerFreeRun;
}
if(m_dspEngine != NULL) {
qDebug("amp %f", amplitude);
m_triggerLevelHigh = amplitude + 0.01 / m_amp;
m_triggerLevelLow = amplitude - 0.01 / m_amp;
if(m_triggerLevelHigh > 1.0)
m_triggerLevelHigh = 1.0;
else if(m_triggerLevelHigh < -1.0)
m_triggerLevelHigh = -1.0;
if(m_triggerLevelLow > 1.0)
m_triggerLevelLow = 1.0;
else if(m_triggerLevelLow < -1.0)
m_triggerLevelLow = -1.0;
m_scopeVis->configure(m_dspEngine->getMessageQueue(), channel, m_triggerLevelHigh, m_triggerLevelLow);
m_triggerChannel = channel;
m_changed = true;
update();
}
#endif
}
void GLScope::handleMode()
{
switch(m_mode) {
case ModeIQ:
m_displayTrace = &m_rawTrace;
m_amp1 = m_amp;
m_amp2 = m_amp;
m_ofs1 = 0.0;
m_ofs2 = 0.0;
break;
case ModeMagLinPha: {
m_mathTrace.resize(m_rawTrace.size());
std::vector<Complex>::iterator dst = m_mathTrace.begin();
for(std::vector<Complex>::const_iterator src = m_rawTrace.begin(); src != m_rawTrace.end(); ++src)
*dst++ = Complex(abs(*src), arg(*src) / M_PI);
m_displayTrace = &m_mathTrace;
m_amp1 = m_amp;
m_amp2 = 1.0;
m_ofs1 = -1.0 / m_amp1;
m_ofs2 = 0.0;
break;
}
case ModeMagdBPha: {
m_mathTrace.resize(m_rawTrace.size());
std::vector<Complex>::iterator dst = m_mathTrace.begin();
Real mult = (10.0f / log2f(10.0f));
for(std::vector<Complex>::const_iterator src = m_rawTrace.begin(); src != m_rawTrace.end(); ++src) {
Real v = src->real() * src->real() + src->imag() * src->imag();
v = (96.0 + (mult * log2f(v))) / 96.0;
*dst++ = Complex(v, arg(*src) / M_PI);
}
m_displayTrace = &m_mathTrace;
m_amp1 = 2.0 * m_amp;
m_amp2 = 1.0;
m_ofs1 = -1.0 / m_amp1;
m_ofs2 = 0.0;
break;
}
case ModeDerived12: {
if(m_rawTrace.size() > 3) {
m_mathTrace.resize(m_rawTrace.size() - 3);
std::vector<Complex>::iterator dst = m_mathTrace.begin();
for(uint i = 3; i < m_rawTrace.size() ; i++) {
*dst++ = Complex(
abs(m_rawTrace[i] - m_rawTrace[i - 1]),
abs(m_rawTrace[i] - m_rawTrace[i - 1]) - abs(m_rawTrace[i - 2] - m_rawTrace[i - 3]));
}
m_displayTrace = &m_mathTrace;
m_amp1 = m_amp;
m_amp2 = m_amp;
m_ofs1 = -1.0 / m_amp1;
m_ofs2 = 0.0;
}
break;
}
case ModeCyclostationary: {
if(m_rawTrace.size() > 2) {
m_mathTrace.resize(m_rawTrace.size() - 2);
std::vector<Complex>::iterator dst = m_mathTrace.begin();
for(uint i = 2; i < m_rawTrace.size() ; i++)
*dst++ = Complex(abs(m_rawTrace[i] - conj(m_rawTrace[i - 1])), 0);
m_displayTrace = &m_mathTrace;
m_amp1 = m_amp;
m_amp2 = m_amp;
m_ofs1 = -1.0 / m_amp1;
m_ofs2 = 0.0;
}
break;
}
}
}
void GLScope::applyConfig()
{
m_configChanged = false;
if(m_orientation == Qt::Vertical) {
m_glScopeRect1 = QRectF(
(float)10 / (float)width(),
(float)10 / (float)height(),
(float)(width() - 10 - 10) / (float)width(),
(float)((height() - 10 - 10 - 10) / 2) / (float)height()
);
m_glScopeRect2 = QRectF(
(float)10 / (float)width(),
(float)(10 + 10 + (height() - 10 - 10 - 10) / 2) / (float)height(),
(float)(width() - 10 - 10) / (float)width(),
(float)((height() - 10 - 10 - 10) / 2) / (float)height()
);
} else {
m_glScopeRect1 = QRectF(
(float)10 / (float)width(),
(float)10 / (float)height(),
(float)((width() - 10 - 10 - 10) / 2) / (float)width(),
(float)(height() - 10 - 10) / (float)height()
);
m_glScopeRect2 = QRectF(
(float)(10 + 10 + ((width() - 10 - 10 - 10) / 2)) / (float)width(),
(float)10 / (float)height(),
(float)((width() - 10 - 10 - 10) / 2) / (float)width(),
(float)(height() - 10 - 10) / (float)height()
);
}
}
void GLScope::tick()
{
if(m_dataChanged)
update();
}
File diff suppressed because it is too large Load Diff
+210
View File
@@ -0,0 +1,210 @@
#include "gui/glspectrumgui.h"
#include "dsp/fftwindow.h"
#include "dsp/spectrumvis.h"
#include "gui/glspectrum.h"
#include "util/simpleserializer.h"
#include "ui_glspectrumgui.h"
GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
QWidget(parent),
ui(new Ui::GLSpectrumGUI),
m_messageQueue(NULL),
m_spectrumVis(NULL),
m_glSpectrum(NULL),
m_fftSize(1024),
m_fftOverlap(10),
m_fftWindow(FFTWindow::Hamming),
m_refLevel(0),
m_powerRange(100),
m_decay(0),
m_displayWaterfall(false),
m_invertedWaterfall(false),
m_displayMaxHold(true),
m_displayHistogram(true),
m_displayGrid(true),
m_invert(false)
{
ui->setupUi(this);
for(int ref = 0; ref >= -95; ref -= 5)
ui->refLevel->addItem(QString("%1").arg(ref));
for(int range = 100; range >= 5; range -= 5)
ui->levelRange->addItem(QString("%1").arg(range));
}
GLSpectrumGUI::~GLSpectrumGUI()
{
delete ui;
}
void GLSpectrumGUI::setBuddies(MessageQueue* messageQueue, SpectrumVis* spectrumVis, GLSpectrum* glSpectrum)
{
m_messageQueue = messageQueue;
m_spectrumVis = spectrumVis;
m_glSpectrum = glSpectrum;
applySettings();
}
void GLSpectrumGUI::resetToDefaults()
{
m_fftSize = 1024;
m_fftOverlap = 10;
m_fftWindow = FFTWindow::Hamming;
m_refLevel = 0;
m_powerRange = 100;
m_decay = 0;
m_displayWaterfall = false;
m_invertedWaterfall = false;
m_displayMaxHold = true;
m_displayHistogram = true;
m_displayGrid = true;
m_invert = false;
applySettings();
}
QByteArray GLSpectrumGUI::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_fftSize);
s.writeS32(2, m_fftOverlap);
s.writeS32(3, m_fftWindow);
s.writeReal(4, m_refLevel);
s.writeReal(5, m_powerRange);
s.writeBool(6, m_displayWaterfall);
s.writeBool(7, m_invertedWaterfall);
s.writeBool(8, m_displayMaxHold);
s.writeBool(9, m_displayHistogram);
s.writeS32(10, m_decay);
s.writeBool(11, m_displayGrid);
s.writeBool(12, m_invert);
return s.final();
}
bool GLSpectrumGUI::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid()) {
resetToDefaults();
return false;
}
if(d.getVersion() == 1) {
d.readS32(1, &m_fftSize, 1024);
d.readS32(2, &m_fftOverlap, 10);
d.readS32(3, &m_fftWindow, FFTWindow::Hamming);
d.readReal(4, &m_refLevel, 0);
d.readReal(5, &m_powerRange, 100);
d.readBool(6, &m_displayWaterfall, true);
d.readBool(7, &m_invertedWaterfall, false);
d.readBool(8, &m_displayMaxHold, false);
d.readBool(9, &m_displayHistogram, true);
d.readS32(10, &m_decay, 0);
d.readBool(11, &m_displayGrid, true);
d.readBool(12, &m_invert, false);
applySettings();
return true;
} else {
resetToDefaults();
return false;
}
}
void GLSpectrumGUI::applySettings()
{
ui->fftWindow->setCurrentIndex(m_fftWindow);
for(int i = 0; i < 6; i++) {
if(m_fftSize == (1 << (i + 7))) {
ui->fftSize->setCurrentIndex(i);
break;
}
}
ui->refLevel->setCurrentIndex(-m_refLevel / 5);
ui->levelRange->setCurrentIndex((100 - m_powerRange) / 5);
ui->decay->setCurrentIndex(m_decay + 2);
ui->waterfall->setChecked(m_displayWaterfall);
ui->maxHold->setChecked(m_displayMaxHold);
ui->histogram->setChecked(m_displayHistogram);
ui->invert->setChecked(m_invert);
ui->grid->setChecked(m_displayGrid);
m_glSpectrum->setDisplayWaterfall(m_displayWaterfall);
m_glSpectrum->setInvertedWaterfall(m_invertedWaterfall);
m_glSpectrum->setDisplayMaxHold(m_displayMaxHold);
m_glSpectrum->setDisplayHistogram(m_displayHistogram);
m_glSpectrum->setDecay(m_decay);
m_glSpectrum->setInvertedWaterfall(m_invert);
m_glSpectrum->setDisplayGrid(m_displayGrid);
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
}
void GLSpectrumGUI::on_fftWindow_currentIndexChanged(int index)
{
m_fftWindow = index;
if(m_spectrumVis == NULL)
return;
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
}
void GLSpectrumGUI::on_fftSize_currentIndexChanged(int index)
{
m_fftSize = 1 << (7 + index);
if(m_spectrumVis != NULL)
m_spectrumVis->configure(m_messageQueue, m_fftSize, m_fftOverlap, (FFTWindow::Function)m_fftWindow);
}
void GLSpectrumGUI::on_refLevel_currentIndexChanged(int index)
{
m_refLevel = 0 - index * 5;
if(m_glSpectrum != NULL)
m_glSpectrum->setReferenceLevel(m_refLevel);
}
void GLSpectrumGUI::on_levelRange_currentIndexChanged(int index)
{
m_powerRange = 100 - index * 5;
if(m_glSpectrum != NULL)
m_glSpectrum->setPowerRange(m_powerRange);
}
void GLSpectrumGUI::on_decay_currentIndexChanged(int index)
{
m_decay = index - 2;
if(m_glSpectrum != NULL)
m_glSpectrum->setDecay(m_decay);
}
void GLSpectrumGUI::on_waterfall_toggled(bool checked)
{
m_displayWaterfall = checked;
if(m_glSpectrum != NULL)
m_glSpectrum->setDisplayWaterfall(m_displayWaterfall);
}
void GLSpectrumGUI::on_histogram_toggled(bool checked)
{
m_displayHistogram = checked;
if(m_glSpectrum != NULL)
m_glSpectrum->setDisplayHistogram(m_displayHistogram);
}
void GLSpectrumGUI::on_maxHold_toggled(bool checked)
{
m_displayMaxHold = checked;
if(m_glSpectrum != NULL)
m_glSpectrum->setDisplayMaxHold(m_displayMaxHold);
}
void GLSpectrumGUI::on_invert_toggled(bool checked)
{
m_invert = checked;
if(m_glSpectrum != NULL)
m_glSpectrum->setInvertedWaterfall(m_invert);
}
void GLSpectrumGUI::on_grid_toggled(bool checked)
{
m_displayGrid = checked;
if(m_glSpectrum != NULL)
m_glSpectrum->setDisplayGrid(m_displayGrid);
}
+476
View File
@@ -0,0 +1,476 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GLSpectrumGUI</class>
<widget class="QWidget" name="GLSpectrumGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>215</width>
<height>94</height>
</rect>
</property>
<property name="windowTitle">
<string>Oscilloscope</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,1,1,1">
<property name="margin">
<number>2</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Window</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_18">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>FFT Size</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_19">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Ref (dB)</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_20">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Range (dB)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="fftWindow">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FFT window function</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>Bartlett</string>
</property>
</item>
<item>
<property name="text">
<string>Blackman-Harris</string>
</property>
</item>
<item>
<property name="text">
<string>Flat Top</string>
</property>
</item>
<item>
<property name="text">
<string>Hamming</string>
</property>
</item>
<item>
<property name="text">
<string>Hanning</string>
</property>
</item>
<item>
<property name="text">
<string>Rectangle</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="fftSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FFT window function</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>128</string>
</property>
</item>
<item>
<property name="text">
<string>256</string>
</property>
</item>
<item>
<property name="text">
<string>512</string>
</property>
</item>
<item>
<property name="text">
<string>1024</string>
</property>
</item>
<item>
<property name="text">
<string>2048</string>
</property>
</item>
<item>
<property name="text">
<string>4096</string>
</property>
</item>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="refLevel">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FFT window function</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="levelRange">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FFT window function</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Decay</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="decay">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FFT window function</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>-2</string>
</property>
</item>
<item>
<property name="text">
<string>-1</string>
</property>
</item>
<item>
<property name="text">
<string>normal</string>
</property>
</item>
<item>
<property name="text">
<string>+1</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1" colspan="3">
<layout class="QHBoxLayout" name="controlBtns">
<property name="spacing">
<number>3</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="waterfall">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Display waterfall</string>
</property>
<property name="text">
<string>Waterfall</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/waterfall.png</normaloff>:/waterfall.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="histogram">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Display phosphor effect spectrum</string>
</property>
<property name="text">
<string>Histogram</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/histogram.png</normaloff>:/histogram.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="maxHold">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Display live spectrum</string>
</property>
<property name="text">
<string>Max Hold</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/maxhold.png</normaloff>:/maxhold.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="invert">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Exchange waterfall and histogram</string>
</property>
<property name="text">
<string>Inv</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/invertspectrum.png</normaloff>:/invertspectrum.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="grid">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Toggle the scale grid</string>
</property>
<property name="text">
<string>Grid</string>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/grid.png</normaloff>:/grid.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>fftWindow</tabstop>
<tabstop>fftSize</tabstop>
<tabstop>refLevel</tabstop>
<tabstop>levelRange</tabstop>
<tabstop>decay</tabstop>
<tabstop>waterfall</tabstop>
<tabstop>histogram</tabstop>
<tabstop>maxHold</tabstop>
<tabstop>invert</tabstop>
<tabstop>grid</tabstop>
</tabstops>
<resources>
<include location="../resources/res.qrc"/>
</resources>
<connections/>
</ui>
+56
View File
@@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QPainter>
#include "gui/indicator.h"
void Indicator::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setPen(Qt::black);
painter.setBrush(m_color);
painter.drawRect(0, 0, 18, 15);
painter.drawText(0, 0, 19, 16, Qt::AlignCenter | Qt::AlignHCenter, m_text);
}
QSize Indicator::sizeHint() const
{
return QSize(20, 16);
}
Indicator::Indicator(const QString& text, QWidget* parent) :
QWidget(parent),
m_color(Qt::gray),
m_text(text)
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QFont f = font();
f.setBold(true);
f.setPixelSize(8);
setFont(f);
}
void Indicator::setColor(const QColor& color)
{
if(m_color != color) {
m_color = color;
update();
}
}
+42
View File
@@ -0,0 +1,42 @@
#include "gui/pluginsdialog.h"
#include "mainwindow.h"
#include "ui_pluginsdialog.h"
PluginsDialog::PluginsDialog(PluginManager* pluginManager, QWidget* parent) :
QDialog(parent),
ui(new Ui::PluginsDialog)
{
ui->setupUi(this);
const PluginManager::Plugins& plugins = pluginManager->getPlugins();
for(PluginManager::Plugins::const_iterator it = plugins.constBegin(); it != plugins.constEnd(); ++it) {
QStringList sl;
const PluginDescriptor& desc = it->plugin->getPluginDescriptor();
sl.append(desc.displayedName);
sl.append(desc.version);
if(desc.licenseIsGPL)
sl.append(tr("YES"));
else sl.append("no");
QTreeWidgetItem* pluginItem = new QTreeWidgetItem(ui->tree, sl);
sl.clear();
sl.append(tr("Copyright: %1").arg(desc.copyright));
QTreeWidgetItem* item = new QTreeWidgetItem(pluginItem, sl);
item->setFirstColumnSpanned(true);
sl.clear();
sl.append(tr("Website: %1").arg(desc.website));
item = new QTreeWidgetItem(pluginItem, sl);
item->setFirstColumnSpanned(true);
sl.clear();
sl.append(tr("Source Code: %1").arg(desc.sourceCodeURL));
item = new QTreeWidgetItem(pluginItem, sl);
item->setFirstColumnSpanned(true);
}
ui->tree->resizeColumnToContents(0);
ui->tree->resizeColumnToContents(1);
ui->tree->resizeColumnToContents(2);
}
PluginsDialog::~PluginsDialog()
{
delete ui;
}
+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PluginsDialog</class>
<widget class="QDialog" name="PluginsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Loaded Plugins</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="tree">
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>GPL</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PluginsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+55
View File
@@ -0,0 +1,55 @@
#include <QTreeWidgetItem>
#include "gui/preferencesdialog.h"
#include "ui_preferencesdialog.h"
#include "audio/audiodeviceinfo.h"
PreferencesDialog::PreferencesDialog(AudioDeviceInfo* audioDeviceInfo, QWidget* parent) :
QDialog(parent),
ui(new Ui::PreferencesDialog),
m_audioDeviceInfo(audioDeviceInfo)
{
ui->setupUi(this);
const AudioDeviceInfo::Devices& devices = audioDeviceInfo->getDevices();
QTreeWidgetItem* api;
QStringList sl;
sl.append(tr("Default (use first suitable device)"));
api = new QTreeWidgetItem(ui->audioTree, sl, ATDefault);
api->setFirstColumnSpanned(true);
for(AudioDeviceInfo::Devices::const_iterator it = devices.begin(); it != devices.end(); ++it) {
int apiIndex;
sl.clear();
for(apiIndex = 0; apiIndex < ui->audioTree->topLevelItemCount(); ++apiIndex) {
if(ui->audioTree->topLevelItem(apiIndex)->text(0) == it->api)
break;
}
if(apiIndex >= ui->audioTree->topLevelItemCount()) {
sl.append(it->api);
api = new QTreeWidgetItem(ui->audioTree, sl, ATInterface);
api->setExpanded(true);
api->setFirstColumnSpanned(true);
sl.clear();
} else {
api = ui->audioTree->topLevelItem(apiIndex);
}
sl.append(it->name);
new QTreeWidgetItem(api, sl, ATDevice);
}
if(ui->audioTree->currentItem() == NULL)
ui->audioTree->setCurrentItem(ui->audioTree->topLevelItem(0));
ui->tabWidget->setCurrentIndex(0);
}
PreferencesDialog::~PreferencesDialog()
{
delete ui;
}
void PreferencesDialog::accept()
{
QDialog::accept();
}
+109
View File
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PreferencesDialog</class>
<widget class="QDialog" name="PreferencesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Receiver Hardware</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeWidget" name="treeWidget">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Audio Output</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTreeWidget" name="audioTree">
<column>
<property name="text">
<string notr="true">Device</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>buttonBox</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>treeWidget</tabstop>
<tabstop>audioTree</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PreferencesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PreferencesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+29
View File
@@ -0,0 +1,29 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "gui/presetitem.h"
PresetItem::PresetItem(QTreeWidgetItem* parent, const QStringList& strings, quint64 frequency, int type) :
QTreeWidgetItem(parent, strings, type),
m_frequency(frequency)
{
}
bool PresetItem::operator<(const QTreeWidgetItem& other) const
{
return m_frequency < ((const PresetItem&)other).m_frequency;
}
+348
View File
@@ -0,0 +1,348 @@
#include <QEvent>
#include <QPainter>
#include <QMouseEvent>
#include "gui/rollupwidget.h"
#include "ui_glspectrumgui.h"
RollupWidget::RollupWidget(QWidget* parent) :
QWidget(parent)
{
setMinimumSize(250, 150);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setBackgroundRole(QPalette::Window);
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
m_titleColor = palette().highlight().color();
}
QByteArray RollupWidget::saveState(int version) const
{
QByteArray state;
QDataStream stream(&state, QIODevice::WriteOnly);
int count = 0;
for(int i = 0; i < children().count(); ++i) {
QWidget* r = qobject_cast<QWidget*>(children()[i]);
if(r != NULL)
count++;
}
stream << VersionMarker;
stream << version;
stream << count;
for(int i = 0; i < children().count(); ++i) {
QWidget* r = qobject_cast<QWidget*>(children()[i]);
if(r != NULL) {
stream << r->objectName();
if(r->isHidden())
stream << (int)0;
else stream << (int)1;
}
}
return state;
}
bool RollupWidget::restoreState(const QByteArray& state, int version)
{
if(state.isEmpty())
return false;
QByteArray sd = state;
QDataStream stream(&sd, QIODevice::ReadOnly);
int marker, v;
stream >> marker;
stream >> v;
if((stream.status() != QDataStream::Ok) || (marker != VersionMarker) || (v != version))
return false;
int count;
stream >> count;
if(stream.status() != QDataStream::Ok)
return false;
for(int i = 0; i < count; ++i) {
QString name;
int visible;
stream >> name;
stream >> visible;
if(stream.status() != QDataStream::Ok)
return false;
for(int j = 0; j < children().count(); ++j) {
QWidget* r = qobject_cast<QWidget*>(children()[j]);
if(r != NULL) {
if(r->objectName() == name) {
if(visible)
r->show();
else r->hide();
break;
}
}
}
}
return true;
}
void RollupWidget::setTitleColor(const QColor& c)
{
m_titleColor = c;
update();
}
int RollupWidget::arrangeRollups()
{
QFontMetrics fm(font());
int pos = fm.height() + 4;
for(int i = 0; i < children().count(); ++i) {
QWidget* r = qobject_cast<QWidget*>(children()[i]);
if(r != NULL) {
pos += fm.height() + 2;
if(!r->isHidden()) {
r->move(2, pos + 3);
int h = 0;
if(r->hasHeightForWidth())
h = r->heightForWidth(width() - 4);
else h = r->sizeHint().height();
r->resize(width() - 4, h);
pos += r->height() + 5;
}
}
}
setMinimumHeight(pos);
setMaximumHeight(pos);
return pos;
}
void RollupWidget::paintEvent(QPaintEvent*)
{
QPainter p(this);
QColor frame = palette().highlight().color();
// Eigenbau
QFontMetrics fm(font());
p.setRenderHint(QPainter::Antialiasing, true);
// Ecken
p.setPen(Qt::NoPen);
p.setBrush(palette().base());
p.drawRect(0, 0, 5, 5);
p.drawRect(width() - 5, 0, 5, 5);
p.drawRect(0, height() - 5, 5, 5);
p.drawRect(width() - 5, height() - 5, 5, 5);
// Rahmen
p.setPen(frame);
p.setBrush(palette().window());
QRectF r(rect());
r.adjust(0.5, 0.5, -0.5, -0.5);
p.drawRoundedRect(r, 3.0, 3.0, Qt::AbsoluteSize);
// Titel-Hintergrund
p.setPen(Qt::NoPen);
p.setBrush(m_titleColor);
QPainterPath path;
path.moveTo(1.5, fm.height() + 2.5);
path.lineTo(width() - 1.5, fm.height() + 2.5);
path.lineTo(width() - 1.5, 3.5);
path.arcTo(QRectF(width() - 3.5, 0, 2.5, 2.5), 270, -90);
path.lineTo(3.5, 1.5);
path.arcTo(QRectF(1.5, 2.5, 2.5, 2.5), 90, 90);
p.drawPath(path);
// Titel-Abschlusslinie
p.setPen(frame);
p.drawLine(QPointF(0.5, 2 + fm.height() + 1.5), QPointF(width() - 1.5, 2 + fm.height() + 1.5));
// Aktiv-Button links
p.setPen(QPen(palette().windowText().color(), 1.0));
p.setBrush(palette().light());
p.drawRoundedRect(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()), 2.0, 2.0, Qt::AbsoluteSize);
// Schließen-Button rechts
p.setRenderHint(QPainter::Antialiasing, true);
p.setPen(QPen(palette().windowText().color(), 1.0));
p.setBrush(palette().light());
r = QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent());
p.drawRoundedRect(r, 2.0, 2.0, Qt::AbsoluteSize);
p.setPen(QPen(palette().windowText().color(), 1.5));
p.drawLine(r.topLeft() + QPointF(1, 1), r.bottomRight() + QPointF(-1, -1));
p.drawLine(r.bottomLeft() + QPointF(1, -1), r.topRight() + QPointF(-1, 1));
// Titel
p.setPen(palette().highlightedText().color());
p.drawText(QRect(2 + fm.height(), 2, width() - 4 - 2 * fm.height(), fm.height()),
fm.elidedText(windowTitle(), Qt::ElideMiddle, width() - 4 - 2 * fm.height(), 0));
// Rollups
int pos = fm.height() + 4;
const QObjectList& c = children();
QObjectList::ConstIterator w = c.begin();
QObjectList::ConstIterator n = c.begin();
for(n = c.begin(); n != c.end(); ++n) {
if(qobject_cast<QWidget*>(*n) != NULL)
break;
}
for(w = n; w != c.end(); w = n) {
if(n != c.end())
++n;
for(; n != c.end(); ++n) {
if(qobject_cast<QWidget*>(*n) != NULL)
break;
}
pos += paintRollup(qobject_cast<QWidget*>(*w), pos, &p, n == c.end(), frame);
}
}
int RollupWidget::paintRollup(QWidget* rollup, int pos, QPainter* p, bool last, const QColor& frame)
{
QFontMetrics fm(font());
int height = 1;
// Titel-Abschlusslinie
if(!rollup->isHidden()) {
p->setPen(palette().dark().color());
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
p->setPen(palette().light().color());
p->drawLine(QPointF(1.5, pos + fm.height() + 2.5), QPointF(width() - 1.5, pos + fm.height() + 2.5));
height += 2;
} else {
if(!last) {
p->setPen(frame);
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
height++;
}
}
// Titel
p->setPen(palette().windowText().color());
p->drawText(QRect(2 + fm.height(), pos, width() - 4 - fm.height(), fm.height()),
fm.elidedText(rollup->windowTitle(), Qt::ElideMiddle, width() - 4 - fm.height(), 0));
height += fm.height();
// Ausklapp-Icon
p->setPen(palette().windowText().color());
p->setBrush(palette().windowText());
if(!rollup->isHidden()) {
QPolygonF a;
a.append(QPointF(3.5, pos + 2));
a.append(QPointF(3.5 + fm.ascent(), pos + 2));
a.append(QPointF(3.5 + fm.ascent() / 2.0, pos + fm.height() - 2));
p->drawPolygon(a);
} else {
QPolygonF a;
a.append(QPointF(3.5, pos + 2));
a.append(QPointF(3.5, pos + fm.height() - 2));
a.append(QPointF(3.5 + fm.ascent(), pos + fm.height() / 2));
p->drawPolygon(a);
}
// Inhalt
if(!rollup->isHidden() && (!last)) {
// Rollup-Abschlusslinie
p->setPen(frame);
p->drawLine(QPointF(1.5, pos + fm.height() + rollup->height() + 6.5),
QPointF(width() - 1.5, pos + fm.height() + rollup->height() + 6.5));
height += rollup->height() + 4;
}
return height;
}
void RollupWidget::resizeEvent(QResizeEvent* size)
{
arrangeRollups();
QWidget::resizeEvent(size);
}
void RollupWidget::mousePressEvent(QMouseEvent* event)
{
QFontMetrics fm(font());
// menu box left
if(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
emit customContextMenuRequested(event->pos());
return;
}
// close button right
if(QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
close();
return;
}
// check if we need to change a rollup widget
int pos = fm.height() + 4;
for(int i = 0; i < children().count(); ++i) {
QWidget* r = qobject_cast<QWidget*>(children()[i]);
if(r != NULL) {
if((event->y() >= pos) && (event->y() < (pos + fm.height() + 3))) {
if(r->isHidden()) {
r->show();
//emit widgetRolled(r, true);
} else {
r->hide();
//emit widgetRolled(r, false);
}
arrangeRollups();
repaint();
return;
} else {
pos += fm.height() + 2;
if(!r->isHidden())
pos += r->height() + 5;
}
}
}
}
void RollupWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
QFontMetrics fm(font());
// menu box left
if(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
emit menuDoubleClickEvent();
return;
}
}
bool RollupWidget::event(QEvent* event)
{
if(event->type() == QEvent::ChildAdded) {
((QChildEvent*)event)->child()->installEventFilter(this);
arrangeRollups();
} else if(event->type() == QEvent::ChildRemoved) {
((QChildEvent*)event)->child()->removeEventFilter(this);
arrangeRollups();
}
return QWidget::event(event);
}
bool RollupWidget::eventFilter(QObject* object, QEvent* event)
{
if(event->type() == QEvent::Show) {
if(children().contains(object)) {
arrangeRollups();
emit widgetRolled(qobject_cast<QWidget*>(object), true);
}
} else if(event->type() == QEvent::Hide) {
if(children().contains(object)) {
arrangeRollups();
emit widgetRolled(qobject_cast<QWidget*>(object), false);
}
} else if(event->type() == QEvent::WindowTitleChange) {
if(children().contains(object))
repaint();
}
return QWidget::eventFilter(object, event);
}
+129
View File
@@ -0,0 +1,129 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QPainter>
#include "gui/scale.h"
Scale::Scale(QWidget* parent) :
QWidget(parent)
{
}
void Scale::setOrientation(Qt::Orientation orientation)
{
m_orientation = orientation;
m_scaleEngine.setOrientation(orientation);
m_scaleEngine.setFont(font());
QFontMetrics fm(font());
switch(m_orientation) {
case Qt::Horizontal:
m_scaleEngine.setSize(width());
setMinimumWidth(0);
setMaximumWidth(QWIDGETSIZE_MAX);
setMinimumHeight(3 + fontMetrics().ascent());
setMaximumHeight(3 + fontMetrics().ascent());
break;
case Qt::Vertical:
m_scaleEngine.setSize(height());
setMinimumWidth(30);
setMaximumWidth(30);
setMinimumHeight(0);
setMaximumHeight(QWIDGETSIZE_MAX);
break;
}
}
void Scale::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
{
m_scaleEngine.setRange(physicalUnit, rangeMin, rangeMax);
update();
}
void Scale::paintEvent(QPaintEvent*)
{
QPainter painter(this);
const ScaleEngine::TickList& tickList = m_scaleEngine.getTickList();
QFontMetricsF fontMetrics(font());
const ScaleEngine::Tick* tick;
int i;
float bottomLine;
switch(m_orientation) {
case Qt::Horizontal: {
painter.setPen(Qt::black);
// Zwischenlinien für x-Achse zeichnen
for(i = 0; i < tickList.count(); i++) {
tick = &tickList[i];
if(!tick->major)
painter.drawLine(QLineF(tick->pos, 0, tick->pos, 1));
}
// Skala am Rand zeichnen
painter.drawLine(QLineF(0, 0, width() - 1, 0));
// Hauptlinien und Beschriftung für x-Achse zeichnen
for(i = 0; i < tickList.count(); i++) {
tick = &tickList[i];
if(tick->major) {
painter.drawLine(QLineF(tick->pos - 1, 0, tick->pos - 1, 3));
if(tick->textSize > 0) {
painter.drawText(QPointF(tick->textPos, 3 + fontMetrics.ascent()), tick->text);
}
}
}
break;
}
case Qt::Vertical: {
bottomLine = height() - 1;
painter.setPen(Qt::black);
// Zwischenlinien für y-Achse zeichnen
for(i = 0; i < tickList.count(); i++) {
tick = &tickList[i];
if(!tick->major)
painter.drawLine(QLineF(width() - 2, bottomLine - tick->pos, width() - 1, bottomLine - tick->pos));
}
// Skala am Rand zeichnen
painter.drawLine(QLineF(width() - 1, 0, width() - 1, height() - 1));
// Hauptlinien und Beschriftung für y-Achse zeichnen
for(i = 0; i < tickList.count(); i++) {
tick = &tickList[i];
if(tick->major) {
painter.drawLine(QLineF(width() - 4, bottomLine - tick->pos, width() - 1, bottomLine - tick->pos));
if(tick->textSize > 0)
painter.drawText(QPointF(width() - 4 - tick->textSize, bottomLine - tick->textPos), tick->text);
}
}
}
}
}
void Scale::resizeEvent(QResizeEvent*)
{
switch(m_orientation) {
case Qt::Horizontal:
m_scaleEngine.setSize(width());
break;
case Qt::Vertical:
m_scaleEngine.setSize(height());
break;
}
}
+569
View File
@@ -0,0 +1,569 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <math.h>
#include <QFontMetrics>
#include <QDataStream>
#include "gui/scaleengine.h"
/*
static double trunc(double d)
{
return (d > 0) ? floor(d) : ceil(d);
}
*/
QString ScaleEngine::formatTick(double value, int decimalPlaces, bool fancyTime)
{
if((m_physicalUnit != Unit::Time) || (!fancyTime) || 1) {
return QString("%1").arg(value, 0, 'f', decimalPlaces);
} else {
QString str;
double orig = fabs(value);
double tmp;
if(orig >= 86400.0) {
tmp = floor(value / 86400.0);
str = QString("%1.").arg(tmp, 0, 'f', 0);
value -= tmp * 86400.0;
if(value < 0.0)
value *= -1.0;
}
if(orig >= 3600.0) {
tmp = floor(value / 3600.0);
str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
value -= tmp * 3600.0;
if(value < 0.0)
value *= -1.0;
}
if(orig >= 60.0) {
tmp = floor(value / 60.0);
str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
value -= tmp * 60.0;
if(value < 0.0)
value *= -1.0;
}
tmp = value;
str += QString("%1").arg(tmp, 2, 'f', decimalPlaces, QChar('0'));
return str;
}
}
void ScaleEngine::calcCharSize()
{
QFontMetricsF fontMetrics(m_font);
if(m_orientation == Qt::Vertical) {
m_charSize = fontMetrics.height();
} else {
QString str("012345679.,-");
int i;
float size;
float max = 0.0f;
for(i = 0; i < str.length(); i++) {
size = fontMetrics.width(QString(str[i]));
if(size > max)
max = size;
}
m_charSize = max;
}
}
void ScaleEngine::calcScaleFactor()
{
double median;
median = ((m_rangeMax - m_rangeMin) / 2.0) + m_rangeMin;
m_scale = 1.0;
switch(m_physicalUnit) {
case Unit::None:
m_unitStr.clear();
break;
case Unit::Frequency:
if(median < 1000.0) {
m_unitStr = QObject::tr("Hz");
} else if(median < 1000000.0) {
m_unitStr = QObject::tr("kHz");
m_scale = 1000.0;
} else if(median < 1000000000.0) {
m_unitStr = QObject::tr("MHz");
m_scale = 1000000.0;
} else if(median < 1000000000000.0){
m_unitStr = QObject::tr("GHz");
m_scale = 1000000000.0;
} else {
m_unitStr = QObject::tr("THz");
m_scale = 1000000000000.0;
}
break;
case Unit::Information:
if(median < 1024.0) {
m_unitStr = QObject::tr("Bytes");
} else if(median < 1048576.0) {
m_unitStr = QObject::tr("KiBytes");
m_scale = 1024.0;
} else if(median < 1073741824.0) {
m_unitStr = QObject::tr("MiBytes");
m_scale = 1048576.0;
} else if(median < 1099511627776.0) {
m_unitStr = QObject::tr("GiBytes");
m_scale = 1073741824.0;
} else if(median < 1125899906842624.0) {
m_unitStr = QObject::tr("TiBytes");
m_scale = 1099511627776.0;
} else {
m_unitStr = QObject::tr("PiBytes");
m_scale = 1125899906842624.0;
}
break;
case Unit::Percent:
m_unitStr = QString("%");
break;
case Unit::Decibel:
m_unitStr = QString("dB");
break;
case Unit::DecibelMilliWatt:
m_unitStr = QString("dBm");
break;
case Unit::DecibelMicroVolt:
m_unitStr = QString("dBµV");
break;
case Unit::AngleDegrees:
m_unitStr = QString("°");
case Unit::Time:
if(median < 0.001) {
m_unitStr = QString("µs");
m_scale = 0.000001;
} else if(median < 1.0) {
m_unitStr = QString("ms");
m_scale = 0.001;
} else {
m_unitStr = QString("s");
}
break;
}
}
double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces)
{
double sign;
double log10x;
double exponent;
double base;
int decimalPlaces;
if(distance == 0.0)
return 0.0;
sign = (distance > 0.0) ? 1.0 : -1.0;
log10x = log10(fabs(distance));
exponent = floor(log10x);
base = pow(10.0, log10x - exponent);
decimalPlaces = (int)(-exponent);
/*
if((m_physicalUnit == Unit::Time) && (distance >= 1.0)) {
if(retDecimalPlaces != NULL)
*retDecimalPlaces = 0;
if(distance < 1.0)
return 1.0;
else if(distance < 5.0)
return 5.0;
else if(distance < 10.0)
return 10.0;
else if(distance < 15.0)
return 15.0;
else if(distance < 30.0)
return 30.0;
else if(distance < 60.0)
return 60.0;
else if(distance < 5.0 * 60.0)
return 5.0 * 60.0;
else if(distance < 10.0 * 60.0)
return 10.0 * 60.0;
else if(distance < 15.0 * 60.0)
return 15.0 * 60.0;
else if(distance < 30.0 * 60.0)
return 30.0 * 60.0;
else if(distance < 3600.0)
return 3600.0;
else if(distance < 2.0 * 3600.0)
return 2.0 * 3600.0;
else if(distance < 3.0 * 3600.0)
return 3.0 * 3600.0;
else if(distance < 6.0 * 3600.0)
return 6.0 * 3600.0;
else if(distance < 12.0 * 3600.0)
return 12.0 * 3600.0;
else if(distance < 86000.0)
return 86000.0;
else if(distance < 2.0 * 86000.0)
return 2.0 * 86000.0;
else if(distance < 7.0 * 86000.0)
return 7.0 * 86000.0;
else if(distance < 10.0 * 86000.0)
return 10.0 * 86000.0;
else if(distance < 30.0 * 86000.0)
return 30.0 * 86000.0;
else return 90.0 * 86000.0;
} else {*/
if(base <= 1.0) {
base = 1.0;
} else if(base <= 2.0) {
base = 2.0;
} else if(base <= 2.5) {
base = 2.5;
if(decimalPlaces >= 0)
decimalPlaces++;
} else if(base <= 5.0) {
base = 5.0;
} else {
base = 10.0;
}/*
}*/
if(retDecimalPlaces != NULL) {
if(decimalPlaces < 0)
decimalPlaces = 0;
*retDecimalPlaces = decimalPlaces;
}
return sign * base * pow(10.0, exponent);
}
int ScaleEngine::calcTickTextSize()
{
int tmp;
int tickLen;
int decimalPlaces;
tickLen = 1;
tmp = formatTick(m_rangeMin / m_scale, 0).length();
if(tmp > tickLen)
tickLen = tmp;
tmp = formatTick(m_rangeMax / m_scale, 0).length();
if(tmp > tickLen)
tickLen = tmp;
calcMajorTickUnits((m_rangeMax - m_rangeMin) / m_scale, &decimalPlaces);
return tickLen + decimalPlaces + 1;
}
void ScaleEngine::forceTwoTicks()
{
Tick tick;
QFontMetricsF fontMetrics(m_font);
m_tickList.clear();
tick.major = true;
tick.pos = getPosFromValue(m_rangeMin);
tick.text = formatTick(m_rangeMin / m_scale, m_decimalPlaces);
tick.textSize = fontMetrics.boundingRect(tick.text).width();
if(m_orientation == Qt::Vertical)
tick.textPos = tick.pos - fontMetrics.ascent() / 2;
else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
m_tickList.append(tick);
tick.pos = getPosFromValue(m_rangeMax);
tick.text = formatTick(m_rangeMax / m_scale, m_decimalPlaces);
tick.textSize = fontMetrics.boundingRect(tick.text).width();
if(m_orientation == Qt::Vertical)
tick.textPos = tick.pos - fontMetrics.ascent() / 2;
else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
m_tickList.append(tick);
}
void ScaleEngine::reCalc()
{
float majorTickSize;
double rangeMinScaled;
double rangeMaxScaled;
int maxNumMajorTicks;
int numMajorTicks;
int step;
int skip;
double value;
double value2;
int i;
int j;
Tick tick;
float pos;
QString str;
QFontMetricsF fontMetrics(m_font);
float endPos;
float lastEndPos;
bool done;
if(!m_recalc)
return;
m_recalc = false;
m_tickList.clear();
calcScaleFactor();
rangeMinScaled = m_rangeMin / m_scale;
rangeMaxScaled = m_rangeMax / m_scale;
if(m_orientation == Qt::Vertical) {
maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f));
} else {
majorTickSize = (calcTickTextSize() + 2) * m_charSize;
if(majorTickSize != 0.0)
maxNumMajorTicks = (int)(m_size / majorTickSize);
else maxNumMajorTicks = 20;
}
m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance);
if(numMajorTicks == 0) {
forceTwoTicks();
return;
}
if(maxNumMajorTicks > 0)
m_numMinorTicks = (int)(m_size / (maxNumMajorTicks * fontMetrics.height()));
else m_numMinorTicks = 0;
if(m_numMinorTicks < 1)
m_numMinorTicks = 0;
else if(m_numMinorTicks < 2)
m_numMinorTicks = 1;
else if(m_numMinorTicks < 5)
m_numMinorTicks = 2;
else if(m_numMinorTicks < 10)
m_numMinorTicks = 5;
else m_numMinorTicks = 10;
m_firstMajorTickValue = floor(rangeMinScaled / m_majorTickValueDistance) * m_majorTickValueDistance;
skip = 0;
if(rangeMinScaled == rangeMaxScaled)
return;
while(true) {
m_tickList.clear();
step = 0;
lastEndPos = -100000000;
done = true;
for(i = 0; true; i++) {
value = majorTickValue(i);
for(j = 1; j < m_numMinorTicks; j++) {
value2 = value + minorTickValue(j);
if(value2 < rangeMinScaled)
continue;
if(value2 > rangeMaxScaled)
break;
pos = getPosFromValue((value + minorTickValue(j)) * m_scale);
if((pos >= 0) && (pos < m_size)) {
tick.pos = pos;
tick.major = false;
tick.textPos = -1;
tick.textSize = -1;
tick.text.clear();
}
m_tickList.append(tick);
}
pos = getPosFromValue(value * m_scale);
if(pos < 0.0)
continue;
if(pos >= m_size)
break;
tick.pos = pos;
tick.major = true;
tick.textPos = -1;
tick.textSize = -1;
tick.text.clear();
if(step % (skip + 1) != 0) {
m_tickList.append(tick);
step++;
continue;
}
step++;
str = formatTick(value, m_decimalPlaces);
tick.text = str;
tick.textSize = fontMetrics.boundingRect(tick.text).width();
if(m_orientation == Qt::Vertical) {
tick.textPos = pos - fontMetrics.ascent() / 2;
endPos = tick.textPos + fontMetrics.ascent();
} else {
tick.textPos = pos - fontMetrics.boundingRect(tick.text).width() / 2;
endPos = tick.textPos + tick.textSize;
}
if(lastEndPos >= tick.textPos) {
done = false;
break;
} else {
lastEndPos = endPos;
}
m_tickList.append(tick);
}
if(done)
break;
skip++;
}
// make sure we have at least two major ticks with numbers
numMajorTicks = 0;
for(i = 0; i < m_tickList.count(); i++) {
tick = m_tickList.at(i);
if(tick.major)
numMajorTicks++;
}
if(numMajorTicks < 2)
forceTwoTicks();
}
double ScaleEngine::majorTickValue(int tick)
{
return m_firstMajorTickValue + (tick * m_majorTickValueDistance);
}
double ScaleEngine::minorTickValue(int tick)
{
if(m_numMinorTicks < 1)
return 0.0;
return (m_majorTickValueDistance * tick) / m_numMinorTicks;
}
ScaleEngine::ScaleEngine() :
m_orientation(Qt::Horizontal),
m_physicalUnit(Unit::None),
m_rangeMin(-1.0),
m_rangeMax(1.0),
m_recalc(true)
{
}
void ScaleEngine::setOrientation(Qt::Orientation orientation)
{
m_orientation = orientation;
m_recalc = true;
}
void ScaleEngine::setFont(const QFont& font)
{
m_font = font;
m_recalc = true;
calcCharSize();
}
void ScaleEngine::setSize(float size)
{
if(size > 0.0f) {
m_size = size;
} else {
m_size = 1.0f;
}
m_recalc = true;
}
void ScaleEngine::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
{
double tmpRangeMin;
double tmpRangeMax;
/*
if(rangeMin < rangeMax) {
tmpRangeMin = rangeMin;
tmpRangeMax = rangeMax;
} else if(rangeMin > rangeMax) {
tmpRangeMin = rangeMax;
tmpRangeMax = rangeMin;
} else {
tmpRangeMin = rangeMin * 0.99;
tmpRangeMax = rangeMin * 1.01 + 0.01;
}
*/
tmpRangeMin = rangeMin;
tmpRangeMax = rangeMax;
if((tmpRangeMin != m_rangeMin) || (tmpRangeMax != m_rangeMax) || (m_physicalUnit != physicalUnit)) {
m_physicalUnit = physicalUnit;
m_rangeMin = tmpRangeMin;
m_rangeMax = tmpRangeMax;
m_recalc = true;
}
}
float ScaleEngine::getPosFromValue(double value)
{
return ((value - m_rangeMin) / (m_rangeMax - m_rangeMin)) * (m_size - 1.0);
}
float ScaleEngine::getValueFromPos(double pos)
{
return ((pos * (m_rangeMax - m_rangeMin)) / (m_size - 1.0)) + m_rangeMin;
}
const ScaleEngine::TickList& ScaleEngine::getTickList()
{
reCalc();
return m_tickList;
}
QString ScaleEngine::getRangeMinStr()
{
if(m_unitStr.length() > 0)
return QString("%1 %2").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false)).arg(m_unitStr);
else return QString("%1").arg(formatTick(m_rangeMin / m_scale, m_decimalPlaces, false));
}
QString ScaleEngine::getRangeMaxStr()
{
if(m_unitStr.length() > 0)
return QString("%1 %2").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false)).arg(m_unitStr);
else return QString("%1").arg(formatTick(m_rangeMax / m_scale, m_decimalPlaces, false));
}
float ScaleEngine::getScaleWidth()
{
float max;
float len;
int i;
reCalc();
max = 0.0f;
for(i = 0; i < m_tickList.count(); i++) {
len = m_tickList[i].textSize;
if(len > max)
max = len;
}
return max;
}
+185
View File
@@ -0,0 +1,185 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "gui/scopewindow.h"
#include "ui_scopewindow.h"
#include "util/simpleserializer.h"
ScopeWindow::ScopeWindow(DSPEngine* dspEngine, QWidget* parent) :
QWidget(parent),
ui(new Ui::ScopeWindow),
m_sampleRate(0),
m_timeBase(1)
{
ui->setupUi(this);
ui->scope->setDSPEngine(dspEngine);
}
ScopeWindow::~ScopeWindow()
{
delete ui;
}
void ScopeWindow::setSampleRate(int sampleRate)
{
m_sampleRate = sampleRate;
on_scope_traceSizeChanged(0);
}
void ScopeWindow::resetToDefaults()
{
m_displayData = GLScope::ModeIQ;
m_displayOrientation = Qt::Horizontal;
m_timeBase = 1;
m_timeOffset = 0;
m_amplification = 0;
applySettings();
}
QByteArray ScopeWindow::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_displayData);
s.writeS32(2, m_displayOrientation);
s.writeS32(3, m_timeBase);
s.writeS32(4, m_timeOffset);
s.writeS32(5, m_amplification);
return s.final();
}
bool ScopeWindow::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid()) {
resetToDefaults();
return false;
}
if(d.getVersion() == 1) {
d.readS32(1, &m_displayData, GLScope::ModeIQ);
d.readS32(2, &m_displayOrientation, Qt::Horizontal);
d.readS32(3, &m_timeBase, 1);
d.readS32(4, &m_timeOffset, 0);
d.readS32(5, &m_amplification, 0);
if(m_timeBase < 0)
m_timeBase = 1;
applySettings();
return true;
} else {
resetToDefaults();
return false;
}
}
void ScopeWindow::on_amp_valueChanged(int value)
{
static qreal amps[11] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001 };
ui->ampText->setText(tr("%1\n/div").arg(amps[value], 0, 'f', 4));
ui->scope->setAmp(0.2 / amps[value]);
m_amplification = value;
}
void ScopeWindow::on_scope_traceSizeChanged(int)
{
qreal t = (ui->scope->getTraceSize() * 0.1 / m_sampleRate) / (qreal)m_timeBase;
if(t < 0.000001)
ui->timeText->setText(tr("%1\nns/div").arg(t * 1000000000.0));
else if(t < 0.001)
ui->timeText->setText(tr("%1\nµs/div").arg(t * 1000000.0));
else if(t < 1.0)
ui->timeText->setText(tr("%1\nms/div").arg(t * 1000.0));
else ui->timeText->setText(tr("%1\ns/div").arg(t * 1.0));
}
void ScopeWindow::on_time_valueChanged(int value)
{
m_timeBase = value;
on_scope_traceSizeChanged(0);
ui->scope->setTimeBase(m_timeBase);
}
void ScopeWindow::on_timeOfs_valueChanged(int value)
{
m_timeOffset = value;
ui->scope->setTimeOfsProMill(value);
}
void ScopeWindow::on_displayMode_currentIndexChanged(int index)
{
m_displayData = index;
switch(index) {
case 0: // i+q
ui->scope->setMode(GLScope::ModeIQ);
break;
case 1: // mag(lin)+pha
ui->scope->setMode(GLScope::ModeMagLinPha);
break;
case 2: // mag(dB)+pha
ui->scope->setMode(GLScope::ModeMagdBPha);
break;
case 3: // derived1+derived2
ui->scope->setMode(GLScope::ModeDerived12);
break;
case 4: // clostationary
ui->scope->setMode(GLScope::ModeCyclostationary);
break;
default:
break;
}
}
void ScopeWindow::on_horizView_clicked()
{
m_displayOrientation = Qt::Horizontal;
if(ui->horizView->isChecked()) {
ui->vertView->setChecked(false);
ui->scope->setOrientation(Qt::Horizontal);
} else {
ui->horizView->setChecked(true);
}
}
void ScopeWindow::on_vertView_clicked()
{
m_displayOrientation = Qt::Vertical;
if(ui->vertView->isChecked()) {
ui->horizView->setChecked(false);
ui->scope->setOrientation(Qt::Vertical);
} else {
ui->vertView->setChecked(true);
ui->scope->setOrientation(Qt::Vertical);
}
}
void ScopeWindow::applySettings()
{
ui->displayMode->setCurrentIndex(m_displayData);
if(m_displayOrientation == Qt::Horizontal) {
ui->scope->setOrientation(Qt::Horizontal);
ui->horizView->setChecked(true);
ui->vertView->setChecked(false);
} else {
ui->scope->setOrientation(Qt::Vertical);
ui->horizView->setChecked(false);
ui->vertView->setChecked(true);
}
ui->time->setValue(m_timeBase);
ui->timeOfs->setValue(m_timeOffset);
ui->amp->setValue(m_amplification);
}
+320
View File
@@ -0,0 +1,320 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScopeWindow</class>
<widget class="QWidget" name="ScopeWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>798</width>
<height>149</height>
</rect>
</property>
<property name="windowTitle">
<string>Oscilloscope</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="GLScope" name="scope" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Data</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="displayMode">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContentsOnFirstShow</enum>
</property>
<item>
<property name="text">
<string>I+Q Level (linear)</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude (linear) + Phase</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude (dB) + Phase</string>
</property>
</item>
<item>
<property name="text">
<string>Derived 1st + 2nd order</string>
</property>
</item>
<item>
<property name="text">
<string>Cyclostationary</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="horizView">
<property name="toolTip">
<string>Horizontal display arrangement</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/horizontal.png</normaloff>:/horizontal.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="vertView">
<property name="toolTip">
<string>Vertical display arrangement</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/res.qrc">
<normaloff>:/vertical.png</normaloff>:/vertical.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="time">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>5</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0.1000
/div</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="timeOfs">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::NoTicks</enum>
</property>
<property name="tickInterval">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Amp</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="amp">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>8</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ampText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0.2000
/div</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GLScope</class>
<extends>QWidget</extends>
<header>gui/glscope.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/res.qrc"/>
</resources>
<connections/>
</ui>
+427
View File
@@ -0,0 +1,427 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QPainter>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QKeyEvent>
#include "gui/valuedial.h"
ValueDial::ValueDial(QWidget* parent) :
QWidget(parent),
m_animationState(0)
{
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_NoSystemBackground, true);
setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus);
m_background.setStart(0, 0);
m_background.setFinalStop(0, 1);
m_background.setCoordinateMode(QGradient::ObjectBoundingMode);
m_background.setColorAt(0.0, QColor(0x40, 0x40, 0x40));
m_background.setColorAt(0.1, QColor(0xc0, 0xc0, 0xc0));
m_background.setColorAt(0.2, QColor(0xf0, 0xf0, 0xf0));
m_background.setColorAt(0.5, QColor(0xff, 0xff, 0xff));
m_background.setColorAt(0.8, QColor(0xd0, 0xd0, 0xd0));
m_background.setColorAt(0.9, QColor(0xa0, 0xa0, 0xa0));
m_background.setColorAt(1.0, QColor(0x40, 0x40, 0x40));
m_value = 0;
m_valueMin = 0;
m_valueMax = 2200000;
m_numDigits = 7;
m_numDecimalPoints = m_numDigits / 3;
m_cursor = -1;
m_hightlightedDigit = -1;
m_text = formatText(m_value);
m_cursorState = false;
connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
connect(&m_blinkTimer, SIGNAL(timeout()), this, SLOT(blink()));
}
void ValueDial::setFont(const QFont& font)
{
QWidget::setFont(font);
QFontMetrics fm(font);
m_digitWidth = fm.width('0');
m_digitHeight = fm.ascent();
if(m_digitWidth < m_digitHeight)
m_digitWidth = m_digitHeight;
setFixedWidth((m_numDigits + m_numDecimalPoints) * m_digitWidth + 2);
setFixedHeight(m_digitHeight * 2 + 2);
}
void ValueDial::setValue(quint64 value)
{
m_valueNew = value;
if(m_valueNew < m_valueMin)
m_valueNew = m_valueMin;
else if(m_valueNew > m_valueMax)
m_valueNew = m_valueMax;
if(m_valueNew < m_value)
m_animationState = 1;
else if(m_valueNew > m_value)
m_animationState = -1;
else return;
m_animationTimer.start(20);
m_textNew = formatText(m_valueNew);
}
void ValueDial::setValueRange(uint numDigits, quint64 min, quint64 max)
{
m_numDigits = numDigits;
m_numDecimalPoints = m_numDigits / 3;
m_valueMin = min;
m_valueMax = max;
if(m_value < min)
setValue(min);
else if(m_value > max)
setValue(max);
setFixedWidth((m_numDigits + m_numDecimalPoints) * m_digitWidth + 2);
}
quint64 ValueDial::findExponent(int digit)
{
quint64 e = 1;
int d = (m_numDigits + m_numDecimalPoints) - digit;
d = d - (d / 4) - 1;
for(int i = 0; i < d; i++)
e *= 10;
return e;
}
QChar ValueDial::digitNeigh(QChar c, bool dir)
{
if(dir) {
if(c == QChar('0'))
return QChar('9');
else return QChar::fromLatin1(c.toLatin1() - 1);
} else {
if(c == QChar('9'))
return QChar('0');
else return QChar::fromLatin1(c.toLatin1() + 1);
}
}
QString ValueDial::formatText(quint64 value)
{
QString str = QString("%1").arg(value, m_numDigits, 10, QChar('0'));
for(int i = 0; i < m_numDecimalPoints; i++)
str.insert(m_numDigits - 3 - 3 * i, ".");
return str;
}
void ValueDial::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setPen(Qt::black);
painter.setBrush(m_background);
painter.drawRect(0, 0, width() - 1, height() - 1);
painter.setPen(QColor(0x20, 0x20, 0x20));
painter.setBrush(Qt::NoBrush);
for(int i = 1; i < m_numDigits + m_numDecimalPoints; i++) {
painter.setPen(QColor(0x20, 0x20, 0x20));
painter.drawLine(1 + i * m_digitWidth, 1, 1 + i * m_digitWidth, height() - 1);
painter.setPen(QColor(0x00, 0x00, 0x00, 0x20));
painter.drawLine(0 + i * m_digitWidth, 1, 0 + i * m_digitWidth, height() - 1);
painter.drawLine(2 + i * m_digitWidth, 1, 2 + i * m_digitWidth, height() - 1);
}
painter.setPen(QColor(0x00, 0x00, 0x00, 0x20));
painter.drawLine(1, 1, 1, height() - 1);
painter.drawLine(width() - 2, 1, width() - 2, height() - 1);
if(m_hightlightedDigit >= 0) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0xff, 0x00, 0x00, 0x20));
painter.drawRect(2 + m_hightlightedDigit * m_digitWidth, 1, m_digitWidth - 1, height() - 1);
}
if(m_animationState == 0) {
for(int i = 0; i < m_text.length(); i++) {
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
painter.setPen(QColor(0x10, 0x10, 0x10));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 0.6, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
if(m_text[i] != QChar('.')) {
painter.setPen(QColor(0x00, 0x00, 0x00));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
}
}
painter.setClipping(false);
if((m_cursor >= 0) && (m_cursorState)) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0x10, 0x10, 0x10));
painter.drawRect(4 + m_cursor * m_digitWidth, 1 + m_digitHeight * 1.5, m_digitWidth - 5, m_digitHeight / 6);
}
} else {
if(m_animationState != 0) {
for(int i = 0; i < m_text.length(); i++) {
if(m_text[i] == m_textNew[i]) {
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
painter.setPen(QColor(0x10, 0x10, 0x10));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 0.6, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
if(m_text[i] != QChar('.')) {
painter.setPen(QColor(0x00, 0x00, 0x00));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
painter.drawText(QRect(1 + i * m_digitWidth, m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
}
} else {
int h = m_digitHeight * 0.6 + m_digitHeight * m_animationState / 2.0;
painter.setClipRect(1 + i * m_digitWidth, 1, m_digitWidth, m_digitHeight * 2);
painter.setPen(QColor(0x10, 0x10, 0x10));
painter.drawText(QRect(1 + i * m_digitWidth, h, m_digitWidth, m_digitHeight), Qt::AlignCenter, m_text.mid(i, 1));
if(m_text[i] != QChar('.')) {
painter.setPen(QColor(0x00, 0x00, 0x00));
painter.drawText(QRect(1 + i * m_digitWidth, h + m_digitHeight * -0.7, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], true));
painter.drawText(QRect(1 + i * m_digitWidth, h + m_digitHeight * 1.9, m_digitWidth, m_digitHeight), Qt::AlignCenter, digitNeigh(m_text[i], false));
}
}
}
}
}
}
void ValueDial::mousePressEvent(QMouseEvent* event)
{
int i;
i = (event->x() - 1) / m_digitWidth;
if(m_text[i] == QChar('.')) {
i++;
if(i > m_numDigits + m_numDecimalPoints)
return;
}
m_cursor = i;
m_cursorState = true;
m_blinkTimer.start(400);
update();
}
void ValueDial::mouseMoveEvent(QMouseEvent* event)
{
int i;
i = (event->x() - 1) / m_digitWidth;
if(m_text[i] == QChar('.'))
i = -1;
if(i != m_hightlightedDigit) {
m_hightlightedDigit = i;
update();
}
}
void ValueDial::wheelEvent(QWheelEvent* event)
{
int i;
i = (event->x() - 1) / m_digitWidth;
if(m_text[i] != QChar('.'))
m_hightlightedDigit = i;
else
return;
if(m_cursor >= 0) {
m_cursor = -1;
m_blinkTimer.stop();
update();
}
quint64 e = findExponent(m_hightlightedDigit);
if(m_animationState == 0) {
if(event->delta() < 0) {
if(event->modifiers() & Qt::ShiftModifier)
e *= 5;
if(m_value < e)
m_valueNew = m_valueMin;
else m_valueNew = m_value - e;
} else {
if(event->modifiers() & Qt::ShiftModifier)
e *= 5;
if(m_valueMax - m_value < e)
m_valueNew = m_valueMax;
else m_valueNew = m_value + e;
}
setValue(m_valueNew);
emit changed(m_valueNew);
}
}
void ValueDial::leaveEvent(QEvent*)
{
if(m_hightlightedDigit != -1) {
m_hightlightedDigit = -1;
update();
}
}
void ValueDial::keyPressEvent(QKeyEvent* value)
{
if(m_cursor >= 0) {
if((value->key() == Qt::Key_Return) || (value->key() == Qt::Key_Enter) || (value->key() == Qt::Key_Escape)) {
m_cursor = -1;
m_cursorState = false;
m_blinkTimer.stop();
update();
return;
}
}
if((m_cursor < 0) && (m_hightlightedDigit >= 0)) {
m_cursor = m_hightlightedDigit;
if(m_text[m_cursor] == QChar('.'))
m_cursor++;
if(m_cursor >= m_numDigits + m_numDecimalPoints)
return;
m_cursorState = true;
m_blinkTimer.start(400);
update();
}
if(m_cursor < 0)
return;
if((value->key() == Qt::Key_Left) || (value->key() == Qt::Key_Backspace)) {
if(m_cursor > 0) {
m_cursor--;
if(m_text[m_cursor] == QChar('.'))
m_cursor--;
if(m_cursor < 0)
m_cursor++;
m_cursorState = true;
update();
return;
}
} else if(value->key() == Qt::Key_Right) {
if(m_cursor < m_numDecimalPoints + m_numDigits) {
m_cursor++;
if(m_text[m_cursor] == QChar('.'))
m_cursor++;
if(m_cursor >= m_numDecimalPoints + m_numDigits)
m_cursor--;
m_cursorState = true;
update();
return;
}
} else if(value->key() == Qt::Key_Up) {
quint64 e = findExponent(m_cursor);
if(value->modifiers() & Qt::ShiftModifier)
e *= 5;
if(m_animationState != 0)
m_value = m_valueNew;
if(m_valueMax - m_value < e)
m_valueNew = m_valueMax;
else m_valueNew = m_value + e;
setValue(m_valueNew);
emit changed(m_valueNew);
} else if(value->key() == Qt::Key_Down) {
quint64 e = findExponent(m_cursor);
if(value->modifiers() & Qt::ShiftModifier)
e *= 5;
if(m_animationState != 0)
m_value = m_valueNew;
if(m_value < e)
m_valueNew = m_valueMin;
else m_valueNew = m_value - e;
setValue(m_valueNew);
emit changed(m_valueNew);
}
if(value->text().length() != 1)
return;
QChar c = value->text()[0];
if(c >= QChar('0') && (c <= QChar('9'))) {
int d = c.toLatin1() - '0';
quint64 e = findExponent(m_cursor);
quint64 v = (m_value / e) % 10;
if(m_animationState != 0)
m_value = m_valueNew;
v = m_value - v * e;
v += d * e;
setValue(v);
emit changed(m_valueNew);
m_cursor++;
if(m_text[m_cursor] == QChar('.'))
m_cursor++;
if(m_cursor >= m_numDigits + m_numDecimalPoints) {
m_cursor = -1;
m_blinkTimer.stop();
} else {
m_cursorState = true;
}
update();
}
}
void ValueDial::focusInEvent(QFocusEvent*)
{
if(m_cursor == -1) {
m_cursor = 0;
m_cursorState = true;
m_blinkTimer.start(400);
update();
}
}
void ValueDial::focusOutEvent(QFocusEvent*)
{
m_cursor = -1;
m_blinkTimer.stop();
update();
}
void ValueDial::animate()
{
update();
if(m_animationState > 0)
m_animationState++;
else if(m_animationState < 0)
m_animationState--;
else {
m_animationTimer.stop();
m_animationState = 0;
return;
}
if(abs(m_animationState) >= 4) {
m_animationState = 0;
m_animationTimer.stop();
m_value = m_valueNew;
m_text = m_textNew;
}
}
void ValueDial::blink()
{
if(m_cursor >= 0) {
m_cursorState = !m_cursorState;
update();
}
}
+526
View File
@@ -0,0 +1,526 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QInputDialog>
#include <QMessageBox>
#include <QLabel>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "audio/audiodeviceinfo.h"
#include "gui/indicator.h"
#include "gui/presetitem.h"
#include "gui/scopewindow.h"
#include "gui/addpresetdialog.h"
#include "gui/pluginsdialog.h"
#include "gui/preferencesdialog.h"
#include "gui/aboutdialog.h"
#include "gui/rollupwidget.h"
#include "dsp/dspengine.h"
#include "dsp/spectrumvis.h"
#include "dsp/dspcommands.h"
#include "plugin/plugingui.h"
#include "plugin/pluginapi.h"
#include "plugin/plugingui.h"
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
m_audioDeviceInfo(new AudioDeviceInfo),
m_messageQueue(new MessageQueue),
m_settings(),
m_dspEngine(new DSPEngine(m_messageQueue)),
m_lastEngineState((DSPEngine::State)-1),
m_startOsmoSDRUpdateAfterStop(false),
m_scopeWindow(NULL),
m_inputGUI(NULL),
m_sampleRate(0),
m_centerFrequency(0),
m_pluginManager(new PluginManager(this, m_dspEngine))
{
ui->setupUi(this);
delete ui->mainToolBar;
createStatusBar();
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
// work around broken Qt dock widget ordering
removeDockWidget(ui->inputDock);
removeDockWidget(ui->processingDock);
removeDockWidget(ui->presetDock);
removeDockWidget(ui->channelDock);
addDockWidget(Qt::LeftDockWidgetArea, ui->inputDock);
addDockWidget(Qt::LeftDockWidgetArea, ui->processingDock);
addDockWidget(Qt::LeftDockWidgetArea, ui->presetDock);
addDockWidget(Qt::RightDockWidgetArea, ui->channelDock);
ui->inputDock->show();
ui->processingDock->show();
ui->presetDock->show();
ui->channelDock->show();
ui->menu_Window->addAction(ui->inputDock->toggleViewAction());
ui->menu_Window->addAction(ui->processingDock->toggleViewAction());
ui->menu_Window->addAction(ui->presetDock->toggleViewAction());
ui->menu_Window->addAction(ui->channelDock->toggleViewAction());
connect(m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleMessages()), Qt::QueuedConnection);
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(500);
m_pluginManager->loadPlugins();
bool sampleSourceSignalsBlocked = ui->sampleSource->blockSignals(true);
m_pluginManager->fillSampleSourceSelector(ui->sampleSource);
ui->sampleSource->blockSignals(sampleSourceSignalsBlocked);
m_dspEngine->start();
m_spectrumVis = new SpectrumVis(ui->glSpectrum);
m_dspEngine->addSink(m_spectrumVis);
ui->glSpectrumGUI->setBuddies(m_dspEngine->getMessageQueue(), m_spectrumVis, ui->glSpectrum);
loadSettings();
int sampleSourceIndex = m_pluginManager->selectSampleSource(m_settings.getCurrent()->getSource()); // select SampleSource from settings
if(sampleSourceIndex >= 0) {
bool sampleSourceSignalsBlocked = ui->sampleSource->blockSignals(true);
ui->sampleSource->setCurrentIndex(sampleSourceIndex);
ui->sampleSource->blockSignals(sampleSourceSignalsBlocked);
}
loadSettings(m_settings.getCurrent());
applySettings();
updatePresets();
}
MainWindow::~MainWindow()
{
m_dspEngine->stopAcquistion();
saveSettings();
m_pluginManager->freeAll();
m_dspEngine->removeSink(m_spectrumVis);
delete m_spectrumVis;
if(m_scopeWindow != NULL) {
delete m_scopeWindow;
m_scopeWindow = NULL;
}
delete m_pluginManager;
m_dspEngine->stop();
delete m_dspEngine;
delete m_messageQueue;
delete ui;
}
void MainWindow::addChannelCreateAction(QAction* action)
{
ui->menu_Channels->addAction(action);
}
void MainWindow::addChannelRollup(QWidget* widget)
{
((ChannelWindow*)ui->channelDock->widget())->addRollupWidget(widget);
ui->channelDock->show();
ui->channelDock->raise();
}
void MainWindow::addViewAction(QAction* action)
{
ui->menu_Window->addAction(action);
}
void MainWindow::addChannelMarker(ChannelMarker* channelMarker)
{
ui->glSpectrum->addChannelMarker(channelMarker);
}
void MainWindow::removeChannelMarker(ChannelMarker* channelMarker)
{
ui->glSpectrum->removeChannelMarker(channelMarker);
}
void MainWindow::setInputGUI(QWidget* gui)
{
if(m_inputGUI != NULL)
ui->inputDock->widget()->layout()->removeWidget(m_inputGUI);
if(gui != NULL)
ui->inputDock->widget()->layout()->addWidget(gui);
m_inputGUI = gui;
}
void MainWindow::loadSettings()
{
m_settings.load();
for(int i = 0; i < m_settings.getPresetCount(); ++i)
addPresetToTree(m_settings.getPreset(i));
Preset* current = m_settings.getCurrent();
loadSettings(current);
}
void MainWindow::loadSettings(const Preset* preset)
{
if(preset->getShowScope()) {
on_action_Oscilloscope_triggered();
m_scopeWindow->deserialize(preset->getScopeConfig());
}
ui->glSpectrumGUI->deserialize(preset->getSpectrumConfig());
ui->dcOffset->setChecked(preset->getDCOffsetCorrection());
ui->iqImbalance->setChecked(preset->getIQImbalanceCorrection());
m_pluginManager->loadSettings(preset);
// has to be last step
restoreState(preset->getLayout());
}
void MainWindow::saveSettings()
{
saveSettings(m_settings.getCurrent());
m_settings.save();
}
void MainWindow::saveSettings(Preset* preset)
{
preset->setSpectrumConfig(ui->glSpectrumGUI->serialize());
if(preset->getShowScope())
preset->setScopeConfig(m_scopeWindow->serialize());
else preset->setScopeConfig(QByteArray());
preset->clearChannels();
m_pluginManager->saveSettings(preset);
preset->setLayout(saveState());
}
void MainWindow::createStatusBar()
{
m_sampleRateWidget = new QLabel(tr("Rate: 0 kHz"), this);
m_sampleRateWidget->setToolTip(tr("Sample Rate"));
statusBar()->addPermanentWidget(m_sampleRateWidget);
m_engineIdle = new Indicator(tr("Idle"), this);
m_engineIdle->setToolTip(tr("DSP engine is idle"));
statusBar()->addPermanentWidget(m_engineIdle);
m_engineRunning = new Indicator(tr("Run"), this);
m_engineRunning->setToolTip(tr("DSP engine is running"));
statusBar()->addPermanentWidget(m_engineRunning);
m_engineError = new Indicator(tr("Err"), this);
m_engineError->setToolTip(tr("DSP engine failed"));
statusBar()->addPermanentWidget(m_engineError);
}
void MainWindow::closeEvent(QCloseEvent*)
{
}
void MainWindow::updateCenterFreqDisplay()
{
ui->glSpectrum->setCenterFrequency(m_centerFrequency);
}
void MainWindow::updateSampleRate()
{
ui->glSpectrum->setSampleRate(m_sampleRate);
m_sampleRateWidget->setText(tr("Rate: %1 kHz").arg((float)m_sampleRate / 1000));
if(m_scopeWindow != NULL)
m_scopeWindow->setSampleRate(m_sampleRate);
}
void MainWindow::updatePresets()
{
ui->presetTree->resizeColumnToContents(0);
if(ui->presetTree->currentItem() != NULL) {
ui->presetDelete->setEnabled(true);
ui->presetLoad->setEnabled(true);
} else {
ui->presetDelete->setEnabled(false);
ui->presetLoad->setEnabled(false);
}
}
QTreeWidgetItem* MainWindow::addPresetToTree(const Preset* preset)
{
QTreeWidgetItem* group = NULL;
for(int i = 0; i < ui->presetTree->topLevelItemCount(); i++) {
if(ui->presetTree->topLevelItem(i)->text(0) == preset->getGroup()) {
group = ui->presetTree->topLevelItem(i);
break;
}
}
if(group == NULL) {
QStringList sl;
sl.append(preset->getGroup());
group = new QTreeWidgetItem(ui->presetTree, sl, PGroup);
group->setFirstColumnSpanned(true);
group->setExpanded(true);
ui->presetTree->sortByColumn(0, Qt::AscendingOrder);
}
QStringList sl;
sl.append(QString("%1 kHz").arg(preset->getCenterFrequency() / 1000));
sl.append(preset->getDescription());
PresetItem* item = new PresetItem(group, sl, preset->getCenterFrequency(), PItem);
item->setTextAlignment(0, Qt::AlignRight);
item->setData(0, Qt::UserRole, qVariantFromValue(preset));
ui->presetTree->resizeColumnToContents(0);
updatePresets();
return item;
}
void MainWindow::applySettings()
{
updateCenterFreqDisplay();
updateSampleRate();
}
void MainWindow::handleMessages()
{
Message* message;
while((message = m_messageQueue->accept()) != NULL) {
qDebug("Message: %s", message->getIdentifier());
if(DSPEngineReport::match(message)) {
DSPEngineReport* rep = (DSPEngineReport*)message;
m_sampleRate = rep->getSampleRate();
m_centerFrequency = rep->getCenterFrequency();
qDebug("SampleRate:%d, CenterFrequency:%llu", rep->getSampleRate(), rep->getCenterFrequency());
updateCenterFreqDisplay();
updateSampleRate();
message->completed();
} else {
if(!m_pluginManager->handleMessage(message))
message->completed();
}
}
}
void MainWindow::updateStatus()
{
int state = m_dspEngine->state();
if(m_lastEngineState != state) {
switch(state) {
case DSPEngine::StNotStarted:
m_engineIdle->setColor(Qt::gray);
m_engineRunning->setColor(Qt::gray);
m_engineError->setColor(Qt::gray);
statusBar()->clearMessage();
break;
case DSPEngine::StIdle:
m_engineIdle->setColor(Qt::cyan);
m_engineRunning->setColor(Qt::gray);
m_engineError->setColor(Qt::gray);
statusBar()->clearMessage();
if(m_startOsmoSDRUpdateAfterStop)
on_actionOsmoSDR_Firmware_Upgrade_triggered();
break;
case DSPEngine::StRunning:
m_engineIdle->setColor(Qt::gray);
m_engineRunning->setColor(Qt::green);
m_engineError->setColor(Qt::gray);
statusBar()->showMessage(tr("Sampling from %1").arg(m_dspEngine->deviceDescription()));
break;
case DSPEngine::StError:
m_engineIdle->setColor(Qt::gray);
m_engineRunning->setColor(Qt::gray);
m_engineError->setColor(Qt::red);
statusBar()->showMessage(tr("Error: %1").arg(m_dspEngine->errorMessage()));
if(m_startOsmoSDRUpdateAfterStop)
on_actionOsmoSDR_Firmware_Upgrade_triggered();
break;
}
m_lastEngineState = state;
}
}
void MainWindow::scopeWindowDestroyed()
{
ui->action_Oscilloscope->setChecked(false);
m_settings.getCurrent()->setShowScope(false);
m_scopeWindow = NULL;
}
void MainWindow::on_action_Start_triggered()
{
m_dspEngine->startAcquisition();
}
void MainWindow::on_action_Stop_triggered()
{
m_dspEngine->stopAcquistion();
}
void MainWindow::on_dcOffset_toggled(bool checked)
{
m_settings.getCurrent()->setDCOffsetCorrection(checked);
m_dspEngine->configureCorrections(m_settings.getCurrent()->getDCOffsetCorrection(), m_settings.getCurrent()->getIQImbalanceCorrection());
}
void MainWindow::on_iqImbalance_toggled(bool checked)
{
m_settings.getCurrent()->setIQImbalanceCorrection(checked);
m_dspEngine->configureCorrections(m_settings.getCurrent()->getDCOffsetCorrection(), m_settings.getCurrent()->getIQImbalanceCorrection());
}
void MainWindow::on_action_View_Fullscreen_toggled(bool checked)
{
if(checked)
showFullScreen();
else showNormal();
}
void MainWindow::on_actionOsmoSDR_Firmware_Upgrade_triggered()
{
DSPEngine::State engineState = m_dspEngine->state();
if((engineState != DSPEngine::StIdle) && (engineState != DSPEngine::StError)) {
m_startOsmoSDRUpdateAfterStop = true;
m_dspEngine->stopAcquistion();
return;
}
m_startOsmoSDRUpdateAfterStop = false;
/* TODO: osmosdr upgrade
OsmoSDRUpgrade osmoSDRUpgrade;
osmoSDRUpgrade.exec();
*/
}
void MainWindow::on_presetSave_clicked()
{
QStringList groups;
QString group;
for(int i = 0; i < ui->presetTree->topLevelItemCount(); i++)
groups.append(ui->presetTree->topLevelItem(i)->text(0));
QTreeWidgetItem* item = ui->presetTree->currentItem();
if(item != NULL) {
if(item->type() == PGroup)
group = item->text(0);
else if(item->type() == PItem)
group = item->parent()->text(0);
}
AddPresetDialog dlg(groups, group, this);
if(dlg.exec() == QDialog::Accepted) {
Preset* preset = m_settings.newPreset(dlg.group(), dlg.description());
saveSettings(preset);
ui->presetTree->setCurrentItem(addPresetToTree(preset));
}
}
void MainWindow::on_presetLoad_clicked()
{
QTreeWidgetItem* item = ui->presetTree->currentItem();
if(item == NULL) {
updatePresets();
return;
}
const Preset* preset = qvariant_cast<const Preset*>(item->data(0, Qt::UserRole));
if(preset == NULL)
return;
loadSettings(preset);
applySettings();
}
void MainWindow::on_presetDelete_clicked()
{
QTreeWidgetItem* item = ui->presetTree->currentItem();
if(item == NULL) {
updatePresets();
return;
}
const Preset* preset = qvariant_cast<const Preset*>(item->data(0, Qt::UserRole));
if(preset == NULL)
return;
if(QMessageBox::question(this, tr("Delete Preset"), tr("Do you want to delete preset '%1'?").arg(preset->getDescription()), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
delete item;
m_settings.deletePreset(preset);
}
}
void MainWindow::on_presetTree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
updatePresets();
}
void MainWindow::on_presetTree_itemActivated(QTreeWidgetItem *item, int column)
{
on_presetLoad_clicked();
}
void MainWindow::on_action_Oscilloscope_triggered()
{
if(m_scopeWindow != NULL) {
((QWidget*)m_scopeWindow->parent())->raise();
return;
}
QDockWidget* dock = new QDockWidget(tr("Signalscope"), this);
dock->setObjectName(QString::fromUtf8("scopeDock"));
m_scopeWindow = new ScopeWindow(m_dspEngine);
connect(m_scopeWindow, SIGNAL(destroyed()), this, SLOT(scopeWindowDestroyed()));
m_scopeWindow->setSampleRate(m_sampleRate);
dock->setWidget(m_scopeWindow);
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
addDockWidget(Qt::BottomDockWidgetArea, dock);
dock->setAttribute(Qt::WA_DeleteOnClose);
m_settings.getCurrent()->setShowScope(true);
}
void MainWindow::on_action_Loaded_Plugins_triggered()
{
PluginsDialog pluginsDialog(m_pluginManager, this);
pluginsDialog.exec();
}
void MainWindow::on_action_Preferences_triggered()
{
PreferencesDialog preferencesDialog(m_audioDeviceInfo, this);
preferencesDialog.exec();
}
void MainWindow::on_sampleSource_currentIndexChanged(int index)
{
m_pluginManager->selectSampleSource(ui->sampleSource->currentIndex());
}
void MainWindow::on_action_About_triggered()
{
AboutDialog dlg(this);
dlg.exec();
}
+483
View File
@@ -0,0 +1,483 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1012</width>
<height>706</height>
</rect>
</property>
<property name="windowTitle">
<string>SDRangelove</string>
</property>
<property name="windowIcon">
<iconset resource="resources/res.qrc">
<normaloff>:/appicon.png</normaloff>:/appicon.png</iconset>
</property>
<property name="animated">
<bool>true</bool>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="GLSpectrum" name="glSpectrum" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1012</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="action_Exit"/>
</widget>
<widget class="QMenu" name="menu_Options">
<property name="title">
<string>&amp;Options</string>
</property>
<addaction name="actionOsmoSDR_Firmware_Upgrade"/>
<addaction name="separator"/>
<addaction name="action_Preferences"/>
</widget>
<widget class="QMenu" name="menu_Acquisition">
<property name="title">
<string>&amp;Acquisition</string>
</property>
<addaction name="action_Start"/>
<addaction name="action_Stop"/>
</widget>
<widget class="QMenu" name="menu_View">
<property name="title">
<string>&amp;View</string>
</property>
<addaction name="action_View_Fullscreen"/>
<addaction name="separator"/>
<addaction name="action_Oscilloscope"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="action_Open_Website"/>
<addaction name="action_Check_for_Update"/>
<addaction name="separator"/>
<addaction name="action_Loaded_Plugins"/>
<addaction name="separator"/>
<addaction name="action_About"/>
</widget>
<widget class="QMenu" name="menu_Channels">
<property name="title">
<string>&amp;Channels</string>
</property>
</widget>
<widget class="QMenu" name="menu_Window">
<property name="title">
<string>&amp;Window</string>
</property>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_View"/>
<addaction name="menu_Acquisition"/>
<addaction name="menu_Channels"/>
<addaction name="menu_Options"/>
<addaction name="menu_Window"/>
<addaction name="menu_Help"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QDockWidget" name="presetDock">
<property name="windowTitle">
<string>Presets</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="gridLayout_6">
<property name="margin">
<number>2</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="5" column="1">
<widget class="QToolButton" name="presetSave">
<property name="toolTip">
<string>Save current settings as preset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="resources/res.qrc">
<normaloff>:/preset-save.png</normaloff>:/preset-save.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item row="5" column="5">
<widget class="QToolButton" name="presetLoad">
<property name="toolTip">
<string>Load selected preset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="resources/res.qrc">
<normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item row="5" column="3" colspan="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="2">
<widget class="QToolButton" name="presetDelete">
<property name="toolTip">
<string>Delete selected preset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="resources/res.qrc">
<normaloff>:/preset-delete.png</normaloff>:/preset-delete.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1" rowspan="4" colspan="5">
<widget class="QTreeWidget" name="presetTree">
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Frequency</string>
</property>
</column>
<column>
<property name="text">
<string>Description</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="processingDock">
<property name="windowTitle">
<string>Processing and Display</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<item>
<widget class="ButtonSwitch" name="dcOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Compensate DC offset</string>
</property>
<property name="text">
<string>DC Offset Corr</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="iqImbalance">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Compensate I/Q imbalance</string>
</property>
<property name="text">
<string>I/Q Imbal. Corr</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="GLSpectrumGUI" name="glSpectrumGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="inputDock">
<property name="windowTitle">
<string>Sample Source</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>2</number>
</property>
<item>
<widget class="QComboBox" name="sampleSource">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="channelDock">
<property name="windowTitle">
<string>Channels</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="ChannelWindow" name="channelWindow">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>2</number>
</property>
</layout>
</widget>
</widget>
<action name="action_Exit">
<property name="text">
<string>E&amp;xit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="action_Start">
<property name="text">
<string>&amp;Start</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
<action name="action_Stop">
<property name="text">
<string>&amp;Stop</string>
</property>
<property name="shortcut">
<string>F6</string>
</property>
</action>
<action name="action_View_Fullscreen">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Fullscreen</string>
</property>
<property name="shortcut">
<string>F11</string>
</property>
</action>
<action name="actionOsmoSDR_Firmware_Upgrade">
<property name="text">
<string>OsmoSDR &amp;Firmware Upgrade...</string>
</property>
</action>
<action name="action_Oscilloscope">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Oscilloscope</string>
</property>
<property name="shortcut">
<string>F8</string>
</property>
</action>
<action name="action_About">
<property name="text">
<string>&amp;About SDRangelove...</string>
</property>
</action>
<action name="action_Open_Website">
<property name="text">
<string>&amp;Open Website</string>
</property>
</action>
<action name="action_Check_for_Update">
<property name="text">
<string>Check for &amp;Update...</string>
</property>
</action>
<action name="action_Preferences">
<property name="text">
<string>&amp;Preferences...</string>
</property>
</action>
<action name="action_Demod_NFM">
<property name="text">
<string>N&amp;FM</string>
</property>
</action>
<action name="action_Tetra">
<property name="text">
<string>&amp;Tetra</string>
</property>
</action>
<action name="action_Loaded_Plugins">
<property name="text">
<string>Loaded &amp;Plugins...</string>
</property>
</action>
<zorder>presetDock</zorder>
<zorder>channelDock</zorder>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>GLSpectrum</class>
<extends>QWidget</extends>
<header>gui/glspectrum.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrumGUI</class>
<extends>QWidget</extends>
<header>gui/glspectrumgui.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ChannelWindow</class>
<extends>QWidget</extends>
<header>gui/channelwindow.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>presetTree</tabstop>
<tabstop>presetSave</tabstop>
<tabstop>presetDelete</tabstop>
<tabstop>presetLoad</tabstop>
<tabstop>dcOffset</tabstop>
<tabstop>iqImbalance</tabstop>
</tabstops>
<resources>
<include location="resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>action_Exit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>462</x>
<y>332</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+99
View File
@@ -0,0 +1,99 @@
#include <QDockWidget>
#include "plugin/pluginapi.h"
#include "plugin/pluginmanager.h"
#include "mainwindow.h"
#include "dsp/dspengine.h"
QDockWidget* PluginAPI::createMainWindowDock(Qt::DockWidgetArea dockWidgetArea, const QString& title)
{
QDockWidget* dock = new QDockWidget(title, m_mainWindow);
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
dock->setAttribute(Qt::WA_DeleteOnClose);
m_mainWindow->addDockWidget(dockWidgetArea, dock);
m_mainWindow->addViewAction(dock->toggleViewAction());
return dock;
}
MessageQueue* PluginAPI::getMainWindowMessageQueue()
{
return m_mainWindow->getMessageQueue();
}
void PluginAPI::setInputGUI(QWidget* inputGUI)
{
m_mainWindow->setInputGUI(inputGUI);
}
void PluginAPI::registerChannel(const QString& channelName, PluginInterface* plugin, QAction* action)
{
m_pluginManager->registerChannel(channelName, plugin, action);
}
void PluginAPI::registerChannelInstance(const QString& channelName, PluginGUI* pluginGUI)
{
m_pluginManager->registerChannelInstance(channelName, pluginGUI);
}
void PluginAPI::addChannelRollup(QWidget* pluginGUI)
{
m_pluginManager->addChannelRollup(pluginGUI);
}
void PluginAPI::removeChannelInstance(PluginGUI* pluginGUI)
{
m_pluginManager->removeChannelInstance(pluginGUI);
}
void PluginAPI::addChannelMarker(ChannelMarker* channelMarker)
{
m_mainWindow->addChannelMarker(channelMarker);
}
void PluginAPI::removeChannelMarker(ChannelMarker* channelMarker)
{
m_mainWindow->removeChannelMarker(channelMarker);
}
void PluginAPI::setSampleSource(SampleSource* sampleSource)
{
m_dspEngine->stopAcquistion();
m_dspEngine->setSource(sampleSource);
}
void PluginAPI::addSampleSink(SampleSink* sampleSink)
{
m_dspEngine->addSink(sampleSink);
}
void PluginAPI::removeSampleSink(SampleSink* sampleSink)
{
m_dspEngine->removeSink(sampleSink);
}
MessageQueue* PluginAPI::getDSPEngineMessageQueue()
{
return m_dspEngine->getMessageQueue();
}
void PluginAPI::addAudioSource(AudioFifo* audioFifo)
{
m_dspEngine->addAudioSource(audioFifo);
}
void PluginAPI::removeAudioSource(AudioFifo* audioFifo)
{
m_dspEngine->removeAudioSource(audioFifo);
}
void PluginAPI::registerSampleSource(const QString& sourceName, PluginInterface* plugin)
{
m_pluginManager->registerSampleSource(sourceName, plugin);
}
PluginAPI::PluginAPI(PluginManager* pluginManager, MainWindow* mainWindow, DSPEngine* dspEngine) :
QObject(mainWindow),
m_pluginManager(pluginManager),
m_mainWindow(mainWindow),
m_dspEngine(dspEngine)
{
}
+16
View File
@@ -0,0 +1,16 @@
#include "plugin/plugingui.h"
QByteArray PluginGUI::serializeGeneral() const
{
return QByteArray();
}
bool PluginGUI::deserializeGeneral(const QByteArray& data)
{
return false;
}
quint64 PluginGUI::getCenterFrequency() const
{
return 0;
}
+1
View File
@@ -0,0 +1 @@
#include "plugin/plugininterface.h"
+280
View File
@@ -0,0 +1,280 @@
#include <QApplication>
#include <QPluginLoader>
#include <QComboBox>
#include "plugin/pluginmanager.h"
#include "plugin/plugingui.h"
#include "settings/preset.h"
#include "mainwindow.h"
#include "dsp/dspengine.h"
#include "dsp/samplesource/samplesource.h"
PluginManager::PluginManager(MainWindow* mainWindow, DSPEngine* dspEngine, QObject* parent) :
QObject(parent),
m_pluginAPI(this, mainWindow, dspEngine),
m_mainWindow(mainWindow),
m_dspEngine(dspEngine),
m_sampleSource(),
m_sampleSourceInstance(NULL)
{
}
PluginManager::~PluginManager()
{
freeAll();
}
void PluginManager::loadPlugins()
{
QDir pluginsDir = QDir(QApplication::instance()->applicationDirPath());
loadPlugins(pluginsDir);
qSort(m_plugins);
for(Plugins::const_iterator it = m_plugins.begin(); it != m_plugins.end(); ++it)
it->plugin->initPlugin(&m_pluginAPI);
updateSampleSourceDevices();
}
void PluginManager::registerChannel(const QString& channelName, PluginInterface* plugin, QAction* action)
{
m_channelRegistrations.append(ChannelRegistration(channelName, plugin));
m_mainWindow->addChannelCreateAction(action);
}
void PluginManager::registerChannelInstance(const QString& channelName, PluginGUI* pluginGUI)
{
m_channelInstanceRegistrations.append(ChannelInstanceRegistration(channelName, pluginGUI));
renameChannelInstances();
}
void PluginManager::addChannelRollup(QWidget* pluginGUI)
{
m_mainWindow->addChannelRollup(pluginGUI);
}
void PluginManager::removeChannelInstance(PluginGUI* pluginGUI)
{
for(ChannelInstanceRegistrations::iterator it = m_channelInstanceRegistrations.begin(); it != m_channelInstanceRegistrations.end(); ++it) {
if(it->m_gui == pluginGUI) {
m_channelInstanceRegistrations.erase(it);
break;
}
}
renameChannelInstances();
}
void PluginManager::registerSampleSource(const QString& sourceName, PluginInterface* plugin)
{
m_sampleSourceRegistrations.append(SampleSourceRegistration(sourceName, plugin));
}
void PluginManager::loadSettings(const Preset* preset)
{
qDebug("-------- [%s | %s] --------", qPrintable(preset->getGroup()), qPrintable(preset->getDescription()));
// copy currently open channels and clear list
ChannelInstanceRegistrations openChannels = m_channelInstanceRegistrations;
m_channelInstanceRegistrations.clear();
for(int i = 0; i < preset->getChannelCount(); i++) {
const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i);
ChannelInstanceRegistration reg;
// if we have one instance available already, use it
for(int i = 0; i < openChannels.count(); i++) {
qDebug("compare [%s] vs [%s]", qPrintable(openChannels[i].m_channelName), qPrintable(channelConfig.m_channel));
if(openChannels[i].m_channelName == channelConfig.m_channel) {
qDebug("channel [%s] found", qPrintable(openChannels[i].m_channelName));
reg = openChannels.takeAt(i);
m_channelInstanceRegistrations.append(reg);
break;
}
}
// if we haven't one already, create one
if(reg.m_gui == NULL) {
for(int i = 0; i < m_channelRegistrations.count(); i++) {
if(m_channelRegistrations[i].m_channelName == channelConfig.m_channel) {
qDebug("creating new channel [%s]", qPrintable(channelConfig.m_channel));
reg = ChannelInstanceRegistration(channelConfig.m_channel, m_channelRegistrations[i].m_plugin->createChannel(channelConfig.m_channel));
break;
}
}
}
if(reg.m_gui != NULL)
reg.m_gui->deserialize(channelConfig.m_config);
}
// everything, that is still "available" is not needed anymore
for(int i = 0; i < openChannels.count(); i++) {
qDebug("destroying spare channel [%s]", qPrintable(openChannels[i].m_channelName));
openChannels[i].m_gui->destroy();
}
renameChannelInstances();
if(m_sampleSourceInstance != NULL) {
m_sampleSourceInstance->deserializeGeneral(preset->getSourceGeneralConfig());
if(m_sampleSource == preset->getSource()) {
m_sampleSourceInstance->deserialize(preset->getSourceConfig());
}
}
}
void PluginManager::saveSettings(Preset* preset) const
{
if(m_sampleSourceInstance != NULL) {
preset->setSourceConfig(m_sampleSource, m_sampleSourceInstance->serializeGeneral(), m_sampleSourceInstance->serialize());
preset->setCenterFrequency(m_sampleSourceInstance->getCenterFrequency());
} else {
preset->setSourceConfig(QString::null, QByteArray(), QByteArray());
}
for(int i = 0; i < m_channelInstanceRegistrations.count(); i++)
preset->addChannel(m_channelInstanceRegistrations[i].m_channelName, m_channelInstanceRegistrations[i].m_gui->serialize());
}
void PluginManager::freeAll()
{
m_dspEngine->stopAcquistion();
while(!m_channelInstanceRegistrations.isEmpty()) {
ChannelInstanceRegistration reg(m_channelInstanceRegistrations.takeLast());
reg.m_gui->destroy();
}
if(m_sampleSourceInstance != NULL) {
m_dspEngine->setSource(NULL);
m_sampleSourceInstance->destroy();
m_sampleSourceInstance = NULL;
m_sampleSource.clear();
}
}
bool PluginManager::handleMessage(Message* message)
{
if(m_sampleSourceInstance != NULL) {
if((message->getDestination() == NULL) || (message->getDestination() == m_sampleSourceInstance)) {
if(m_sampleSourceInstance->handleMessage(message))
return true;
}
}
for(ChannelInstanceRegistrations::iterator it = m_channelInstanceRegistrations.begin(); it != m_channelInstanceRegistrations.end(); ++it) {
if((message->getDestination() == NULL) || (message->getDestination() == it->m_gui)) {
if(it->m_gui->handleMessage(message))
return true;
}
}
return false;
}
void PluginManager::updateSampleSourceDevices()
{
m_sampleSourceDevices.clear();
for(int i = 0; i < m_sampleSourceRegistrations.count(); ++i) {
PluginInterface::SampleSourceDevices ssd = m_sampleSourceRegistrations[i].m_plugin->enumSampleSources();
for(int j = 0; j < ssd.count(); ++j)
m_sampleSourceDevices.append(SampleSourceDevice(m_sampleSourceRegistrations[i].m_plugin, ssd[j].displayedName, ssd[j].name, ssd[j].address));
}
}
void PluginManager::fillSampleSourceSelector(QComboBox* comboBox)
{
comboBox->clear();
for(int i = 0; i < m_sampleSourceDevices.count(); i++)
comboBox->addItem(m_sampleSourceDevices[i].m_displayName, i);
}
int PluginManager::selectSampleSource(int index)
{
m_dspEngine->stopAcquistion();
if(m_sampleSourceInstance != NULL) {
m_dspEngine->stopAcquistion();
m_dspEngine->setSource(NULL);
m_sampleSourceInstance->destroy();
m_sampleSourceInstance = NULL;
m_sampleSource.clear();
}
if(index == -1) {
if(!m_sampleSource.isEmpty()) {
for(int i = 0; i < m_sampleSourceDevices.count(); i++) {
if(m_sampleSourceDevices[i].m_sourceName == m_sampleSource) {
index = i;
break;
}
}
}
if(index == -1) {
if(m_sampleSourceDevices.count() > 0)
index = 0;
}
}
if(index == -1)
return -1;
m_sampleSource = m_sampleSourceDevices[index].m_sourceName;
m_sampleSourceInstance = m_sampleSourceDevices[index].m_plugin->createSampleSource(m_sampleSource, m_sampleSourceDevices[index].m_address);
return index;
}
int PluginManager::selectSampleSource(const QString& source)
{
int index = -1;
m_dspEngine->stopAcquistion();
if(m_sampleSourceInstance != NULL) {
m_dspEngine->stopAcquistion();
m_dspEngine->setSource(NULL);
m_sampleSourceInstance->destroy();
m_sampleSourceInstance = NULL;
m_sampleSource.clear();
}
qDebug("finding sample source [%s]", qPrintable(source));
for(int i = 0; i < m_sampleSourceDevices.count(); i++) {
qDebug("*** %s vs %s", qPrintable(m_sampleSourceDevices[i].m_sourceName), qPrintable(source));
if(m_sampleSourceDevices[i].m_sourceName == source) {
index = i;
break;
}
}
if(index == -1) {
if(m_sampleSourceDevices.count() > 0)
index = 0;
}
if(index == -1)
return -1;
m_sampleSource = m_sampleSourceDevices[index].m_sourceName;
m_sampleSourceInstance = m_sampleSourceDevices[index].m_plugin->createSampleSource(m_sampleSource, m_sampleSourceDevices[index].m_address);
return index;
}
void PluginManager::loadPlugins(const QDir& dir)
{
QDir pluginsDir(dir);
foreach(QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader* loader = new QPluginLoader(pluginsDir.absoluteFilePath(fileName));
PluginInterface* plugin = qobject_cast<PluginInterface*>(loader->instance());
if(loader->isLoaded())
qDebug("loaded plugin %s", qPrintable(fileName));
if(plugin != NULL) {
m_plugins.append(Plugin(fileName, loader, plugin));
} else {
loader->unload();
delete loader;
}
}
foreach(QString dirName, pluginsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
loadPlugins(pluginsDir.absoluteFilePath(dirName));
}
void PluginManager::renameChannelInstances()
{
for(int i = 0; i < m_channelInstanceRegistrations.count(); i++) {
m_channelInstanceRegistrations[i].m_gui->setName(QString("%1:%2").arg(m_channelInstanceRegistrations[i].m_channelName).arg(i));
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

+16
View File
@@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>appicon.png</file>
<file>histogram.png</file>
<file>waterfall.png</file>
<file>preset-load.png</file>
<file>preset-save.png</file>
<file>preset-delete.png</file>
<file>horizontal.png</file>
<file>vertical.png</file>
<file>logo.png</file>
<file>maxhold.png</file>
<file>grid.png</file>
<file>invertspectrum.png</file>
</qresource>
</RCC>
+1
View File
@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "appicon.ico"
Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+46
View File
@@ -0,0 +1,46 @@
#include "settings/preferences.h"
#include "util/simpleserializer.h"
Preferences::Preferences()
{
resetToDefaults();
}
void Preferences::resetToDefaults()
{
m_sourceType.clear();
m_sourceDevice.clear();
m_audioType.clear();
m_audioDevice.clear();
}
QByteArray Preferences::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_sourceType);
s.writeString(2, m_sourceDevice);
s.writeString(3, m_audioType);
s.writeString(4, m_audioDevice);
return s.final();
}
bool Preferences::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid()) {
resetToDefaults();
return false;
}
if(d.getVersion() == 1) {
d.readString(1, &m_sourceType);
d.readString(2, &m_sourceDevice);
d.readString(3, &m_audioType);
d.readString(4, &m_audioDevice);
return true;
} else {
resetToDefaults();
return false;
}
}
+88
View File
@@ -0,0 +1,88 @@
#include "util/simpleserializer.h"
#include "settings/preset.h"
Preset::Preset()
{
resetToDefaults();
}
void Preset::resetToDefaults()
{
m_group = "default";
m_description = "no name";
m_centerFrequency = 0;
m_spectrumConfig.clear();
m_scopeConfig.clear();
m_dcOffsetCorrection = true;
m_iqImbalanceCorrection = true;
m_showScope = true;
m_layout.clear();
m_spectrumConfig.clear();
m_channelConfigs.clear();
m_source.clear();
m_sourceConfig.clear();
}
QByteArray Preset::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_group);
s.writeString(2, m_description);
s.writeU64(3, m_centerFrequency);
s.writeBool(4, m_showScope);
s.writeBlob(5, m_layout);
s.writeBlob(6, m_spectrumConfig);
s.writeBool(7, m_dcOffsetCorrection);
s.writeBool(8, m_iqImbalanceCorrection);
s.writeBlob(9, m_scopeConfig);
s.writeString(10, m_source);
s.writeBlob(11, m_sourceGeneralConfig);
s.writeBlob(12, m_sourceConfig);
s.writeS32(100, m_channelConfigs.size());
for(int i = 0; i < m_channelConfigs.size(); i++) {
s.writeString(101 + i * 2, m_channelConfigs[i].m_channel);
s.writeBlob(102 + i * 2, m_channelConfigs[i].m_config);
}
return s.final();
}
bool Preset::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid()) {
resetToDefaults();
return false;
}
if(d.getVersion() == 1) {
d.readString(1, &m_group, "default");
d.readString(2, &m_description, "no name");
d.readU64(3, &m_centerFrequency, 0);
d.readBool(4, &m_showScope, true);
d.readBlob(5, &m_layout);
d.readBlob(6, &m_spectrumConfig);
d.readBool(7, &m_dcOffsetCorrection, true);
d.readBool(8, &m_iqImbalanceCorrection, true);
d.readBlob(9, &m_scopeConfig);
d.readString(10, &m_source);
d.readBlob(11, &m_sourceGeneralConfig);
d.readBlob(12, &m_sourceConfig);
qint32 channelCount = 0;
d.readS32(100, &channelCount, 0);
for(int i = 0; i < channelCount; i++) {
QString channel;
QByteArray config;
d.readString(101 + i * 2, &channel, "unknown-channel");
d.readBlob(102 + i * 2, &config);
m_channelConfigs.append(ChannelConfig(channel, config));
}
return true;
} else {
resetToDefaults();
return false;
}
}
+76
View File
@@ -0,0 +1,76 @@
#include <QSettings>
#include <QStringList>
#include "settings/settings.h"
Settings::Settings()
{
resetToDefaults();
}
Settings::~Settings()
{
for(int i = 0; i < m_presets.count(); ++i)
delete m_presets[i];
}
void Settings::load()
{
QSettings s;
m_preferences.deserialize(qUncompress(QByteArray::fromBase64(s.value("preferences").toByteArray())));
m_current.deserialize(qUncompress(QByteArray::fromBase64(s.value("current").toByteArray())));
QStringList groups = s.childGroups();
for(int i = 0; i < groups.size(); ++i) {
if(groups[i].startsWith("preset")) {
s.beginGroup(groups[i]);
Preset* preset = new Preset;
if(preset->deserialize(qUncompress(QByteArray::fromBase64(s.value("data").toByteArray()))))
m_presets.append(preset);
else delete preset;
s.endGroup();
}
}
}
void Settings::save() const
{
QSettings s;
s.setValue("preferences", qCompress(m_preferences.serialize()).toBase64());
s.setValue("current", qCompress(m_current.serialize()).toBase64());
QStringList groups = s.childGroups();
for(int i = 0; i < groups.size(); ++i) {
if(groups[i].startsWith("preset"))
s.remove(groups[i]);
}
for(int i = 0; i < m_presets.count(); ++i) {
QString group = QString("preset-%1").arg(i + 1);
s.beginGroup(group);
s.setValue("data", qCompress(m_presets[i]->serialize()).toBase64());
s.endGroup();
}
}
void Settings::resetToDefaults()
{
m_preferences.resetToDefaults();
m_current.resetToDefaults();
}
Preset* Settings::newPreset(const QString& group, const QString& description)
{
Preset* preset = new Preset();
preset->setGroup(group);
preset->setDescription(description);
m_presets.append(preset);
return preset;
}
void Settings::deletePreset(const Preset* preset)
{
m_presets.removeAll((Preset*)preset);
delete (Preset*)preset;
}
+79
View File
@@ -0,0 +1,79 @@
#include <QWaitCondition>
#include <QMutex>
#include "util/message.h"
#include "util/messagequeue.h"
const char* Message::m_identifier = "Message";
Message::Message() :
m_destination(NULL),
m_synchronous(false),
m_waitCondition(NULL),
m_mutex(NULL),
m_complete(0)
{
}
Message::~Message()
{
if(m_waitCondition != NULL)
delete m_waitCondition;
if(m_mutex != NULL)
delete m_mutex;
}
const char* Message::getIdentifier() const
{
return m_identifier;
}
bool Message::matchIdentifier(const char* identifier) const
{
return m_identifier == identifier;
}
bool Message::match(Message* message)
{
return message->matchIdentifier(m_identifier);
}
void Message::submit(MessageQueue* queue, void* destination)
{
m_destination = destination;
m_synchronous = false;
queue->submit(this);
}
int Message::execute(MessageQueue* queue, void* destination)
{
m_destination = destination;
m_synchronous = true;
if(m_waitCondition == NULL)
m_waitCondition = new QWaitCondition;
if(m_mutex == NULL)
m_mutex = new QMutex;
m_mutex->lock();
m_complete.testAndSetAcquire(0, 1);
queue->submit(this);
while(!m_complete.testAndSetAcquire(0, 1))
((QWaitCondition*)m_waitCondition)->wait(m_mutex, 100);
m_complete = 0;
int result = m_result;
m_mutex->unlock();
return result;
}
void Message::completed(int result)
{
if(m_synchronous) {
m_result = result;
m_complete = 0;
if(m_waitCondition == NULL)
qFatal("wait condition is NULL");
m_waitCondition->wakeAll();
} else {
delete this;
}
}
+40
View File
@@ -0,0 +1,40 @@
#include "util/messagequeue.h"
#include "util/message.h"
MessageQueue::MessageQueue(QObject* parent) :
QObject(parent),
m_lock(),
m_queue()
{
}
MessageQueue::~MessageQueue()
{
Message* cmd;
while((cmd = accept()) != NULL)
cmd->completed();
}
void MessageQueue::submit(Message* message)
{
m_lock.lock();
m_queue.append(message);
m_lock.unlock();
emit messageEnqueued();
}
Message* MessageQueue::accept()
{
SpinlockHolder spinlockHolder(&m_lock);
if(m_queue.isEmpty())
return NULL;
else return m_queue.takeFirst();
}
int MessageQueue::countPending()
{
SpinlockHolder spinlockHolder(&m_lock);
return m_queue.size();
}
File diff suppressed because it is too large Load Diff
+704
View File
@@ -0,0 +1,704 @@
#include <stdint.h>
#include "util/simpleserializer.h"
#if __WORDSIZE == 64
#define PRINTF_FORMAT_S32 "%d"
#define PRINTF_FORMAT_U32 "%u"
#define PRINTF_FORMAT_S64 "%d"
#define PRINTF_FORMAT_U64 "%u"
#else
#define PRINTF_FORMAT_S32 "%d"
#define PRINTF_FORMAT_U32 "%u"
#define PRINTF_FORMAT_S64 "%lld"
#define PRINTF_FORMAT_U64 "%llu"
#endif
template <bool> struct Assert { };
typedef Assert<(bool(sizeof(float) == 4))> float_must_be_32_bits[bool(sizeof(float) == 4) ? 1 : -1];
typedef Assert<(bool(sizeof(double) == 8))> double_must_be_64_bits[bool(sizeof(double) == 8) ? 1 : -1];
SimpleSerializer::SimpleSerializer(uint version) :
m_data(),
m_finalized(false)
{
m_data.reserve(100);
// write version information
int length;
if(version >= (1 << 24))
length = 4;
else if(version >= (1 << 16))
length = 3;
else if(version >= (1 << 8))
length = 2;
else if(version > 0)
length = 1;
else length = 0;
if(!writeTag(TVersion, 0, length))
return;
length--;
for(int i = length; i >= 0; i--)
m_data.push_back((char)((version >> (i * 8)) & 0xff));
}
void SimpleSerializer::writeS32(quint32 id, qint32 value)
{
int length;
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if((value < -(1 << 23)) || (value >= (1 << 23)))
length = 4;
else if((value < -(1 << 15)) || (value >= (1 << 15)))
length = 3;
else if((value < -(1 << 7)) || (value >= (1 << 7)))
length = 2;
else if(value != 0)
length = 1;
else length = 0;
if(!writeTag(TSigned32, id, length))
return;
length--;
for(int i = length; i >= 0; i--)
m_data.push_back((char)((value >> (i * 8)) & 0xff));
}
void SimpleSerializer::writeU32(quint32 id, quint32 value)
{
int length;
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(value >= (1 << 24))
length = 4;
else if(value >= (1 << 16))
length = 3;
else if(value >= (1 << 8))
length = 2;
else if(value > 0)
length = 1;
else length = 0;
if(!writeTag(TUnsigned32, id, length))
return;
length--;
for(int i = length; i >= 0; i--)
m_data.push_back((char)((value >> (i * 8)) & 0xff));
}
void SimpleSerializer::writeS64(quint32 id, qint64 value)
{
int length;
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if((value < -(1ll << 55)) || (value >= (1ll << 55)))
length = 8;
else if((value < -(1ll << 47)) || (value >= (1ll << 47)))
length = 7;
else if((value < -(1ll << 39)) || (value >= (1ll << 39)))
length = 6;
else if((value < -(1ll << 31)) || (value >= (1ll << 31)))
length = 5;
else if((value < -(1ll << 23)) || (value >= (1ll << 23)))
length = 4;
else if((value < -(1ll << 15)) || (value >= (1ll << 15)))
length = 3;
else if((value < -(1ll << 7)) || (value >= (1ll << 7)))
length = 2;
else if(value != 0)
length = 1;
else length = 0;
if(!writeTag(TSigned64, id, length))
return;
length--;
for(int i = length; i >= 0; i--)
m_data.push_back((char)((value >> (i * 8)) & 0xff));
}
void SimpleSerializer::writeU64(quint32 id, quint64 value)
{
int length;
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(value >= (1ll << 56))
length = 8;
else if(value >= (1ll << 48))
length = 7;
else if(value >= (1ll << 40))
length = 6;
else if(value >= (1ll << 32))
length = 5;
else if(value >= (1ll << 24))
length = 4;
else if(value >= (1ll << 16))
length = 3;
else if(value >= (1ll << 8))
length = 2;
else if(value > 0)
length = 1;
else length = 0;
if(!writeTag(TUnsigned64, id, length))
return;
length--;
for(int i = length; i >= 0; i--)
m_data.push_back((char)((value >> (i * 8)) & 0xff));
}
void SimpleSerializer::writeFloat(quint32 id, float value)
{
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(!writeTag(TFloat, id, 4))
return;
quint32 tmp = *((quint32*)&value);
m_data.push_back((char)((tmp >> 24) & 0xff));
m_data.push_back((char)((tmp >> 16) & 0xff));
m_data.push_back((char)((tmp >> 8) & 0xff));
m_data.push_back((char)(tmp & 0xff));
}
void SimpleSerializer::writeDouble(quint32 id, double value)
{
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(!writeTag(TDouble, id, 8))
return;
quint64 tmp = *((quint64*)&value);
m_data.push_back((char)((tmp >> 56) & 0xff));
m_data.push_back((char)((tmp >> 48) & 0xff));
m_data.push_back((char)((tmp >> 40) & 0xff));
m_data.push_back((char)((tmp >> 32) & 0xff));
m_data.push_back((char)((tmp >> 24) & 0xff));
m_data.push_back((char)((tmp >> 16) & 0xff));
m_data.push_back((char)((tmp >> 8) & 0xff));
m_data.push_back((char)(tmp & 0xff));
}
void SimpleSerializer::writeBool(quint32 id, bool value)
{
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(!writeTag(TBool, id, 1))
return;
if(value)
m_data.push_back((char)0x01);
else m_data.push_back((char)0x00);
}
void SimpleSerializer::writeString(quint32 id, const QString& value)
{
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
QByteArray utf8 = value.toUtf8();
if(!writeTag(TString, id, utf8.size()))
return;
m_data.append(utf8);
}
void SimpleSerializer::writeBlob(quint32 id, const QByteArray& value)
{
if(id == 0) {
qCritical("SimpleSerializer: ID 0 is not allowed");
return;
}
if(!writeTag(TBlob, id, value.size()))
return;
m_data.append(value);
}
const QByteArray& SimpleSerializer::final()
{
m_finalized = true;
return m_data;
}
bool SimpleSerializer::writeTag(Type type, quint32 id, quint32 length)
{
if(m_finalized) {
qCritical("SimpleSerializer: config has already been finalized (id %u)", id);
return false;
}
int idLen;
int lengthLen;
if(id < (1 << 8))
idLen = 0;
else if(id < (1 << 16))
idLen = 1;
else if(id < (1 << 24))
idLen = 2;
else idLen = 3;
if(length < (1 << 8))
lengthLen = 0;
else if(length < (1 << 16))
lengthLen = 1;
else if(length < (1 << 24))
lengthLen = 2;
else lengthLen = 3;
m_data.push_back((char)((type << 4) | (idLen << 2) | lengthLen));
for(int i = idLen; i >= 0; i--)
m_data.push_back((char)((id >> (i * 8)) & 0xff));
for(int i = lengthLen; i >= 0; i--)
m_data.push_back((char)((length >> (i * 8)) & 0xff));
return true;
}
SimpleDeserializer::SimpleDeserializer(const QByteArray& data) :
m_data(data)
{
m_valid = parseAll();
// read version information
uint readOfs;
Elements::const_iterator it = m_elements.constFind(0);
if(it == m_elements.constEnd())
goto setInvalid;
if(it->type != TVersion)
goto setInvalid;
if(it->length > 4)
goto setInvalid;
readOfs = it->ofs;
m_version = 0;
for(uint i = 0; i < it->length; i++)
m_version = (m_version << 8) | readByte(&readOfs);
return;
setInvalid:
m_valid = false;
}
bool SimpleDeserializer::readS32(quint32 id, qint32* result, qint32 def) const
{
uint readOfs;
qint32 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TSigned32)
goto returnDefault;
if(it->length > 4)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(uint i = 0; i < it->length; i++) {
quint8 byte = readByte(&readOfs);
if((i == 0) && (byte & 0x80))
tmp = -1;
tmp = (tmp << 8) | byte;
}
*result = tmp;
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readU32(quint32 id, quint32* result, quint32 def) const
{
uint readOfs;
quint32 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TUnsigned32)
goto returnDefault;
if(it->length > 4)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(uint i = 0; i < it->length; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = tmp;
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readS64(quint32 id, qint64* result, qint64 def) const
{
uint readOfs;
qint64 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TSigned64)
goto returnDefault;
if(it->length > 8)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(uint i = 0; i < it->length; i++) {
quint8 byte = readByte(&readOfs);
if((i == 0) && (byte & 0x80))
tmp = -1;
tmp = (tmp << 8) | byte;
}
*result = tmp;
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readU64(quint32 id, quint64* result, quint64 def) const
{
uint readOfs;
quint64 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TUnsigned64)
goto returnDefault;
if(it->length > 8)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(uint i = 0; i < it->length; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = tmp;
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readFloat(quint32 id, float* result, float def) const
{
uint readOfs;
quint32 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TFloat)
goto returnDefault;
if(it->length != 4)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(int i = 0; i < 4; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = *((float*)&tmp);
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readDouble(quint32 id, double* result, double def) const
{
uint readOfs;
quint64 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TDouble)
goto returnDefault;
if(it->length != 8)
goto returnDefault;
readOfs = it->ofs;
tmp = 0;
for(int i = 0; i < 8; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = *((double*)&tmp);
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readReal(quint32 id, Real* result, Real def) const
{
if(sizeof(Real) == 4) {
uint readOfs;
quint32 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault32;
if(it->type != TFloat)
goto returnDefault32;
if(it->length != 4)
goto returnDefault32;
readOfs = it->ofs;
tmp = 0;
for(int i = 0; i < 4; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = *((Real*)&tmp);
return true;
returnDefault32:
*result = def;
return false;
} else {
uint readOfs;
quint64 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault64;
if(it->type != TDouble)
goto returnDefault64;
if(it->length != 8)
goto returnDefault64;
readOfs = it->ofs;
tmp = 0;
for(int i = 0; i < 8; i++)
tmp = (tmp << 8) | readByte(&readOfs);
*result = *((Real*)&tmp);
return true;
returnDefault64:
*result = def;
return false;
}
}
bool SimpleDeserializer::readBool(quint32 id, bool* result, bool def) const
{
uint readOfs;
quint8 tmp;
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TBool)
goto returnDefault;
if(it->length != 1)
goto returnDefault;
readOfs = it->ofs;
tmp = readByte(&readOfs);
if(tmp == 0x00)
*result = false;
else *result = true;
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readString(quint32 id, QString* result, const QString& def) const
{
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TString)
goto returnDefault;
*result = QString::fromUtf8(m_data.data() + it->ofs, it->length);
return true;
returnDefault:
*result = def;
return false;
}
bool SimpleDeserializer::readBlob(quint32 id, QByteArray* result, const QByteArray& def) const
{
Elements::const_iterator it = m_elements.constFind(id);
if(it == m_elements.constEnd())
goto returnDefault;
if(it->type != TBlob)
goto returnDefault;
*result = QByteArray(m_data.data() + it->ofs, it->length);
return true;
returnDefault:
*result = def;
return false;
}
void SimpleDeserializer::dump() const
{
if(!m_valid) {
qDebug("SimpleDeserializer dump: data invalid");
return;
} else {
qDebug("SimpleDeserializer dump: version %u", m_version);
}
for(Elements::const_iterator it = m_elements.constBegin(); it != m_elements.constEnd(); ++it) {
switch(it->type) {
case TSigned32: {
qint32 tmp;
readS32(it.key(), &tmp);
qDebug("id %d, S32, len %d: " PRINTF_FORMAT_S32, it.key(), it->length, tmp);
break;
}
case TUnsigned32: {
quint32 tmp;
readU32(it.key(), &tmp);
qDebug("id %d, U32, len %d: " PRINTF_FORMAT_U32, it.key(), it->length, tmp);
break;
}
case TSigned64: {
qint64 tmp;
readS64(it.key(), &tmp);
qDebug("id %d, S64, len %d: " PRINTF_FORMAT_S64, it.key(), it->length, (int)tmp);
break;
}
case TUnsigned64: {
quint64 tmp;
readU64(it.key(), &tmp);
qDebug("id %d, U64, len %d: " PRINTF_FORMAT_U64, it.key(), it->length, (uint)tmp);
break;
}
case TFloat: {
float tmp;
readFloat(it.key(), &tmp);
qDebug("id %d, FLOAT, len %d: %f", it.key(), it->length, tmp);
break;
}
case TDouble: {
double tmp;
readDouble(it.key(), &tmp);
qDebug("id %d, DOUBLE, len %d: %f", it.key(), it->length, tmp);
break;
}
case TBool: {
bool tmp;
readBool(it.key(), &tmp);
qDebug("id %d, BOOL, len %d: %s", it.key(), it->length, tmp ? "true" : "false");
break;
}
case TString: {
QString tmp;
readString(it.key(), &tmp);
qDebug("id %d, STRING, len %d: \"%s\"", it.key(), it->length, qPrintable(tmp));
break;
}
case TBlob: {
QByteArray tmp;
readBlob(it.key(), &tmp);
qDebug("id %d, BLOB, len %d", it.key(), it->length);
break;
}
case TVersion: {
qDebug("id %d, VERSION, len %d", it.key(), it->length);
break;
}
default: {
qDebug("id %d, UNKNOWN TYPE 0x%02x, len %d", it.key(), it->type, it->length);
break;
}
}
}
}
bool SimpleDeserializer::parseAll()
{
uint readOfs = 0;
Type type;
quint32 id;
quint32 length;
/*
QString hex;
for(int i = 0; i < m_data.size(); i++)
hex.append(QString("%1 ").arg((quint8)m_data[i], 2, 16, QChar('0')));
qDebug("%s", qPrintable(hex));
qDebug("==");
*/
while(readOfs < (uint)m_data.size()) {
if(!readTag(&readOfs, m_data.size(), &type, &id, &length))
return false;
//qDebug("-- id %d, TYPE 0x%02x, len %d", id, type, length);
if(m_elements.contains(id)) {
qDebug("SimpleDeserializer: same ID found twice (id %u)", id);
return false;
}
m_elements.insert(id, Element(type, readOfs, length));
readOfs += length;
if(readOfs == m_data.size())
return true;
}
return false;
}
bool SimpleDeserializer::readTag(uint* readOfs, uint readEnd, Type* type, quint32* id, quint32* length) const
{
quint8 tag = readByte(readOfs);
*type = (Type)(tag >> 4);
int idLen = ((tag >> 2) & 0x03) + 1;
int lengthLen = (tag & 0x03) + 1;
// make sure we have enough header bytes left
if(((*readOfs) + idLen + lengthLen) > readEnd)
return false;
quint32 tmp = 0;
for(int i = 0; i < idLen; i++)
tmp = (tmp << 8) | readByte(readOfs);
*id = tmp;
tmp = 0;
for(int i = 0; i < lengthLen; i++)
tmp = (tmp << 8) | readByte(readOfs);
*length = tmp;
// check if payload fits the buffer
if(((*readOfs) + (*length)) > readEnd)
return false;
else return true;
}
+1
View File
@@ -0,0 +1 @@
#include "util/spinlock.h"