mirror of
https://github.com/miek/inspectrum.git
synced 2026-03-09 01:37:07 +01:00
Merge branch 'refactor'
This commit is contained in:
@@ -10,12 +10,12 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
if (WIN32)
|
||||
find_library (MMAN mman)
|
||||
if(NOT(MMAN))
|
||||
message(FATAL_ERROR "please install mman-win32")
|
||||
else(NOT(MMAN))
|
||||
set (extraLibs ${extraLibs} ${MMAN})
|
||||
endif(NOT(MMAN))
|
||||
find_library (MMAN mman)
|
||||
if(NOT(MMAN))
|
||||
message(FATAL_ERROR "please install mman-win32")
|
||||
else(NOT(MMAN))
|
||||
set (extraLibs ${extraLibs} ${MMAN})
|
||||
endif(NOT(MMAN))
|
||||
ENDIF (WIN32)
|
||||
|
||||
if (NOT CMAKE_CXX_FLAGS)
|
||||
@@ -27,32 +27,52 @@ endif (NOT CMAKE_CXX_FLAGS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
|
||||
|
||||
list(APPEND inspectrum_sources
|
||||
main.cpp
|
||||
fft.cpp
|
||||
mainwindow.cpp
|
||||
inputsource.cpp
|
||||
spectrogram.cpp
|
||||
spectrogramcontrols.cpp
|
||||
cursors.cpp
|
||||
main.cpp
|
||||
fft.cpp
|
||||
mainwindow.cpp
|
||||
grsamplebuffer.cpp
|
||||
inputsource.cpp
|
||||
memory_sink_impl.cc
|
||||
memory_source_impl.cc
|
||||
plot.cpp
|
||||
plotview.cpp
|
||||
samplebuffer.cpp
|
||||
samplesource.cpp
|
||||
spectrogramcontrols.cpp
|
||||
spectrogramplot.cpp
|
||||
traceplot.cpp
|
||||
)
|
||||
|
||||
INCLUDE(FindPkgConfig)
|
||||
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Boost COMPONENTS system program_options REQUIRED)
|
||||
set(GR_REQUIRED_COMPONENTS RUNTIME ANALOG BLOCKS FILTER)
|
||||
find_package(Gnuradio REQUIRED)
|
||||
pkg_check_modules(FFTW REQUIRED fftw3f)
|
||||
|
||||
include_directories(
|
||||
${QT_INCLUDES}
|
||||
${FFTW_INCLUDEDIR}
|
||||
${FFTW_INCLUDE_DIRS}
|
||||
${GNURADIO_RUNTIME_INCLUDE_DIRS}
|
||||
${QT_INCLUDES}
|
||||
${FFTW_INCLUDEDIR}
|
||||
${FFTW_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
link_directories(
|
||||
${FFTW_LIBRARY_DIRS}
|
||||
${FFTW_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
add_executable(inspectrum ${inspectrum_sources})
|
||||
qt5_use_modules(inspectrum Widgets)
|
||||
|
||||
target_link_libraries(inspectrum ${QT_LIBRARIES} ${FFTW_LIBRARIES} ${extraLibs})
|
||||
target_link_libraries(inspectrum
|
||||
${Boost_LIBRARIES}
|
||||
${GNURADIO_ALL_LIBRARIES}
|
||||
${QT_LIBRARIES}
|
||||
${FFTW_LIBRARIES}
|
||||
${extraLibs}
|
||||
)
|
||||
set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX")
|
||||
|
||||
install(TARGETS inspectrum RUNTIME DESTINATION ${INSTALL_DEFAULT_BINDIR})
|
||||
|
||||
@@ -6,6 +6,8 @@ inspectrum is a tool for analysing captured signals, primarily from software-def
|
||||
## Try it
|
||||
### Prerequisites
|
||||
|
||||
* boost >=1.35
|
||||
* gnuradio 3.7.x
|
||||
* qt5
|
||||
* fftw 3.x
|
||||
* cmake
|
||||
|
||||
30
abstractsamplesource.h
Normal file
30
abstractsamplesource.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
|
||||
class AbstractSampleSource
|
||||
{
|
||||
|
||||
public:
|
||||
virtual ~AbstractSampleSource() {};
|
||||
};
|
||||
122
cursors.cpp
Normal file
122
cursors.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMouseEvent>
|
||||
#include "cursors.h"
|
||||
|
||||
Cursors::Cursors(QObject * parent) : QObject::QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Return true if point is over a cursor, put cursor ID in `cursor`
|
||||
bool Cursors::pointOverCursor(QPoint point, int &cursor)
|
||||
{
|
||||
int margin = 5;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
range_t<int> range = {cursorPositions[i] - margin, cursorPositions[i] + margin};
|
||||
if (range.contains(point.x())) {
|
||||
cursor = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Cursors::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
// Start dragging on left mouse button press, if over a cursor
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
if (pointOverCursor(mouseEvent->pos(), selectedCursor)) {
|
||||
dragging = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update current cursor positon if we're dragging
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (dragging) {
|
||||
cursorPositions[selectedCursor] = mouseEvent->pos().x();
|
||||
emit cursorsMoved();
|
||||
}
|
||||
|
||||
// Stop dragging on left mouse button release
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
dragging = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Cursors::paintFront(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
painter.save();
|
||||
|
||||
QRect cursorRect(cursorPositions[0], rect.top(), cursorPositions[1] - cursorPositions[0], rect.height());
|
||||
// Draw translucent white fill for highlight
|
||||
painter.fillRect(
|
||||
cursorRect,
|
||||
QBrush(QColor(255, 255, 255, 50))
|
||||
);
|
||||
|
||||
// Draw vertical edges for individual bits
|
||||
painter.setPen(QPen(Qt::gray, 1, Qt::DashLine));
|
||||
for (int i = 1; i < bitCount; i++) {
|
||||
int pos = cursorPositions[0] + (i * cursorRect.width() / bitCount);
|
||||
painter.drawLine(pos, rect.top(), pos, rect.bottom());
|
||||
}
|
||||
|
||||
// Draw vertical edges
|
||||
painter.setPen(QPen(Qt::white, 1, Qt::SolidLine));
|
||||
painter.drawLine(cursorPositions[0], rect.top(), cursorPositions[0], rect.bottom());
|
||||
painter.drawLine(cursorPositions[1], rect.top(), cursorPositions[1], rect.bottom());
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
range_t<int> Cursors::selection()
|
||||
{
|
||||
// TODO: ensure correct ordering during dragging, not here
|
||||
if (cursorPositions[0] < cursorPositions[1]) {
|
||||
return {cursorPositions[0], cursorPositions[1]};
|
||||
|
||||
} else {
|
||||
return {cursorPositions[1], cursorPositions[0]};
|
||||
}
|
||||
}
|
||||
|
||||
void Cursors::setBits(int bits)
|
||||
{
|
||||
bitCount = std::max(bits, 1);
|
||||
|
||||
}
|
||||
|
||||
void Cursors::setSelection(range_t<int> selection)
|
||||
{
|
||||
cursorPositions[0] = selection.minimum;
|
||||
cursorPositions[1] = selection.maximum;
|
||||
emit cursorsMoved();
|
||||
}
|
||||
52
cursors.h
Normal file
52
cursors.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QPoint>
|
||||
#include "util.h"
|
||||
|
||||
class Cursors : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Cursors(QObject * parent);
|
||||
void paintFront(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
range_t<int> selection();
|
||||
void setBits(int bits);
|
||||
void setSelection(range_t<int> selection);
|
||||
|
||||
signals:
|
||||
void cursorsMoved();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private:
|
||||
bool pointOverCursor(QPoint point, int &cursor);
|
||||
|
||||
int bitCount = 1;
|
||||
bool dragging = false;
|
||||
int selectedCursor = 0;
|
||||
int cursorPositions[2] = {0, 50};
|
||||
|
||||
};
|
||||
7
fft.h
7
fft.h
@@ -21,12 +21,15 @@
|
||||
|
||||
#include <fftw3.h>
|
||||
|
||||
class FFT {
|
||||
class FFT
|
||||
{
|
||||
public:
|
||||
FFT(int size);
|
||||
~FFT();
|
||||
void process(void *dest, void *source);
|
||||
int getSize() { return fftSize; }
|
||||
int getSize() {
|
||||
return fftSize;
|
||||
}
|
||||
|
||||
private:
|
||||
int fftSize;
|
||||
|
||||
31
grsamplebuffer.cpp
Normal file
31
grsamplebuffer.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "grsamplebuffer.h"
|
||||
|
||||
template<typename Tin, typename Tout>
|
||||
void GRSampleBuffer<Tin, Tout>::work(void *input, void *output, int length)
|
||||
{
|
||||
mem_source->set_source(input, length);
|
||||
mem_sink->set_sink(output, length);
|
||||
tb->run();
|
||||
}
|
||||
|
||||
template class GRSampleBuffer<std::complex<float>, std::complex<float>>;
|
||||
template class GRSampleBuffer<std::complex<float>, float>;
|
||||
41
grsamplebuffer.h
Normal file
41
grsamplebuffer.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
#include <gnuradio/top_block.h>
|
||||
#include "memory_sink.h"
|
||||
#include "memory_source.h"
|
||||
|
||||
#include "samplebuffer.h"
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
class GRSampleBuffer : public SampleBuffer<Tin, Tout>
|
||||
{
|
||||
private:
|
||||
gr::top_block_sptr tb;
|
||||
gr::blocks::memory_source::sptr mem_source;
|
||||
gr::blocks::memory_sink::sptr mem_sink;
|
||||
|
||||
public:
|
||||
GRSampleBuffer(SampleSource<Tin> *src, gr::top_block_sptr tb, gr::blocks::memory_source::sptr mem_source, gr::blocks::memory_sink::sptr mem_sink)
|
||||
: SampleBuffer<Tin, Tout>(src), tb(tb), mem_source(mem_source), mem_sink(mem_sink) {};
|
||||
virtual void work(void *input, void *output, int count);
|
||||
};
|
||||
@@ -27,35 +27,80 @@
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
InputSource::InputSource(const char *filename) {
|
||||
m_file = fopen(filename, "rb");
|
||||
if (m_file == nullptr)
|
||||
InputSource::InputSource()
|
||||
{
|
||||
}
|
||||
|
||||
InputSource::~InputSource()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void InputSource::cleanup()
|
||||
{
|
||||
if (mmapData != nullptr) {
|
||||
munmap(mmapData, fileSize);
|
||||
mmapData = nullptr;
|
||||
fileSize = 0;
|
||||
}
|
||||
|
||||
if (inputFile != nullptr) {
|
||||
fclose(inputFile);
|
||||
inputFile = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void InputSource::openFile(const char *filename)
|
||||
{
|
||||
FILE *file = fopen(filename, "rb");
|
||||
if (file == nullptr)
|
||||
throw std::runtime_error("Error opening file");
|
||||
|
||||
struct stat sb;
|
||||
if (fstat(fileno(m_file), &sb) != 0)
|
||||
if (fstat(fileno(file), &sb) != 0)
|
||||
throw std::runtime_error("Error fstating file");
|
||||
m_file_size = sb.st_size;
|
||||
sampleCount = m_file_size / sizeof(fftwf_complex);
|
||||
off_t size = sb.st_size;
|
||||
sampleCount = size / sizeof(std::complex<float>);
|
||||
|
||||
m_data = (fftwf_complex*)mmap(NULL, m_file_size, PROT_READ, MAP_SHARED, fileno(m_file), 0);
|
||||
if (m_data == 0)
|
||||
auto data = (std::complex<float>*)mmap(NULL, size, PROT_READ, MAP_SHARED, fileno(file), 0);
|
||||
if (data == nullptr)
|
||||
throw std::runtime_error("Error mmapping file");
|
||||
|
||||
cleanup();
|
||||
|
||||
inputFile = file;
|
||||
fileSize = size;
|
||||
mmapData = data;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
InputSource::~InputSource() {
|
||||
munmap(m_data, m_file_size);
|
||||
fclose(m_file);
|
||||
}
|
||||
|
||||
bool InputSource::getSamples(fftwf_complex *dest, off_t start, int length)
|
||||
void InputSource::setSampleRate(off_t rate)
|
||||
{
|
||||
sampleRate = rate;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
off_t InputSource::rate()
|
||||
{
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::complex<float>[]> InputSource::getSamples(off_t start, off_t length)
|
||||
{
|
||||
if (inputFile == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if (mmapData == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if(start < 0 || length < 0)
|
||||
return false;
|
||||
return nullptr;
|
||||
|
||||
if (start + length >= sampleCount)
|
||||
return false;
|
||||
return nullptr;
|
||||
|
||||
memcpy(dest, &m_data[start], length * sizeof(fftwf_complex));
|
||||
return true;
|
||||
std::unique_ptr<std::complex<float>[]> dest(new std::complex<float>[length]);
|
||||
memcpy(dest.get(), &mmapData[start], length * sizeof(std::complex<float>));
|
||||
return dest;
|
||||
}
|
||||
|
||||
@@ -19,22 +19,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fft.h"
|
||||
#include <fftw3.h>
|
||||
#include <memory>
|
||||
#include <complex>
|
||||
#include "samplesource.h"
|
||||
|
||||
class InputSource
|
||||
class InputSource : public SampleSource<std::complex<float>>
|
||||
{
|
||||
private:
|
||||
FILE *m_file;
|
||||
off_t m_file_size;
|
||||
off_t sampleCount;
|
||||
fftwf_complex *m_data;
|
||||
FILE *inputFile = nullptr;
|
||||
off_t fileSize = 0;
|
||||
off_t sampleCount = 0;
|
||||
off_t sampleRate = 0;
|
||||
std::complex<float> *mmapData = nullptr;
|
||||
|
||||
public:
|
||||
InputSource(const char *filename);
|
||||
InputSource();
|
||||
~InputSource();
|
||||
|
||||
bool getSamples(fftwf_complex *dest, off_t start, int length);
|
||||
off_t getSampleCount() { return sampleCount; };
|
||||
void cleanup();
|
||||
void openFile(const char *filename);
|
||||
std::unique_ptr<std::complex<float>[]> getSamples(off_t start, off_t length);
|
||||
off_t count() {
|
||||
return sampleCount;
|
||||
};
|
||||
void setSampleRate(off_t rate);
|
||||
off_t rate();
|
||||
};
|
||||
|
||||
24
main.cpp
24
main.cpp
@@ -35,27 +35,27 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Add options
|
||||
QCommandLineOption rateOption(QStringList() << "r" << "rate",
|
||||
QCoreApplication::translate("main", "Set sample rate."),
|
||||
QCoreApplication::translate("main", "Hz"));
|
||||
QCoreApplication::translate("main", "Set sample rate."),
|
||||
QCoreApplication::translate("main", "Hz"));
|
||||
parser.addOption(rateOption);
|
||||
|
||||
// Process the actual command line
|
||||
parser.process(a);
|
||||
|
||||
if (parser.isSet(rateOption)){
|
||||
bool ok;
|
||||
// Use toDouble just for scientific notation support
|
||||
int rate = parser.value(rateOption).toDouble(&ok);
|
||||
if(!ok){
|
||||
fputs("ERROR: could not parse rate\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
mainWin.changeSampleRate(rate);
|
||||
if (parser.isSet(rateOption)) {
|
||||
bool ok;
|
||||
// Use toDouble just for scientific notation support
|
||||
int rate = parser.value(rateOption).toDouble(&ok);
|
||||
if(!ok) {
|
||||
fputs("ERROR: could not parse rate\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
mainWin.setSampleRate(rate);
|
||||
}
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size()>=1)
|
||||
mainWin.openFile(args.at(0));
|
||||
mainWin.openFile(args.at(0));
|
||||
|
||||
mainWin.show();
|
||||
return a.exec();
|
||||
|
||||
@@ -18,96 +18,53 @@
|
||||
*/
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QRubberBand>
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "util.h"
|
||||
|
||||
MainWindow::MainWindow()
|
||||
{
|
||||
setWindowTitle(tr("inspectrum"));
|
||||
scrollArea.setWidget(&spectrogram);
|
||||
scrollArea.viewport()->installEventFilter(this);
|
||||
setCentralWidget(&scrollArea);
|
||||
|
||||
dock = new SpectrogramControls(tr("Controls"), this);
|
||||
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||
|
||||
input = new InputSource();
|
||||
|
||||
plots = new PlotView(input);
|
||||
setCentralWidget(plots);
|
||||
|
||||
// Connect dock inputs
|
||||
connect(dock, SIGNAL(openFile(QString)), this, SLOT(openFile(QString)));
|
||||
connect(dock->sampleRate, SIGNAL(textChanged(QString)), this, SLOT(setSampleRate(QString)));
|
||||
connect(dock, SIGNAL(fftSizeChanged(int)), this, SLOT(setFFTSize(int)));
|
||||
connect(dock->zoomLevelSlider, SIGNAL(valueChanged(int)), this, SLOT(setZoomLevel(int)));
|
||||
connect(dock->powerMaxSlider, SIGNAL(valueChanged(int)), &spectrogram, SLOT(setPowerMax(int)));
|
||||
connect(dock->powerMinSlider, SIGNAL(valueChanged(int)), &spectrogram, SLOT(setPowerMin(int)));
|
||||
connect(dock->timeScaleCheckBox, SIGNAL(stateChanged(int)), &spectrogram, SLOT(setTimeScaleEnable(int)));
|
||||
connect(&spectrogram, SIGNAL(cursorFrequencyChanged(QString)), dock->cursorFrequencyLabel, SLOT(setText(QString)));
|
||||
connect(&spectrogram, SIGNAL(cursorTimeChanged(QString)), dock->cursorTimeLabel, SLOT(setText(QString)));
|
||||
connect(dock->deltaDragCheckBox, SIGNAL(stateChanged(int)), &spectrogram, SLOT(setDeltaDragEnable(int)));
|
||||
connect(&spectrogram, SIGNAL(deltaFrequencyChanged(QString)), dock->deltaFrequencyLabel, SLOT(setText(QString)));
|
||||
connect(&spectrogram, SIGNAL(deltaTimeChanged(QString)), dock->deltaTimeLabel, SLOT(setText(QString)));
|
||||
}
|
||||
connect(dock, SIGNAL(fftOrZoomChanged(int, int)), plots, SLOT(setFFTAndZoom(int, int)));
|
||||
connect(dock->powerMaxSlider, SIGNAL(valueChanged(int)), plots, SLOT(setPowerMax(int)));
|
||||
connect(dock->powerMinSlider, SIGNAL(valueChanged(int)), plots, SLOT(setPowerMin(int)));
|
||||
connect(dock->cursorsCheckBox, &QCheckBox::stateChanged, plots, &PlotView::enableCursors);
|
||||
connect(dock->cursorBitsSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), plots, &PlotView::setCursorBits);
|
||||
|
||||
bool MainWindow::eventFilter(QObject * /*obj*/, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Wheel) {
|
||||
QWheelEvent *wheelEvent = (QWheelEvent*)event;
|
||||
QSlider *slider = nullptr;
|
||||
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
|
||||
slider = dock->zoomLevelSlider;
|
||||
} else if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
|
||||
slider = dock->fftSizeSlider;
|
||||
}
|
||||
if (slider != nullptr) {
|
||||
if (wheelEvent->angleDelta().y() > 0) {
|
||||
slider->setValue(slider->value() + 1);
|
||||
} else if (wheelEvent->angleDelta().y() < 0) {
|
||||
slider->setValue(slider->value() - 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Connect dock outputs
|
||||
connect(plots, SIGNAL(timeSelectionChanged(float)), dock, SLOT(timeSelectionChanged(float)));
|
||||
|
||||
void MainWindow::setSampleRate(QString rate)
|
||||
{
|
||||
spectrogram.setSampleRate(rate.toInt());
|
||||
}
|
||||
|
||||
void MainWindow::changeSampleRate(int rate)
|
||||
{
|
||||
spectrogram.setSampleRate(rate);
|
||||
dock->sampleRate->setText(QString::number(rate));
|
||||
}
|
||||
|
||||
void MainWindow::setFFTSize(int size)
|
||||
{
|
||||
off_t sample = getCenterSample();
|
||||
spectrogram.setFFTSize(size);
|
||||
scrollArea.verticalScrollBar()->setValue(getScrollPos(sample));
|
||||
}
|
||||
|
||||
void MainWindow::setZoomLevel(int zoom)
|
||||
{
|
||||
off_t sample = getCenterSample();
|
||||
spectrogram.setZoomLevel(zoom);
|
||||
scrollArea.verticalScrollBar()->setValue(getScrollPos(sample));
|
||||
}
|
||||
|
||||
off_t MainWindow::getCenterSample()
|
||||
{
|
||||
int height = scrollArea.height();
|
||||
return (scrollArea.verticalScrollBar()->value() + height / 2) * spectrogram.getStride();
|
||||
}
|
||||
|
||||
int MainWindow::getScrollPos(off_t sample)
|
||||
{
|
||||
int height = scrollArea.height();
|
||||
return sample / spectrogram.getStride() - height / 2;
|
||||
// Set defaults after making connections so everything is in sync
|
||||
dock->setDefaults();
|
||||
}
|
||||
|
||||
void MainWindow::openFile(QString fileName)
|
||||
{
|
||||
QString title="%1: %2";
|
||||
this->setWindowTitle(title.arg(QApplication::applicationName(),fileName.section('/',-1,-1)));
|
||||
spectrogram.openFile(fileName);
|
||||
input->openFile(fileName.toUtf8().constData());
|
||||
}
|
||||
|
||||
void MainWindow::setSampleRate(QString rate)
|
||||
{
|
||||
input->setSampleRate(rate.toInt());
|
||||
}
|
||||
|
||||
void MainWindow::setSampleRate(int rate)
|
||||
{
|
||||
dock->sampleRate->setText(QString::number(rate));
|
||||
}
|
||||
|
||||
17
mainwindow.h
17
mainwindow.h
@@ -21,8 +21,8 @@
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QScrollArea>
|
||||
#include "spectrogram.h"
|
||||
#include "spectrogramcontrols.h"
|
||||
#include "plotview.h"
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
@@ -34,18 +34,11 @@ public:
|
||||
|
||||
public slots:
|
||||
void openFile(QString fileName);
|
||||
void setSampleRate(QString rate);
|
||||
void setFFTSize(int size);
|
||||
void setZoomLevel(int zoom);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void setSampleRate(QString rate);
|
||||
void setSampleRate(int rate);
|
||||
|
||||
private:
|
||||
QScrollArea scrollArea;
|
||||
Spectrogram spectrogram;
|
||||
SpectrogramControls *dock;
|
||||
|
||||
off_t getCenterSample();
|
||||
int getScrollPos(off_t sample);
|
||||
PlotView *plots;
|
||||
InputSource *input;
|
||||
};
|
||||
|
||||
43
memory_sink.h
Normal file
43
memory_sink.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_GR_MEMORY_SINK_H
|
||||
#define INCLUDED_GR_MEMORY_SINK_H
|
||||
|
||||
#include <gnuradio/blocks/api.h>
|
||||
#include <gnuradio/sync_block.h>
|
||||
|
||||
namespace gr
|
||||
{
|
||||
namespace blocks
|
||||
{
|
||||
class memory_sink : virtual public sync_block
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<memory_sink> sptr;
|
||||
|
||||
static sptr make(size_t itemsize);
|
||||
|
||||
virtual void set_sink(void *sink, size_t length) = 0;
|
||||
};
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_GR_MEMORY_SINK_H */
|
||||
75
memory_sink_impl.cc
Normal file
75
memory_sink_impl.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "memory_sink_impl.h"
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace gr {
|
||||
namespace blocks {
|
||||
|
||||
memory_sink::sptr
|
||||
memory_sink::make(size_t itemsize)
|
||||
{
|
||||
return gnuradio::get_initial_sptr
|
||||
(new memory_sink_impl(itemsize));
|
||||
}
|
||||
|
||||
memory_sink_impl::memory_sink_impl(size_t itemsize)
|
||||
: sync_block("memory_sink",
|
||||
io_signature::make(1, 1, itemsize),
|
||||
io_signature::make(0, 0, 0)),
|
||||
d_itemsize(itemsize)
|
||||
{
|
||||
}
|
||||
|
||||
memory_sink_impl::~memory_sink_impl()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
memory_sink_impl::set_sink(void *sink, size_t length)
|
||||
{
|
||||
d_sink = sink;
|
||||
d_length = length;
|
||||
d_ptr = 0;
|
||||
}
|
||||
|
||||
int
|
||||
memory_sink_impl::work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
char *inbuf = (char*)input_items[0];
|
||||
long nwritten = 0;
|
||||
|
||||
if(!d_sink)
|
||||
return noutput_items;
|
||||
|
||||
nwritten = std::min((long)(d_length - d_ptr), (long)noutput_items);
|
||||
if (nwritten >= 0) {
|
||||
memcpy((char*)d_sink + d_ptr * d_itemsize, inbuf, nwritten * d_itemsize);
|
||||
}
|
||||
d_ptr += nwritten;
|
||||
|
||||
return (nwritten == 0) ? -1 : nwritten;
|
||||
}
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
52
memory_sink_impl.h
Normal file
52
memory_sink_impl.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_GR_MEMORY_SINK_IMPL_H
|
||||
#define INCLUDED_GR_MEMORY_SINK_IMPL_H
|
||||
|
||||
#include "memory_sink.h"
|
||||
|
||||
namespace gr
|
||||
{
|
||||
namespace blocks
|
||||
{
|
||||
|
||||
class memory_sink_impl : public memory_sink
|
||||
{
|
||||
private:
|
||||
size_t d_itemsize;
|
||||
void *d_sink;
|
||||
size_t d_length;
|
||||
size_t d_ptr = 0;
|
||||
|
||||
public:
|
||||
memory_sink_impl(size_t itemsize);
|
||||
~memory_sink_impl();
|
||||
|
||||
void set_sink(void *sink, size_t length);
|
||||
|
||||
int work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
};
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_GR_MEMORY_SINK_IMPL_H */
|
||||
43
memory_source.h
Normal file
43
memory_source.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_GR_MEMORY_SOURCE_H
|
||||
#define INCLUDED_GR_MEMORY_SOURCE_H
|
||||
|
||||
#include <gnuradio/blocks/api.h>
|
||||
#include <gnuradio/sync_block.h>
|
||||
|
||||
namespace gr
|
||||
{
|
||||
namespace blocks
|
||||
{
|
||||
class memory_source : virtual public sync_block
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<memory_source> sptr;
|
||||
|
||||
static sptr make(size_t itemsize);
|
||||
|
||||
virtual void set_source(void *source, size_t length) = 0;
|
||||
};
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_GR_MEMORY_SOURCE_H */
|
||||
76
memory_source_impl.cc
Normal file
76
memory_source_impl.cc
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "memory_source_impl.h"
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace gr {
|
||||
namespace blocks {
|
||||
|
||||
memory_source::sptr
|
||||
memory_source::make(size_t itemsize)
|
||||
{
|
||||
return gnuradio::get_initial_sptr
|
||||
(new memory_source_impl(itemsize));
|
||||
}
|
||||
|
||||
memory_source_impl::memory_source_impl(size_t itemsize)
|
||||
: sync_block("memory_source",
|
||||
io_signature::make(0, 0, 0),
|
||||
io_signature::make(1, 1, itemsize)),
|
||||
d_itemsize(itemsize)
|
||||
{
|
||||
}
|
||||
|
||||
memory_source_impl::~memory_source_impl()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
memory_source_impl::set_source(void *source, size_t length)
|
||||
{
|
||||
d_source = source;
|
||||
d_length = length;
|
||||
d_ptr = 0;
|
||||
}
|
||||
|
||||
int
|
||||
memory_source_impl::work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
char *outbuf = (char*)output_items[0];
|
||||
long nwritten = 0;
|
||||
|
||||
if(!d_source)
|
||||
return noutput_items;
|
||||
|
||||
nwritten = std::min((long)(d_length - d_ptr), (long)noutput_items);
|
||||
|
||||
if (nwritten >= 0) {
|
||||
memcpy(outbuf, (char*)d_source + d_ptr * d_itemsize, nwritten * d_itemsize);
|
||||
}
|
||||
d_ptr += nwritten;
|
||||
|
||||
return (nwritten == 0) ? -1 : nwritten;
|
||||
}
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
52
memory_source_impl.h
Normal file
52
memory_source_impl.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*ha
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_GR_MEMORY_SOURCE_IMPL_H
|
||||
#define INCLUDED_GR_MEMORY_SOURCE_IMPL_H
|
||||
|
||||
#include "memory_source.h"
|
||||
|
||||
namespace gr
|
||||
{
|
||||
namespace blocks
|
||||
{
|
||||
|
||||
class memory_source_impl : public memory_source
|
||||
{
|
||||
private:
|
||||
size_t d_itemsize;
|
||||
void *d_source;
|
||||
size_t d_length;
|
||||
size_t d_ptr = 0;
|
||||
|
||||
public:
|
||||
memory_source_impl(size_t itemsize);
|
||||
~memory_source_impl();
|
||||
|
||||
void set_source(void *source, size_t length);
|
||||
|
||||
int work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
};
|
||||
|
||||
} /* namespace blocks */
|
||||
} /* namespace gr */
|
||||
|
||||
#endif /* INCLUDED_GR_MEMORY_SOURCE_IMPL_H */
|
||||
39
plot.cpp
Normal file
39
plot.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "plot.h"
|
||||
|
||||
void Plot::paintBack(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
painter.save();
|
||||
QPen pen(Qt::white, 1, Qt::DashLine);
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(rect.left(), rect.center().y(), rect.right(), rect.center().y());
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void Plot::paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Plot::paintFront(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
|
||||
}
|
||||
41
plot.h
Normal file
41
plot.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include "util.h"
|
||||
|
||||
class Plot : public QObject
|
||||
{
|
||||
|
||||
public:
|
||||
virtual void paintBack(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
virtual void paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
virtual void paintFront(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
int height() const { return _height; };
|
||||
|
||||
protected:
|
||||
void setHeight(int height) { _height = height; };
|
||||
|
||||
private:
|
||||
// TODO: don't hardcode this
|
||||
int _height = 200;
|
||||
};
|
||||
231
plotview.cpp
Normal file
231
plotview.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "plotview.h"
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <gnuradio/top_block.h>
|
||||
#include <gnuradio/analog/quadrature_demod_cf.h>
|
||||
#include <gnuradio/blocks/multiply_const_cc.h>
|
||||
#include <gnuradio/filter/firdes.h>
|
||||
#include <gnuradio/filter/freq_xlating_fir_filter_ccf.h>
|
||||
#include "grsamplebuffer.h"
|
||||
#include "memory_sink.h"
|
||||
#include "memory_source.h"
|
||||
|
||||
PlotView::PlotView(InputSource *input) : cursors(this), viewRange({0, 0})
|
||||
{
|
||||
mainSampleSource = input;
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
enableCursors(false);
|
||||
viewport()->installEventFilter(&cursors);
|
||||
connect(&cursors, SIGNAL(cursorsMoved()), this, SLOT(cursorsMoved()));
|
||||
|
||||
spectrogramPlot = new SpectrogramPlot(mainSampleSource);
|
||||
plots.emplace_back(spectrogramPlot);
|
||||
|
||||
mainSampleSource->subscribe(this);
|
||||
}
|
||||
|
||||
TracePlot* PlotView::createIQPlot(SampleSource<std::complex<float>> *src)
|
||||
{
|
||||
gr::top_block_sptr iq_tb = gr::make_top_block("multiply");
|
||||
auto iq_mem_source = gr::blocks::memory_source::make(8);
|
||||
auto iq_mem_sink = gr::blocks::memory_sink::make(8);
|
||||
auto multiply = gr::blocks::multiply_const_cc::make(20);
|
||||
if (selection || true) {
|
||||
float centre = -0.05; //(selectionFreq.first + selectionFreq.second) / 2;
|
||||
float cutoff = 0.02; //std::abs(selectionFreq.first - centre);
|
||||
auto lp_taps = gr::filter::firdes::low_pass(1.0, 1.0, cutoff, cutoff / 2);
|
||||
auto filter = gr::filter::freq_xlating_fir_filter_ccf::make(1, lp_taps, centre, 1.0);
|
||||
|
||||
iq_tb->connect(iq_mem_source, 0, filter, 0);
|
||||
iq_tb->connect(filter, 0, multiply, 0);
|
||||
iq_tb->connect(multiply, 0, iq_mem_sink, 0);
|
||||
} else {
|
||||
iq_tb->connect(iq_mem_source, 0, multiply, 0);
|
||||
iq_tb->connect(multiply, 0, iq_mem_sink, 0);
|
||||
}
|
||||
|
||||
auto iq_src = std::make_shared<GRSampleBuffer<std::complex<float>, std::complex<float>>>(mainSampleSource, iq_tb, iq_mem_source, iq_mem_sink);
|
||||
return new TracePlot(iq_src);
|
||||
}
|
||||
|
||||
TracePlot* PlotView::createQuadratureDemodPlot(SampleSource<std::complex<float>> *src)
|
||||
{
|
||||
gr::top_block_sptr quad_demod_tb = gr::make_top_block("quad_demod");
|
||||
auto quad_demod_mem_source = gr::blocks::memory_source::make(8);
|
||||
auto quad_demod_mem_sink = gr::blocks::memory_sink::make(4);
|
||||
auto quad_demod = gr::analog::quadrature_demod_cf::make(5);
|
||||
quad_demod_tb->connect(quad_demod_mem_source, 0, quad_demod, 0);
|
||||
quad_demod_tb->connect(quad_demod, 0, quad_demod_mem_sink, 0);
|
||||
|
||||
return new TracePlot(
|
||||
std::make_shared<GRSampleBuffer<std::complex<float>, float>>(
|
||||
dynamic_cast<SampleSource<std::complex<float>>*>(src), quad_demod_tb, quad_demod_mem_source, quad_demod_mem_sink
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void PlotView::cursorsMoved()
|
||||
{
|
||||
selectedSamples = {
|
||||
horizontalScrollBar()->value() + cursors.selection().minimum * samplesPerLine(),
|
||||
horizontalScrollBar()->value() + cursors.selection().maximum * samplesPerLine()
|
||||
};
|
||||
|
||||
off_t sampleCount = selectedSamples.length();
|
||||
float selectionTime = sampleCount / (float)mainSampleSource->rate();
|
||||
emit timeSelectionChanged(selectionTime);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void PlotView::enableCursors(bool enabled)
|
||||
{
|
||||
cursorsEnabled = enabled;
|
||||
if (enabled) {
|
||||
int margin = viewport()->rect().width() / 3;
|
||||
cursors.setSelection({viewport()->rect().left() + margin, viewport()->rect().right() - margin});
|
||||
}
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void PlotView::invalidateEvent()
|
||||
{
|
||||
horizontalScrollBar()->setMinimum(0);
|
||||
horizontalScrollBar()->setMaximum(mainSampleSource->count());
|
||||
}
|
||||
|
||||
void PlotView::setCursorBits(int bits)
|
||||
{
|
||||
cursors.setBits(bits);
|
||||
cursorsMoved();
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void PlotView::setFFTAndZoom(int size, int zoom)
|
||||
{
|
||||
// Set new FFT size
|
||||
fftSize = size;
|
||||
if (spectrogramPlot != nullptr)
|
||||
spectrogramPlot->setFFTSize(size);
|
||||
|
||||
// Set new zoom level
|
||||
zoomLevel = zoom;
|
||||
if (spectrogramPlot != nullptr)
|
||||
spectrogramPlot->setZoomLevel(zoom);
|
||||
|
||||
// Update horizontal (time) scrollbar
|
||||
horizontalScrollBar()->setSingleStep(size * 10 / zoomLevel);
|
||||
horizontalScrollBar()->setPageStep(size * 100 / zoomLevel);
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
void PlotView::setPowerMin(int power)
|
||||
{
|
||||
powerMin = power;
|
||||
if (spectrogramPlot != nullptr)
|
||||
spectrogramPlot->setPowerMin(power);
|
||||
updateView();
|
||||
}
|
||||
|
||||
void PlotView::setPowerMax(int power)
|
||||
{
|
||||
powerMax = power;
|
||||
if (spectrogramPlot != nullptr)
|
||||
spectrogramPlot->setPowerMax(power);
|
||||
updateView();
|
||||
}
|
||||
|
||||
void PlotView::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
if (mainSampleSource == nullptr) return;
|
||||
|
||||
QRect rect = QRect(0, 0, width(), height());
|
||||
QPainter painter(viewport());
|
||||
painter.fillRect(rect, Qt::black);
|
||||
|
||||
|
||||
#define PLOT_LAYER(paintFunc) \
|
||||
{ \
|
||||
int y = -verticalScrollBar()->value(); \
|
||||
for (auto&& plot : plots) { \
|
||||
QRect rect = QRect(0, y, width(), plot->height()); \
|
||||
plot->paintFunc(painter, rect, {viewRange.first, viewRange.second});\
|
||||
y += plot->height(); \
|
||||
} \
|
||||
}
|
||||
|
||||
PLOT_LAYER(paintBack);
|
||||
PLOT_LAYER(paintMid);
|
||||
PLOT_LAYER(paintFront);
|
||||
if (cursorsEnabled)
|
||||
cursors.paintFront(painter, rect, {viewRange.first, viewRange.second});
|
||||
|
||||
#undef PLOT_LAYER
|
||||
}
|
||||
|
||||
int PlotView::plotsHeight()
|
||||
{
|
||||
int height = 0;
|
||||
for (auto&& plot : plots) {
|
||||
height += plot->height();
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
void PlotView::resizeEvent(QResizeEvent * event)
|
||||
{
|
||||
updateView();
|
||||
}
|
||||
|
||||
off_t PlotView::samplesPerLine()
|
||||
{
|
||||
return fftSize / zoomLevel;
|
||||
}
|
||||
|
||||
void PlotView::scrollContentsBy(int dx, int dy)
|
||||
{
|
||||
updateView();
|
||||
}
|
||||
|
||||
void PlotView::updateView()
|
||||
{
|
||||
// Update current view
|
||||
viewRange = {
|
||||
horizontalScrollBar()->value(),
|
||||
horizontalScrollBar()->value() + width() * samplesPerLine()
|
||||
};
|
||||
horizontalScrollBar()->setMaximum(mainSampleSource->count() - ((width() - 1) * samplesPerLine()));
|
||||
|
||||
verticalScrollBar()->setMaximum(std::max(0, plotsHeight() - viewport()->height()));
|
||||
|
||||
// Update cursors
|
||||
QRect rect = viewport()->rect();
|
||||
range_t<int> newSelection = {
|
||||
(int)((selectedSamples.minimum - horizontalScrollBar()->value()) / samplesPerLine()),
|
||||
(int)((selectedSamples.maximum - horizontalScrollBar()->value()) / samplesPerLine())
|
||||
};
|
||||
cursors.setSelection(newSelection);
|
||||
|
||||
// Re-paint
|
||||
viewport()->update();
|
||||
}
|
||||
78
plotview.h
Normal file
78
plotview.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QPaintEvent>
|
||||
|
||||
#include "cursors.h"
|
||||
#include "inputsource.h"
|
||||
#include "plot.h"
|
||||
#include "samplesource.h"
|
||||
#include "spectrogramplot.h"
|
||||
#include "traceplot.h"
|
||||
|
||||
class PlotView : public QAbstractScrollArea, Subscriber
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PlotView(InputSource *input);
|
||||
|
||||
signals:
|
||||
void timeSelectionChanged(float time);
|
||||
|
||||
public slots:
|
||||
void cursorsMoved();
|
||||
void enableCursors(bool enable);
|
||||
void invalidateEvent();
|
||||
void setCursorBits(int bits);
|
||||
void setFFTAndZoom(int fftSize, int zoomLevel);
|
||||
void setPowerMin(int power);
|
||||
void setPowerMax(int power);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void resizeEvent(QResizeEvent * event);
|
||||
void scrollContentsBy(int dx, int dy);
|
||||
|
||||
private:
|
||||
Cursors cursors;
|
||||
SampleSource<std::complex<float>> *mainSampleSource = nullptr;
|
||||
SpectrogramPlot *spectrogramPlot = nullptr;
|
||||
TracePlot *iqPlot = nullptr;
|
||||
std::vector<std::unique_ptr<Plot>> plots;
|
||||
std::pair<off_t, off_t> viewRange;
|
||||
bool selection = false;
|
||||
range_t<off_t> selectedSamples;
|
||||
std::pair<float, float> selectionFreq;
|
||||
|
||||
int fftSize = 1024;
|
||||
int zoomLevel = 0;
|
||||
int powerMin;
|
||||
int powerMax;
|
||||
bool cursorsEnabled;
|
||||
|
||||
TracePlot* createIQPlot(SampleSource<std::complex<float>> *src);
|
||||
TracePlot* createQuadratureDemodPlot(SampleSource<std::complex<float>> *src);
|
||||
int plotsHeight();
|
||||
off_t samplesPerLine();
|
||||
void updateView();
|
||||
};
|
||||
53
samplebuffer.cpp
Normal file
53
samplebuffer.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "samplebuffer.h"
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
SampleBuffer<Tin, Tout>::SampleBuffer(SampleSource<Tin> *src) : src(src)
|
||||
{
|
||||
src->subscribe(this);
|
||||
}
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
SampleBuffer<Tin, Tout>::~SampleBuffer()
|
||||
{
|
||||
src->unsubscribe(this);
|
||||
}
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
std::unique_ptr<Tout[]> SampleBuffer<Tin, Tout>::getSamples(off_t start, off_t length)
|
||||
{
|
||||
auto samples = src->getSamples(start, length);
|
||||
if (samples == nullptr)
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<Tout[]> dest(new Tout[length]);
|
||||
work(samples.get(), dest.get(), length);
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
void SampleBuffer<Tin, Tout>::invalidateEvent()
|
||||
{
|
||||
SampleSource<Tout>::invalidate();
|
||||
}
|
||||
|
||||
template class SampleBuffer<std::complex<float>, std::complex<float>>;
|
||||
template class SampleBuffer<std::complex<float>, float>;
|
||||
44
samplebuffer.h
Normal file
44
samplebuffer.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
#include "samplesource.h"
|
||||
|
||||
template <typename Tin, typename Tout>
|
||||
class SampleBuffer : public SampleSource<Tout>, public Subscriber
|
||||
{
|
||||
private:
|
||||
SampleSource<Tin> *src;
|
||||
|
||||
public:
|
||||
SampleBuffer(SampleSource<Tin> *src);
|
||||
~SampleBuffer();
|
||||
void invalidateEvent();
|
||||
virtual std::unique_ptr<Tout[]> getSamples(off_t start, off_t length);
|
||||
virtual void work(void *input, void *output, int count) = 0;
|
||||
virtual off_t count() {
|
||||
return src->count();
|
||||
};
|
||||
off_t rate() {
|
||||
return src->rate();
|
||||
};
|
||||
};
|
||||
43
samplesource.cpp
Normal file
43
samplesource.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "samplesource.h"
|
||||
|
||||
template<typename T>
|
||||
void SampleSource<T>::subscribe(Subscriber *subscriber)
|
||||
{
|
||||
subscribers.insert(subscriber);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SampleSource<T>::invalidate()
|
||||
{
|
||||
for (auto subscriber : subscribers) {
|
||||
subscriber->invalidateEvent();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SampleSource<T>::unsubscribe(Subscriber *subscriber)
|
||||
{
|
||||
subscribers.erase(subscriber);
|
||||
}
|
||||
|
||||
template class SampleSource<std::complex<float>>;
|
||||
template class SampleSource<float>;
|
||||
47
samplesource.h
Normal file
47
samplesource.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include "abstractsamplesource.h"
|
||||
#include "subscriber.h"
|
||||
|
||||
template<typename T>
|
||||
class SampleSource : public AbstractSampleSource
|
||||
{
|
||||
|
||||
public:
|
||||
virtual ~SampleSource() {};
|
||||
|
||||
virtual std::unique_ptr<T[]> getSamples(off_t start, off_t length) = 0;
|
||||
virtual void invalidateEvent() { };
|
||||
virtual off_t count() = 0;
|
||||
virtual off_t rate() = 0;
|
||||
void subscribe(Subscriber *subscriber);
|
||||
void unsubscribe(Subscriber *subscriber);
|
||||
|
||||
protected:
|
||||
virtual void invalidate();
|
||||
|
||||
private:
|
||||
std::set<Subscriber*> subscribers;
|
||||
};
|
||||
330
spectrogram.cpp
330
spectrogram.cpp
@@ -1,330 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "spectrogram.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QRect>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
Spectrogram::Spectrogram()
|
||||
{
|
||||
sampleRate = 8000000;
|
||||
setFFTSize(1024);
|
||||
zoomLevel = 0;
|
||||
powerMax = 0.0f;
|
||||
powerMin = -50.0f;
|
||||
timeScaleIsEnabled = true;
|
||||
deltaDragIsEnabled = true;
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
float p = (float)i / 256;
|
||||
colormap[i] = QColor::fromHsvF(p * 0.83f, 1.0, 1.0 - p).rgba();
|
||||
}
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
Spectrogram::~Spectrogram()
|
||||
{
|
||||
delete fft;
|
||||
delete inputSource;
|
||||
}
|
||||
|
||||
QSize Spectrogram::sizeHint() const {
|
||||
return QSize(1024, 2048);
|
||||
}
|
||||
|
||||
void Spectrogram::openFile(QString fileName)
|
||||
{
|
||||
if (fileName != nullptr) {
|
||||
try {
|
||||
InputSource *newFile = new InputSource(fileName.toUtf8().constData());
|
||||
delete inputSource;
|
||||
pixmapCache.clear();
|
||||
fftCache.clear();
|
||||
inputSource = newFile;
|
||||
resize(fftSize, getHeight());
|
||||
} catch (std::runtime_error e) {
|
||||
// TODO: display error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> const T& clamp (const T& value, const T& min, const T& max) {
|
||||
return std::min(max, std::max(min, value));
|
||||
}
|
||||
|
||||
void Spectrogram::xyToFreqTime(int x, int y, float *freq, float *time) {
|
||||
*freq = labs(x - (fftSize / 2)) * sampleRate / 2 / (float)fftSize;
|
||||
*time = (float)lineToSample(y) / sampleRate;
|
||||
}
|
||||
|
||||
void Spectrogram::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (deltaDragIsEnabled) {
|
||||
cursorStartX = -1;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrogram::mouseMoveEvent(QMouseEvent *event) {
|
||||
float freq, time;
|
||||
xyToFreqTime(event->x(), event->y(), &freq, &time);
|
||||
emit cursorFrequencyChanged(QString::number(freq) + " Hz");
|
||||
emit cursorTimeChanged(QString::number(time) + " s");
|
||||
if (cursorStartX != -1) {
|
||||
float s_freq, s_time;
|
||||
xyToFreqTime(cursorStartX, cursorStartY, &s_freq, &s_time);
|
||||
emit deltaFrequencyChanged(QString::number(fabs(s_freq - freq)) + " Hz");
|
||||
emit deltaTimeChanged(QString::number(fabs(s_time - time)) + " s");
|
||||
cursorEndX = event->x();
|
||||
cursorEndY = event->y();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrogram::mousePressEvent(QMouseEvent *event) {
|
||||
if (cursorStartX == -1) {
|
||||
cursorEndX = cursorStartX = event->x();
|
||||
cursorEndY = cursorStartY = event->y();
|
||||
} else {
|
||||
cursorStartX = -1;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrogram::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QRect rect = event->rect();
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect, Qt::black);
|
||||
|
||||
if (inputSource != nullptr) {
|
||||
int height = rect.height();
|
||||
off_t y = rect.y();
|
||||
|
||||
QImage image(fftSize, height, QImage::Format_RGB32);
|
||||
|
||||
while (height > 0) {
|
||||
int tileOffset = y % linesPerTile(); // To handle drawing a partial first tile
|
||||
int drawHeight = std::min(linesPerTile() - tileOffset, height); // Draw rest of first tile, full tile, or partial final tile
|
||||
off_t tileId = lineToSample(y - tileOffset);
|
||||
QPixmap *tile = getPixmapTile(tileId);
|
||||
painter.drawPixmap(QRect(0, y, fftSize, drawHeight), *tile, QRect(0, tileOffset, fftSize, drawHeight));
|
||||
y += drawHeight;
|
||||
height -= drawHeight;
|
||||
}
|
||||
|
||||
paintTimeAxis(&painter, rect);
|
||||
paintCursors(&painter, rect);
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap* Spectrogram::getPixmapTile(off_t tile)
|
||||
{
|
||||
QPixmap *obj = pixmapCache.object(TileCacheKey(fftSize, zoomLevel, tile));
|
||||
if (obj != 0)
|
||||
return obj;
|
||||
|
||||
float *fftTile = getFFTTile(tile);
|
||||
obj = new QPixmap(fftSize, linesPerTile());
|
||||
QImage image(fftSize, linesPerTile(), QImage::Format_RGB32);
|
||||
for (int y = 0; y < linesPerTile(); y++) {
|
||||
float *line = &fftTile[y * fftSize];
|
||||
for (int x = 0; x < fftSize; x++) {
|
||||
float powerRange = std::abs(int(powerMin - powerMax));
|
||||
float normPower = (line[x] - powerMax) * -1.0f / powerRange;
|
||||
normPower = clamp(normPower, 0.0f, 1.0f);
|
||||
|
||||
image.setPixel(x, y, colormap[(uint8_t)(normPower * (256 - 1))]);
|
||||
}
|
||||
}
|
||||
obj->convertFromImage(image);
|
||||
pixmapCache.insert(TileCacheKey(fftSize, zoomLevel, tile), obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
float* Spectrogram::getFFTTile(off_t tile)
|
||||
{
|
||||
float *obj = fftCache.object(TileCacheKey(fftSize, zoomLevel, tile));
|
||||
if (obj != 0)
|
||||
return obj;
|
||||
|
||||
float *dest = new float[tileSize];
|
||||
float *ptr = dest;
|
||||
off_t sample = tile;
|
||||
while ((ptr - dest) < tileSize) {
|
||||
getLine(ptr, sample);
|
||||
sample += getStride();
|
||||
ptr += fftSize;
|
||||
}
|
||||
fftCache.insert(TileCacheKey(fftSize, zoomLevel, tile), dest);
|
||||
return dest;
|
||||
}
|
||||
|
||||
void Spectrogram::getLine(float *dest, off_t sample)
|
||||
{
|
||||
if (inputSource && fft) {
|
||||
fftwf_complex buffer[fftSize];
|
||||
inputSource->getSamples(buffer, sample, fftSize);
|
||||
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
buffer[i][0] *= window[i];
|
||||
buffer[i][1] *= window[i];
|
||||
}
|
||||
|
||||
fft->process(buffer, buffer);
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
int k = (i + fftSize / 2) % fftSize;
|
||||
float re = buffer[k][0];
|
||||
float im = buffer[k][1];
|
||||
float mag = sqrt(re * re + im * im) / fftSize;
|
||||
float magdb = 10 * log2(mag) / log2(10);
|
||||
*dest = magdb;
|
||||
dest++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrogram::paintCursors(QPainter *painter, QRect rect)
|
||||
{
|
||||
if (cursorStartX != -1) {
|
||||
painter->save();
|
||||
QPen pen(Qt::white, 1, Qt::DashLine);
|
||||
painter->setPen(pen);
|
||||
painter->drawLine(rect.left(), cursorStartY, rect.right(), cursorStartY);
|
||||
painter->drawLine(cursorStartX, rect.top(), cursorStartX, rect.bottom());
|
||||
painter->drawLine(rect.left(), cursorEndY, rect.right(), cursorEndY);
|
||||
painter->drawLine(cursorEndX, rect.top(), cursorEndX, rect.bottom());
|
||||
painter->restore();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrogram::paintTimeAxis(QPainter *painter, QRect rect)
|
||||
{
|
||||
if (timeScaleIsEnabled){
|
||||
// Round up for firstLine and round each to nearest linesPerGraduation
|
||||
int firstLine = ((rect.y() + linesPerGraduation - 1) / linesPerGraduation) * linesPerGraduation;
|
||||
int lastLine = ((rect.y() + rect.height()) / linesPerGraduation) * linesPerGraduation;
|
||||
|
||||
painter->save();
|
||||
QPen pen(Qt::white, 1, Qt::SolidLine);
|
||||
painter->setPen(pen);
|
||||
QFontMetrics fm(painter->font());
|
||||
int textOffset = fm.ascent() / 2 - 1;
|
||||
for (int line = firstLine; line <= lastLine; line += linesPerGraduation) {
|
||||
painter->drawLine(0, line, 10, line);
|
||||
painter->drawText(12, line + textOffset, sampleToTime(lineToSample(line)));
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrogram::setSampleRate(int rate)
|
||||
{
|
||||
sampleRate = rate;
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrogram::setFFTSize(int size)
|
||||
{
|
||||
fftSize = size;
|
||||
delete fft;
|
||||
fft = new FFT(fftSize);
|
||||
|
||||
window.reset(new float[fftSize]);
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
window[i] = 0.5f * (1.0f - cos(Tau * i / (fftSize - 1)));
|
||||
}
|
||||
|
||||
resize(fftSize, getHeight());
|
||||
}
|
||||
|
||||
void Spectrogram::setPowerMax(int power)
|
||||
{
|
||||
powerMax = power;
|
||||
pixmapCache.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrogram::setPowerMin(int power)
|
||||
{
|
||||
powerMin = power;
|
||||
pixmapCache.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrogram::setZoomLevel(int zoom)
|
||||
{
|
||||
zoomLevel = clamp(zoom, 0, (int)log2(fftSize));
|
||||
resize(fftSize, getHeight());
|
||||
}
|
||||
|
||||
void Spectrogram::setTimeScaleEnable(int state)
|
||||
{
|
||||
timeScaleIsEnabled = (state == Qt::Checked);
|
||||
pixmapCache.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrogram::setDeltaDragEnable(int state)
|
||||
{
|
||||
deltaDragIsEnabled = (state == Qt::Checked);
|
||||
}
|
||||
|
||||
|
||||
int Spectrogram::getHeight()
|
||||
{
|
||||
if (!inputSource)
|
||||
return 0;
|
||||
|
||||
return inputSource->getSampleCount() / getStride();
|
||||
}
|
||||
|
||||
int Spectrogram::getStride()
|
||||
{
|
||||
return fftSize / pow(2, zoomLevel);
|
||||
}
|
||||
|
||||
off_t Spectrogram::lineToSample(off_t line) {
|
||||
return line * getStride();
|
||||
}
|
||||
|
||||
int Spectrogram::sampleToLine(off_t sample) {
|
||||
return sample / getStride();
|
||||
}
|
||||
|
||||
QString Spectrogram::sampleToTime(off_t sample)
|
||||
{
|
||||
return QString::number((float)sample / sampleRate).append("s");
|
||||
}
|
||||
|
||||
int Spectrogram::linesPerTile() {
|
||||
return tileSize / fftSize;
|
||||
}
|
||||
|
||||
uint qHash(const TileCacheKey &key, uint seed) {
|
||||
return key.fftSize ^ key.zoomLevel ^ key.sample ^ seed;
|
||||
}
|
||||
118
spectrogram.h
118
spectrogram.h
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QWidget>
|
||||
#include "fft.h"
|
||||
#include "inputsource.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static const double Tau = M_PI * 2.0;
|
||||
|
||||
class TileCacheKey;
|
||||
|
||||
class Spectrogram : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Spectrogram();
|
||||
~Spectrogram();
|
||||
QSize sizeHint() const;
|
||||
int getHeight();
|
||||
int getStride();
|
||||
|
||||
signals:
|
||||
void cursorFrequencyChanged(QString);
|
||||
void cursorTimeChanged(QString);
|
||||
void deltaFrequencyChanged(QString);
|
||||
void deltaTimeChanged(QString);
|
||||
|
||||
public slots:
|
||||
void openFile(QString fileName);
|
||||
void setSampleRate(int rate);
|
||||
void setFFTSize(int size);
|
||||
void setPowerMax(int power);
|
||||
void setPowerMin(int power);
|
||||
void setZoomLevel(int zoom);
|
||||
void setTimeScaleEnable(int state);
|
||||
void setDeltaDragEnable(int state);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent * event);
|
||||
void mouseMoveEvent(QMouseEvent * event);
|
||||
void mousePressEvent(QMouseEvent * event);
|
||||
|
||||
|
||||
private:
|
||||
const int linesPerGraduation = 50;
|
||||
const int tileSize = 65536; // This must be a multiple of the maximum FFT size
|
||||
|
||||
InputSource *inputSource = nullptr;
|
||||
FFT *fft = nullptr;
|
||||
std::unique_ptr<float[]> window;
|
||||
fftwf_complex *lineBuffer = nullptr;
|
||||
QCache<TileCacheKey, QPixmap> pixmapCache;
|
||||
QCache<TileCacheKey, float> fftCache;
|
||||
uint colormap[256];
|
||||
|
||||
int sampleRate;
|
||||
int fftSize;
|
||||
int zoomLevel;
|
||||
float powerMax;
|
||||
float powerMin;
|
||||
bool timeScaleIsEnabled;
|
||||
bool deltaDragIsEnabled;
|
||||
int cursorStartX = -1, cursorStartY;
|
||||
int cursorEndX, cursorEndY;
|
||||
|
||||
QPixmap* getPixmapTile(off_t tile);
|
||||
float* getFFTTile(off_t tile);
|
||||
void getLine(float *dest, off_t sample);
|
||||
void paintTimeAxis(QPainter *painter, QRect rect);
|
||||
void paintCursors(QPainter *painter, QRect rect);
|
||||
off_t lineToSample(off_t line);
|
||||
int sampleToLine(off_t sample);
|
||||
QString sampleToTime(off_t sample);
|
||||
int linesPerTile();
|
||||
void xyToFreqTime(int x, int y, float *freq, float *time);
|
||||
};
|
||||
|
||||
class TileCacheKey {
|
||||
|
||||
public:
|
||||
TileCacheKey(int fftSize, int zoomLevel, off_t sample) {
|
||||
this->fftSize = fftSize;
|
||||
this->zoomLevel = zoomLevel;
|
||||
this->sample = sample;
|
||||
}
|
||||
|
||||
bool operator==(const TileCacheKey &k2) const {
|
||||
return (this->fftSize == k2.fftSize) &&
|
||||
(this->zoomLevel == k2.zoomLevel) &&
|
||||
(this->sample == k2.sample);
|
||||
}
|
||||
|
||||
int fftSize;
|
||||
int zoomLevel;
|
||||
off_t sample;
|
||||
};
|
||||
@@ -24,74 +24,120 @@
|
||||
#include <cmath>
|
||||
|
||||
SpectrogramControls::SpectrogramControls(const QString & title, QWidget * parent)
|
||||
: QDockWidget::QDockWidget(title, parent)
|
||||
: QDockWidget::QDockWidget(title, parent)
|
||||
{
|
||||
widget = new QWidget(this);
|
||||
layout = new QFormLayout(widget);
|
||||
widget = new QWidget(this);
|
||||
layout = new QFormLayout(widget);
|
||||
|
||||
fileOpenButton = new QPushButton("Open file...", widget);
|
||||
layout->addRow(fileOpenButton);
|
||||
fileOpenButton = new QPushButton("Open file...", widget);
|
||||
layout->addRow(fileOpenButton);
|
||||
|
||||
sampleRate = new QLineEdit("8000000");
|
||||
sampleRate->setValidator(new QIntValidator(this));
|
||||
layout->addRow(new QLabel(tr("Sample rate:")), sampleRate);
|
||||
sampleRate = new QLineEdit();
|
||||
sampleRate->setValidator(new QIntValidator(this));
|
||||
layout->addRow(new QLabel(tr("Sample rate:")), sampleRate);
|
||||
|
||||
fftSizeSlider = new QSlider(Qt::Horizontal, widget);
|
||||
fftSizeSlider->setRange(7, 13);
|
||||
fftSizeSlider->setValue(10);
|
||||
layout->addRow(new QLabel(tr("FFT size:")), fftSizeSlider);
|
||||
// Spectrogram settings
|
||||
layout->addRow(new QLabel()); // TODO: find a better way to add an empty row?
|
||||
layout->addRow(new QLabel(tr("<b>Spectrogram</b>")));
|
||||
|
||||
zoomLevelSlider = new QSlider(Qt::Horizontal, widget);
|
||||
zoomLevelSlider->setRange(0, 5);
|
||||
zoomLevelSlider->setValue(0);
|
||||
layout->addRow(new QLabel(tr("Zoom:")), zoomLevelSlider);
|
||||
fftSizeSlider = new QSlider(Qt::Horizontal, widget);
|
||||
fftSizeSlider->setRange(7, 13);
|
||||
layout->addRow(new QLabel(tr("FFT size:")), fftSizeSlider);
|
||||
|
||||
powerMaxSlider = new QSlider(Qt::Horizontal, widget);
|
||||
powerMaxSlider->setRange(-100, 20);
|
||||
powerMaxSlider->setValue(0);
|
||||
layout->addRow(new QLabel(tr("Power max:")), powerMaxSlider);
|
||||
zoomLevelSlider = new QSlider(Qt::Horizontal, widget);
|
||||
zoomLevelSlider->setRange(0, 10);
|
||||
layout->addRow(new QLabel(tr("Zoom:")), zoomLevelSlider);
|
||||
|
||||
powerMinSlider = new QSlider(Qt::Horizontal, widget);
|
||||
powerMinSlider->setRange(-100, 20);
|
||||
powerMinSlider->setValue(-50);
|
||||
layout->addRow(new QLabel(tr("Power min:")), powerMinSlider);
|
||||
powerMaxSlider = new QSlider(Qt::Horizontal, widget);
|
||||
powerMaxSlider->setRange(-100, 20);
|
||||
layout->addRow(new QLabel(tr("Power max:")), powerMaxSlider);
|
||||
|
||||
timeScaleCheckBox = new QCheckBox(widget);
|
||||
timeScaleCheckBox->setCheckState(Qt::Checked);
|
||||
layout->addRow(new QLabel(tr("time overlay:")), timeScaleCheckBox);
|
||||
powerMinSlider = new QSlider(Qt::Horizontal, widget);
|
||||
powerMinSlider->setRange(-100, 20);
|
||||
layout->addRow(new QLabel(tr("Power min:")), powerMinSlider);
|
||||
|
||||
cursorFrequencyLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Cursor frequency:")), cursorFrequencyLabel);
|
||||
// Time selection settings
|
||||
layout->addRow(new QLabel()); // TODO: find a better way to add an empty row?
|
||||
layout->addRow(new QLabel(tr("<b>Time selection</b>")));
|
||||
|
||||
cursorTimeLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Cursor time:")), cursorTimeLabel);
|
||||
cursorsCheckBox = new QCheckBox(widget);
|
||||
layout->addRow(new QLabel(tr("Enable cursors:")), cursorsCheckBox);
|
||||
|
||||
deltaDragCheckBox = new QCheckBox(widget);
|
||||
deltaDragCheckBox->setCheckState(Qt::Checked);
|
||||
layout->addRow(new QLabel(tr("Delta dragging:")), deltaDragCheckBox);
|
||||
cursorBitsSpinBox = new QSpinBox();
|
||||
cursorBitsSpinBox->setMinimum(1);
|
||||
cursorBitsSpinBox->setMaximum(9999);
|
||||
layout->addRow(new QLabel(tr("Bits:")), cursorBitsSpinBox);
|
||||
|
||||
deltaFrequencyLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Delta frequency:")), deltaFrequencyLabel);
|
||||
timeSelectionFreqLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Frequency:")), timeSelectionFreqLabel);
|
||||
|
||||
deltaTimeLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Delta time:")), deltaTimeLabel);
|
||||
timeSelectionTimeLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Time:")), timeSelectionTimeLabel);
|
||||
|
||||
widget->setLayout(layout);
|
||||
setWidget(widget);
|
||||
bitSelectionFreqLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Bit frequency:")), bitSelectionFreqLabel);
|
||||
|
||||
connect(fftSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(fftSizeSliderChanged(int)));
|
||||
connect(fileOpenButton, SIGNAL(clicked()), this, SLOT(fileOpenButtonClicked()));
|
||||
bitSelectionTimeLabel = new QLabel();
|
||||
layout->addRow(new QLabel(tr("Bit time:")), bitSelectionTimeLabel);
|
||||
|
||||
widget->setLayout(layout);
|
||||
setWidget(widget);
|
||||
|
||||
connect(fftSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(fftOrZoomChanged(int)));
|
||||
connect(zoomLevelSlider, SIGNAL(valueChanged(int)), this, SLOT(fftOrZoomChanged(int)));
|
||||
connect(fileOpenButton, SIGNAL(clicked()), this, SLOT(fileOpenButtonClicked()));
|
||||
connect(cursorsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(cursorsStateChanged(int)));
|
||||
}
|
||||
|
||||
void SpectrogramControls::fftSizeSliderChanged(int size)
|
||||
void SpectrogramControls::clearCursorLabels()
|
||||
{
|
||||
emit fftSizeChanged((int)pow(2, size));
|
||||
timeSelectionTimeLabel->setText("");
|
||||
timeSelectionFreqLabel->setText("");
|
||||
bitSelectionTimeLabel->setText("");
|
||||
bitSelectionFreqLabel->setText("");
|
||||
}
|
||||
|
||||
void SpectrogramControls::cursorsStateChanged(int state)
|
||||
{
|
||||
if (state == Qt::Unchecked) {
|
||||
clearCursorLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrogramControls::setDefaults()
|
||||
{
|
||||
sampleRate->setText("8000000");
|
||||
fftSizeSlider->setValue(9);
|
||||
zoomLevelSlider->setValue(0);
|
||||
powerMaxSlider->setValue(0);
|
||||
powerMinSlider->setValue(-50);
|
||||
cursorsCheckBox->setCheckState(Qt::Unchecked);
|
||||
cursorBitsSpinBox->setValue(1);
|
||||
}
|
||||
|
||||
void SpectrogramControls::fftOrZoomChanged(int value)
|
||||
{
|
||||
int fftSize = pow(2, fftSizeSlider->value());
|
||||
int zoomLevel = std::min(fftSize, (int)pow(2, zoomLevelSlider->value()));
|
||||
emit fftOrZoomChanged(fftSize, zoomLevel);
|
||||
}
|
||||
|
||||
void SpectrogramControls::fileOpenButtonClicked()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open File"), "", tr("Sample file (*.cfile *.bin);;All files (*)")
|
||||
);
|
||||
emit openFile(fileName);
|
||||
QString fileName = QFileDialog::getOpenFileName(
|
||||
this, tr("Open File"), "", tr("Sample file (*.cfile *.bin);;All files (*)")
|
||||
);
|
||||
emit openFile(fileName);
|
||||
}
|
||||
|
||||
void SpectrogramControls::timeSelectionChanged(float time)
|
||||
{
|
||||
if (cursorsCheckBox->checkState() == Qt::Checked) {
|
||||
timeSelectionTimeLabel->setText(QString::number(time) + "s");
|
||||
timeSelectionFreqLabel->setText(QString::number(1 / time) + "Hz");
|
||||
|
||||
int bits = cursorBitsSpinBox->value();
|
||||
bitSelectionTimeLabel->setText(QString::number(time / bits) + "s");
|
||||
bitSelectionFreqLabel->setText(QString::number(bits / time) + "Hz");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,37 +24,46 @@
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
|
||||
class SpectrogramControls : public QDockWidget {
|
||||
Q_OBJECT
|
||||
class SpectrogramControls : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpectrogramControls(const QString & title, QWidget * parent);
|
||||
SpectrogramControls(const QString & title, QWidget * parent);
|
||||
void setDefaults();
|
||||
|
||||
signals:
|
||||
void fftSizeChanged(int size);
|
||||
void openFile(QString fileName);
|
||||
void fftOrZoomChanged(int fftSize, int zoomLevel);
|
||||
void openFile(QString fileName);
|
||||
|
||||
public slots:
|
||||
void timeSelectionChanged(float time);
|
||||
|
||||
private slots:
|
||||
void fftSizeSliderChanged(int size);
|
||||
void fileOpenButtonClicked();
|
||||
void fftOrZoomChanged(int value);
|
||||
void fileOpenButtonClicked();
|
||||
void cursorsStateChanged(int state);
|
||||
|
||||
private:
|
||||
QWidget *widget;
|
||||
QFormLayout *layout;
|
||||
QWidget *widget;
|
||||
QFormLayout *layout;
|
||||
void clearCursorLabels();
|
||||
|
||||
public:
|
||||
QPushButton *fileOpenButton;
|
||||
QLineEdit *sampleRate;
|
||||
QSlider *fftSizeSlider;
|
||||
QSlider *zoomLevelSlider;
|
||||
QSlider *powerMaxSlider;
|
||||
QSlider *powerMinSlider;
|
||||
QCheckBox *timeScaleCheckBox;
|
||||
QLabel *cursorFrequencyLabel;
|
||||
QLabel *cursorTimeLabel;
|
||||
QCheckBox *deltaDragCheckBox;
|
||||
QLabel *deltaFrequencyLabel;
|
||||
QLabel *deltaTimeLabel;
|
||||
QPushButton *fileOpenButton;
|
||||
QLineEdit *sampleRate;
|
||||
QSlider *fftSizeSlider;
|
||||
QSlider *zoomLevelSlider;
|
||||
QSlider *powerMaxSlider;
|
||||
QSlider *powerMinSlider;
|
||||
QCheckBox *cursorsCheckBox;
|
||||
QSpinBox *cursorBitsSpinBox;
|
||||
QLabel *timeSelectionFreqLabel;
|
||||
QLabel *timeSelectionTimeLabel;
|
||||
QLabel *bitSelectionFreqLabel;
|
||||
QLabel *bitSelectionTimeLabel;
|
||||
};
|
||||
|
||||
204
spectrogramplot.cpp
Normal file
204
spectrogramplot.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "spectrogramplot.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
#include <QRect>
|
||||
|
||||
#include <cstdlib>
|
||||
#include "util.h"
|
||||
|
||||
|
||||
SpectrogramPlot::SpectrogramPlot(SampleSource<std::complex<float>> *src)
|
||||
{
|
||||
inputSource = src;
|
||||
sampleRate = 8000000;
|
||||
setFFTSize(512);
|
||||
zoomLevel = 0;
|
||||
powerMax = 0.0f;
|
||||
powerMin = -50.0f;
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
float p = (float)i / 256;
|
||||
colormap[i] = QColor::fromHsvF(p * 0.83f, 1.0, 1.0 - p).rgba();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SpectrogramPlot::paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
if (!inputSource || inputSource->count() == 0)
|
||||
return;
|
||||
|
||||
off_t sampleOffset = sampleRange.minimum % getStride();
|
||||
off_t tileID = sampleRange.minimum - sampleOffset;
|
||||
int xoffset = sampleOffset / fftSize;
|
||||
for (int x = rect.left(); x < rect.right(); x += linesPerTile()) {
|
||||
QPixmap *tile = getPixmapTile(tileID);
|
||||
// TODO: don't draw past rect.right()
|
||||
// TODO: handle partial final tile
|
||||
painter.drawPixmap(QRect(x, rect.y(), linesPerTile() - xoffset, fftSize), *tile, QRect(xoffset, 0, linesPerTile() - xoffset, fftSize));
|
||||
xoffset = 0;
|
||||
tileID += getStride() * linesPerTile();
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap* SpectrogramPlot::getPixmapTile(off_t tile)
|
||||
{
|
||||
QPixmap *obj = pixmapCache.object(TileCacheKey(fftSize, zoomLevel, tile));
|
||||
if (obj != 0)
|
||||
return obj;
|
||||
|
||||
float *fftTile = getFFTTile(tile);
|
||||
obj = new QPixmap(linesPerTile(), fftSize);
|
||||
QImage image(linesPerTile(), fftSize, QImage::Format_RGB32);
|
||||
for (int x = 0; x < linesPerTile(); x++) {
|
||||
float *line = &fftTile[x * fftSize];
|
||||
for (int y = 0; y < fftSize; y++) {
|
||||
float powerRange = std::abs(int(powerMin - powerMax));
|
||||
float normPower = (line[y] - powerMax) * -1.0f / powerRange;
|
||||
normPower = clamp(normPower, 0.0f, 1.0f);
|
||||
|
||||
image.setPixel(x, fftSize - y - 1, colormap[(uint8_t)(normPower * (256 - 1))]);
|
||||
}
|
||||
}
|
||||
obj->convertFromImage(image);
|
||||
pixmapCache.insert(TileCacheKey(fftSize, zoomLevel, tile), obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
float* SpectrogramPlot::getFFTTile(off_t tile)
|
||||
{
|
||||
float *obj = fftCache.object(TileCacheKey(fftSize, zoomLevel, tile));
|
||||
if (obj != 0)
|
||||
return obj;
|
||||
|
||||
float *dest = new float[tileSize];
|
||||
float *ptr = dest;
|
||||
off_t sample = tile;
|
||||
while ((ptr - dest) < tileSize) {
|
||||
getLine(ptr, sample);
|
||||
sample += getStride();
|
||||
ptr += fftSize;
|
||||
}
|
||||
fftCache.insert(TileCacheKey(fftSize, zoomLevel, tile), dest);
|
||||
return dest;
|
||||
}
|
||||
|
||||
void SpectrogramPlot::getLine(float *dest, off_t sample)
|
||||
{
|
||||
if (inputSource && fft) {
|
||||
auto buffer = inputSource->getSamples(sample, fftSize);
|
||||
if (buffer == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
buffer[i].real(buffer[i].real() * window[i]);
|
||||
buffer[i].imag(buffer[i].imag() * window[i]);
|
||||
}
|
||||
|
||||
fft->process(buffer.get(), buffer.get());
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
int k = (i + fftSize / 2) % fftSize;
|
||||
float re = buffer[k].real();
|
||||
float im = buffer[k].imag();
|
||||
float mag = sqrt(re * re + im * im) / fftSize;
|
||||
float magdb = 10 * log2(mag) / log2(10);
|
||||
*dest = magdb;
|
||||
dest++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrogramPlot::setSampleRate(int rate)
|
||||
{
|
||||
sampleRate = rate;
|
||||
}
|
||||
|
||||
void SpectrogramPlot::setFFTSize(int size)
|
||||
{
|
||||
fftSize = size;
|
||||
fft.reset(new FFT(fftSize));
|
||||
|
||||
window.reset(new float[fftSize]);
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
window[i] = 0.5f * (1.0f - cos(Tau * i / (fftSize - 1)));
|
||||
}
|
||||
|
||||
setHeight(fftSize);
|
||||
}
|
||||
|
||||
void SpectrogramPlot::setPowerMax(int power)
|
||||
{
|
||||
powerMax = power;
|
||||
pixmapCache.clear();
|
||||
}
|
||||
|
||||
void SpectrogramPlot::setPowerMin(int power)
|
||||
{
|
||||
powerMin = power;
|
||||
pixmapCache.clear();
|
||||
}
|
||||
|
||||
void SpectrogramPlot::setZoomLevel(int zoom)
|
||||
{
|
||||
zoomLevel = zoom;
|
||||
}
|
||||
|
||||
int SpectrogramPlot::getHeight()
|
||||
{
|
||||
if (!inputSource)
|
||||
return 0;
|
||||
|
||||
return inputSource->count() / getStride();
|
||||
}
|
||||
|
||||
int SpectrogramPlot::getStride()
|
||||
{
|
||||
return fftSize / zoomLevel;
|
||||
}
|
||||
|
||||
off_t SpectrogramPlot::lineToSample(off_t line)
|
||||
{
|
||||
return line * getStride();
|
||||
}
|
||||
|
||||
int SpectrogramPlot::sampleToLine(off_t sample)
|
||||
{
|
||||
return sample / getStride();
|
||||
}
|
||||
|
||||
QString SpectrogramPlot::sampleToTime(off_t sample)
|
||||
{
|
||||
return QString::number((float)sample / sampleRate).append("s");
|
||||
}
|
||||
|
||||
int SpectrogramPlot::linesPerTile()
|
||||
{
|
||||
return tileSize / fftSize;
|
||||
}
|
||||
|
||||
uint qHash(const TileCacheKey &key, uint seed)
|
||||
{
|
||||
return key.fftSize ^ key.zoomLevel ^ key.sample ^ seed;
|
||||
}
|
||||
109
spectrogramplot.h
Normal file
109
spectrogramplot.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QWidget>
|
||||
#include "fft.h"
|
||||
#include "inputsource.h"
|
||||
#include "plot.h"
|
||||
|
||||
#include <memory>
|
||||
#include <math.h>
|
||||
|
||||
static const double Tau = M_PI * 2.0;
|
||||
|
||||
class TileCacheKey;
|
||||
|
||||
class SpectrogramPlot : public Plot
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpectrogramPlot(SampleSource<std::complex<float>> *src);
|
||||
|
||||
void paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
|
||||
QSize sizeHint() const;
|
||||
int getHeight();
|
||||
int getStride();
|
||||
off_t lineToSample(off_t line);
|
||||
|
||||
SampleSource<std::complex<float>> *inputSource = nullptr;
|
||||
|
||||
public slots:
|
||||
void setSampleRate(int rate);
|
||||
void setFFTSize(int size);
|
||||
void setPowerMax(int power);
|
||||
void setPowerMin(int power);
|
||||
void setZoomLevel(int zoom);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent * event);
|
||||
void mouseMoveEvent(QMouseEvent * event);
|
||||
void mousePressEvent(QMouseEvent * event);
|
||||
|
||||
|
||||
private:
|
||||
const int linesPerGraduation = 50;
|
||||
const int tileSize = 65536; // This must be a multiple of the maximum FFT size
|
||||
|
||||
std::unique_ptr<FFT> fft;
|
||||
std::unique_ptr<float[]> window;
|
||||
fftwf_complex *lineBuffer = nullptr;
|
||||
QCache<TileCacheKey, QPixmap> pixmapCache;
|
||||
QCache<TileCacheKey, float> fftCache;
|
||||
uint colormap[256];
|
||||
|
||||
int sampleRate;
|
||||
int fftSize;
|
||||
int zoomLevel;
|
||||
float powerMax;
|
||||
float powerMin;
|
||||
|
||||
QPixmap* getPixmapTile(off_t tile);
|
||||
float* getFFTTile(off_t tile);
|
||||
void getLine(float *dest, off_t sample);
|
||||
void paintCursors(QPainter *painter, QRect rect);
|
||||
int sampleToLine(off_t sample);
|
||||
QString sampleToTime(off_t sample);
|
||||
int linesPerTile();
|
||||
};
|
||||
|
||||
class TileCacheKey
|
||||
{
|
||||
|
||||
public:
|
||||
TileCacheKey(int fftSize, int zoomLevel, off_t sample) {
|
||||
this->fftSize = fftSize;
|
||||
this->zoomLevel = zoomLevel;
|
||||
this->sample = sample;
|
||||
}
|
||||
|
||||
bool operator==(const TileCacheKey &k2) const {
|
||||
return (this->fftSize == k2.fftSize) &&
|
||||
(this->zoomLevel == k2.zoomLevel) &&
|
||||
(this->sample == k2.sample);
|
||||
}
|
||||
|
||||
int fftSize;
|
||||
int zoomLevel;
|
||||
off_t sample;
|
||||
};
|
||||
27
subscriber.h
Normal file
27
subscriber.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
class Subscriber
|
||||
{
|
||||
public:
|
||||
virtual void invalidateEvent() = 0;
|
||||
};
|
||||
74
traceplot.cpp
Normal file
74
traceplot.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "samplesource.h"
|
||||
#include "traceplot.h"
|
||||
|
||||
TracePlot::TracePlot(std::shared_ptr<AbstractSampleSource> source) : sampleSource(source) {
|
||||
|
||||
}
|
||||
|
||||
void TracePlot::paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange)
|
||||
{
|
||||
auto firstSample = sampleRange.minimum;
|
||||
auto length = sampleRange.length();
|
||||
|
||||
// Is it a 2-channel (complex) trace?
|
||||
if (auto src = dynamic_cast<SampleSource<std::complex<float>>*>(sampleSource.get())) {
|
||||
auto samples = src->getSamples(firstSample, length);
|
||||
if (samples == nullptr)
|
||||
return;
|
||||
|
||||
painter.setPen(Qt::red);
|
||||
plotTrace(painter, rect, reinterpret_cast<float*>(samples.get()), length, 2);
|
||||
painter.setPen(Qt::blue);
|
||||
plotTrace(painter, rect, reinterpret_cast<float*>(samples.get())+1, length, 2);
|
||||
|
||||
// Otherwise is it single channel?
|
||||
} else if (auto src = dynamic_cast<SampleSource<float>*>(sampleSource.get())) {
|
||||
auto samples = src->getSamples(firstSample, length);
|
||||
if (samples == nullptr)
|
||||
return;
|
||||
|
||||
painter.setPen(Qt::green);
|
||||
plotTrace(painter, rect, samples.get(), length, 1);
|
||||
} else {
|
||||
throw std::runtime_error("TracePlot::paintMid: Unsupported source type");
|
||||
}
|
||||
}
|
||||
|
||||
void TracePlot::plotTrace(QPainter &painter, QRect &rect, float *samples, off_t count, int step = 1)
|
||||
{
|
||||
int xprev = 0;
|
||||
int yprev = 0;
|
||||
for (off_t i = 0; i < count; i++) {
|
||||
float sample = samples[i*step];
|
||||
int x = (float)i / count * rect.width();
|
||||
int y = rect.height() - ((sample * rect.height()/2) + rect.height()/2);
|
||||
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x >= rect.width()-1) x = rect.width()-2;
|
||||
if (y >= rect.height()-1) y = rect.height()-2;
|
||||
|
||||
painter.drawLine(xprev + rect.x(), yprev + rect.y(), x + rect.x(), y + rect.y());
|
||||
xprev = x;
|
||||
yprev = y;
|
||||
}
|
||||
}
|
||||
40
traceplot.h
Normal file
40
traceplot.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "abstractsamplesource.h"
|
||||
#include "plot.h"
|
||||
#include "util.h"
|
||||
|
||||
class TracePlot : public Plot
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TracePlot(std::shared_ptr<AbstractSampleSource> source);
|
||||
|
||||
void paintMid(QPainter &painter, QRect &rect, range_t<off_t> sampleRange);
|
||||
std::shared_ptr<AbstractSampleSource> source() { return sampleSource; };
|
||||
|
||||
private:
|
||||
std::shared_ptr<AbstractSampleSource> sampleSource;
|
||||
|
||||
void plotTrace(QPainter &painter, QRect &rect, float *samples, off_t count, int step);
|
||||
};
|
||||
75
util.h
Normal file
75
util.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2016, Mike Walters <mike@flomp.net>
|
||||
* Copyright (C) 2016, Jared Boone, ShareBrained Technology, Inc.
|
||||
*
|
||||
* This file is part of inspectrum.
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
|
||||
template <class T> const T& clamp (const T& value, const T& min, const T& max)
|
||||
{
|
||||
return std::min(max, std::max(min, value));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
struct range_t {
|
||||
T minimum;
|
||||
T maximum;
|
||||
|
||||
range_t<T>& operator=(const range_t<T> &other) {
|
||||
minimum = other.minimum;
|
||||
maximum = other.maximum;
|
||||
}
|
||||
|
||||
range_t<T>& operator=(const std::initializer_list<T> &other) {
|
||||
if (other.size() == 2) {
|
||||
minimum = *other.begin();
|
||||
maximum = *(other.begin() + 1);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
const T length() {
|
||||
return maximum - minimum;
|
||||
}
|
||||
|
||||
const T& clip(const T& value) const {
|
||||
return clamp(value, minimum, maximum);
|
||||
}
|
||||
|
||||
void reset_if_outside(T& value, const T& reset_value) const {
|
||||
if( (value < minimum ) ||
|
||||
(value > maximum ) ) {
|
||||
value = reset_value;
|
||||
}
|
||||
}
|
||||
|
||||
bool below_range(const T& value) const {
|
||||
return value < minimum;
|
||||
}
|
||||
|
||||
bool contains(const T& value) const {
|
||||
// TODO: Subtle gotcha here! Range test doesn't include maximum!
|
||||
return (value >= minimum) && (value < maximum);
|
||||
}
|
||||
|
||||
bool out_of_range(const T& value) const {
|
||||
// TODO: Subtle gotcha here! Range test in contains() doesn't include maximum!
|
||||
return !contains(value);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user