New stuff

This commit is contained in:
Ryzerth 2020-07-19 15:59:44 +02:00
parent 370324bc68
commit cbf0b6290d
28 changed files with 9162 additions and 393 deletions

View File

@ -45,3 +45,6 @@ target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l)
# PortAudio
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"

BIN
res/icons/play_raw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
res/icons/stop_raw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -25,7 +25,31 @@ namespace dsp {
}
void start() {
if (running) {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
if (!running) {
return;
}
_in->stopReader();
output.stopWriter();
_workerThread.join();
_in->clearReadStop();
output.clearWriteStop();
running = false;
}
void setBlockSize(int blockSize) {
if (running) {
return;
}
_bufferSize = blockSize;
output.setMaxLatency(blockSize * 2);
}
stream<complex_t> output;
@ -37,9 +61,9 @@ namespace dsp {
float ibias = 0.0f;
float qbias = 0.0f;
while (true) {
_this->_in->read(buf, _this->_bufferSize);
if (_this->_in->read(buf, _this->_bufferSize) < 0) { break; };
if (_this->bypass) {
_this->output.write(buf, _this->_bufferSize);
if (_this->output.write(buf, _this->_bufferSize) < 0) { break; };
continue;
}
for (int i = 0; i < _this->_bufferSize; i++) {
@ -52,12 +76,14 @@ namespace dsp {
buf[i].i -= ibias;
buf[i].q -= qbias;
}
_this->output.write(buf, _this->_bufferSize);
if (_this->output.write(buf, _this->_bufferSize) < 0) { break; };
}
delete[] buf;
}
stream<complex_t>* _in;
int _bufferSize;
std::thread _workerThread;
bool running = false;
};
};

View File

@ -2,6 +2,8 @@
#include <thread>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/source.h>
#include <dsp/math.h>
/*
TODO:
@ -42,6 +44,8 @@ namespace dsp {
_input = in;
_blockSize = blockSize;
_phase = 0.0f;
_deviation = deviation;
_sampleRate = sampleRate;
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / deviation);
}
@ -82,6 +86,16 @@ namespace dsp {
output.setMaxLatency(_blockSize * 2);
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / _deviation);
}
void setDeviation(float deviation) {
_deviation = deviation;
_phasorSpeed = (2 * 3.1415926535) / (_sampleRate / _deviation);
}
stream<float> output;
private:
@ -109,6 +123,8 @@ namespace dsp {
int _blockSize;
float _phase;
float _phasorSpeed;
float _deviation;
float _sampleRate;
std::thread _workerThread;
};
@ -195,4 +211,104 @@ namespace dsp {
int _blockSize;
std::thread _workerThread;
};
class SSBDemod {
public:
SSBDemod() {
}
void init(stream<complex_t>* input, float sampleRate, float bandWidth, int blockSize) {
_blockSize = blockSize;
_bandWidth = bandWidth;
_mode = MODE_USB;
output.init(blockSize * 2);
lo.init(bandWidth / 2.0f, sampleRate, blockSize);
mixer.init(input, &lo.output, blockSize);
lo.start();
}
void start() {
mixer.start();
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
mixer.stop();
mixer.output.stopReader();
output.stopWriter();
_workerThread.join();
mixer.output.clearReadStop();
output.clearWriteStop();
running = false;
}
void setBlockSize(int blockSize) {
if (running) {
return;
}
_blockSize = blockSize;
}
void setMode(int mode) {
if (mode < 0 && mode >= _MODE_COUNT) {
return;
}
_mode = mode;
if (mode == MODE_USB) {
lo.setFrequency(_bandWidth / 2.0f);
}
else if (mode == MODE_LSB) {
lo.setFrequency(-_bandWidth / 2.0f);
}
}
stream<float> output;
enum {
MODE_USB,
MODE_LSB,
_MODE_COUNT
};
private:
static void _worker(SSBDemod* _this) {
complex_t* inBuf = new complex_t[_this->_blockSize];
float* outBuf = new float[_this->_blockSize];
float min, max, factor;
while (true) {
if (_this->mixer.output.read(inBuf, _this->_blockSize) < 0) { break; };
min = INFINITY;
max = -INFINITY;
for (int i = 0; i < _this->_blockSize; i++) {
outBuf[i] = inBuf[i].q;
if (inBuf[i].q < min) {
min = inBuf[i].q;
}
if (inBuf[i].q > max) {
max = inBuf[i].q;
}
}
factor = (max - min) / 2;
for (int i = 0; i < _this->_blockSize; i++) {
outBuf[i] /= factor;
}
if (_this->output.write(outBuf, _this->_blockSize) < 0) { break; };
}
delete[] inBuf;
delete[] outBuf;
}
std::thread _workerThread;
SineSource lo;
Multiplier mixer;
int _blockSize;
float _bandWidth;
int _mode;
bool running = false;
};
};

View File

@ -10,8 +10,17 @@
namespace dsp {
inline void BlackmanWindow(std::vector<float>& taps, float sampleRate, float cutoff, float transWidth) {
taps.clear();
float fc = cutoff / sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
int _M = 4.0f / (transWidth / sampleRate);
if (_M < 4) {
_M = 4;
}
if (_M % 2 == 0) { _M++; }
float M = _M;
float sum = 0.0f;
@ -131,7 +140,11 @@ namespace dsp {
return;
}
_blockSize = blockSize;
output.setMaxLatency((_blockSize * 2) / _decim);
output.setMaxLatency(getOutputBlockSize() * 2);
}
int getOutputBlockSize() {
return _blockSize / _decim;
}
stream<complex_t> output;
@ -302,6 +315,10 @@ namespace dsp {
output.setMaxLatency((_blockSize * 2) / _decim);
}
int getOutputBlockSize() {
return _blockSize / _decim;
}
stream<float> output;
private:

View File

@ -1,218 +0,0 @@
#pragma once
#include <condition_variable>
#include <algorithm>
#include <math.h>
#define STREAM_BUF_SZ 1000000
namespace dsp {
template <class T>
class stream {
public:
stream() {
}
stream(int maxLatency) {
size = STREAM_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
writec = 0;
readc = size - 1;
}
void init(int maxLatency) {
size = STREAM_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
writec = 0;
readc = size - 1;
}
int read(T* data, int len) {
int dataRead = 0;
while (dataRead < len) {
int canRead = waitUntilReadable();
if (canRead < 0) {
clearReadStop();
return -1;
}
int toRead = std::min(canRead, len - dataRead);
int len1 = (toRead >= (size - readc) ? (size - readc) : (toRead));
memcpy(&data[dataRead], &_buffer[readc], len1 * sizeof(T));
if (len1 < toRead) {
memcpy(&data[dataRead + len1], _buffer, (toRead - len1) * sizeof(T));
}
dataRead += toRead;
readc_mtx.lock();
readc = (readc + toRead) % size;
readc_mtx.unlock();
canWriteVar.notify_one();
}
return len;
}
int readAndSkip(T* data, int len, int skip) {
int dataRead = 0;
while (dataRead < len) {
int canRead = waitUntilReadable();
if (canRead < 0) {
clearReadStop();
return -1;
}
int toRead = std::min(canRead, len - dataRead);
int len1 = (toRead >= (size - readc) ? (size - readc) : (toRead));
memcpy(&data[dataRead], &_buffer[readc], len1 * sizeof(T));
if (len1 < toRead) {
memcpy(&data[dataRead + len1], _buffer, (toRead - len1) * sizeof(T));
}
dataRead += toRead;
readc_mtx.lock();
readc = (readc + toRead) % size;
readc_mtx.unlock();
canWriteVar.notify_one();
}
// Skip
dataRead = 0;
while (dataRead < skip) {
int canRead = waitUntilReadable();
int toRead = std::min(canRead, skip - dataRead);
dataRead += toRead;
readc_mtx.lock();
readc = (readc + toRead) % size;
readc_mtx.unlock();
canWriteVar.notify_one();
}
return len;
}
int waitUntilReadable() {
int canRead = readable();
if (canRead > 0) {
return canRead;
}
std::unique_lock<std::mutex> lck(writec_mtx);
canReadVar.wait(lck, [=](){ return ((this->readable(false) > 0) || this->getReadStop()); });
if (this->getReadStop()) {
return -1;
}
return this->readable(false);
}
int readable(bool lock = true) {
if (lock) { writec_mtx.lock(); }
int _wc = writec;
if (lock) { writec_mtx.unlock(); }
int readable = (_wc - readc) % this->size;
if (_wc < readc) {
readable = (this->size + readable);
}
return readable - 1;
}
int write(T* data, int len) {
int dataWrite = 0;
while (dataWrite < len) {
int canWrite = waitUntilWriteable();
if (canWrite < 0) {
clearWriteStop();
return -1;
}
int toWrite = std::min(canWrite, len - dataWrite);
int len1 = (toWrite >= (size - writec) ? (size - writec) : (toWrite));
memcpy(&_buffer[writec], &data[dataWrite], len1 * sizeof(T));
if (len1 < toWrite) {
memcpy(_buffer, &data[dataWrite + len1], (toWrite - len1) * sizeof(T));
}
dataWrite += toWrite;
writec_mtx.lock();
writec = (writec + toWrite) % size;
writec_mtx.unlock();
canReadVar.notify_one();
}
return len;
}
int waitUntilWriteable() {
int canWrite = writeable();
if (canWrite > 0) {
return canWrite;
}
std::unique_lock<std::mutex> lck(readc_mtx);
canWriteVar.wait(lck, [=](){ return ((this->writeable(false) > 0) || this->getWriteStop()); });
if (this->getWriteStop()) {
return -1;
}
return this->writeable(false);
}
int writeable(bool lock = true) {
if (lock) { readc_mtx.lock(); }
int _rc = readc;
if (lock) { readc_mtx.unlock(); }
int writeable = (_rc - writec) % this->size;
if (_rc < writec) {
writeable = (this->size + writeable);
}
return std::min<float>(writeable - 1, maxLatency - readable(false) - 1);
}
void stopReader() {
_stopReader = true;
canReadVar.notify_one();
}
void stopWriter() {
_stopWriter = true;
canWriteVar.notify_one();
}
bool getReadStop() {
return _stopReader;
}
bool getWriteStop() {
return _stopWriter;
}
void clearReadStop() {
_stopReader = false;
}
void clearWriteStop() {
_stopWriter = false;
}
void setMaxLatency(int maxLatency) {
this->maxLatency = maxLatency;
}
private:
T* _buffer;
int size;
int readc;
int writec;
int maxLatency;
bool _stopReader;
bool _stopWriter;
std::mutex readc_mtx;
std::mutex writec_mtx;
std::condition_variable canReadVar;
std::condition_variable canWriteVar;
};
};

View File

@ -79,11 +79,20 @@ namespace dsp {
T* inBuf = new T[_this->_blockSize];
T* outBuf = new T[_this->_blockSize * _this->_interpolation];
int outCount = _this->_blockSize * _this->_interpolation;
int interp = _this->_interpolation;
int count = 0;
while (true) {
if (_this->_input->read(inBuf, _this->_blockSize) < 0) { break; };
for (int i = 0; i < outCount; i++) {
outBuf[i] = inBuf[(int)((float)i / _this->_interpolation)];
}
// for (int i = 0; i < outCount; i += interp) {
// outBuf[i] = inBuf[count];
// count++;
// }
count = 0;
if (_this->output.write(outBuf, outCount) < 0) { break; };
}
delete[] inBuf;
@ -121,6 +130,7 @@ namespace dsp {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
@ -136,9 +146,13 @@ namespace dsp {
}
void setBlockSize(int blockSize) {
printf("%d\n", blockSize);
if (running) {
return;
}
if (blockSize < 1 ) {
return;
}
_blockSize = blockSize;
output.setMaxLatency(blockSize * 2);
}
@ -147,6 +161,9 @@ namespace dsp {
if (running) {
return;
}
if (skip < 0 ) {
skip = 0;
}
_skip = skip;
}
@ -156,9 +173,11 @@ namespace dsp {
static void _worker(BlockDecimator* _this) {
complex_t* buf = new complex_t[_this->_blockSize];
while (true) {
_this->_input->readAndSkip(buf, _this->_blockSize, _this->_skip);
_this->output.write(buf, _this->_blockSize);
int read = _this->_input->readAndSkip(buf, _this->_blockSize, _this->_skip);
if (read < 0) { break; };
if (_this->output.write(buf, _this->_blockSize) < 0) { break; };
}
delete[] buf;
}
stream<complex_t>* _input;
@ -222,6 +241,8 @@ namespace dsp {
_interp = _outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
printf("Resampler.setInputSampleRate(): %d %d\n", _interp, _decim);
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
decim.setTaps(_taps);
@ -250,6 +271,8 @@ namespace dsp {
_interp = outputSampleRate / _gcd;
_decim = _inputSampleRate / _gcd;
printf("Resampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
decim.setTaps(_taps);
@ -285,6 +308,10 @@ namespace dsp {
}
}
int getOutputBlockSize() {
return decim.getOutputBlockSize();
}
stream<complex_t>* output;
private:
@ -357,6 +384,8 @@ namespace dsp {
_interp = _outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
printf("FloatResampler.setInputSampleRate(): %d %d\n", _interp, _decim);
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
decim.setTaps(_taps);
@ -384,6 +413,8 @@ namespace dsp {
_interp = outputSampleRate / _gcd;
_decim = _inputSampleRate / _gcd;
printf("FloatResampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
decim.setTaps(_taps);
@ -419,6 +450,10 @@ namespace dsp {
}
}
int getOutputBlockSize() {
return decim.getOutputBlockSize();
}
stream<float>* output;
private:
@ -436,4 +471,382 @@ namespace dsp {
};
class FIRResampler {
public:
FIRResampler() {
}
void init(stream<complex_t>* in, float inputSampleRate, float outputSampleRate, int blockSize, float passBand = -1.0f, float transWidth = -1.0f) {
_input = in;
_outputSampleRate = outputSampleRate;
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
_blockSize = blockSize;
outputBlockSize = (blockSize * _interp) / _decim;
output.init(outputBlockSize * 2);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
}
void start() {
if (running) {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
if (!running) {
return;
}
_input->stopReader();
output.stopWriter();
_workerThread.join();
_input->clearReadStop();
output.clearWriteStop();
running = false;
}
void setInputSampleRate(float inputSampleRate, int blockSize = -1, float passBand = -1.0f, float transWidth = -1.0f) {
stop();
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)_outputSampleRate);
_interp = _outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
printf("FIRResampler.setInputSampleRate(): %d %d\n", _interp, _decim);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
if (blockSize > 0) {
_blockSize = blockSize;
}
outputBlockSize = (_blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
start();
}
void setOutputSampleRate(float outputSampleRate, float passBand = -1.0f, float transWidth = -1.0f) {
stop();
_outputSampleRate = outputSampleRate;
int _gcd = std::gcd((int)_inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = _inputSampleRate / _gcd;
outputBlockSize = (_blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
printf("FIRResampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
start();
}
void setFilterParams(float passBand, float transWidth) {
stop();
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
start();
}
void setBlockSize(int blockSize) {
stop();
_blockSize = blockSize;
outputBlockSize = (_blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
start();
}
void setInput(stream<complex_t>* input) {
if (running) {
return;
}
_input = input;
}
int getOutputBlockSize() {
return outputBlockSize;
}
stream<complex_t> output;
private:
static void _worker(FIRResampler* _this) {
complex_t* inBuf = new complex_t[_this->_blockSize];
complex_t* outBuf = new complex_t[_this->outputBlockSize];
int outCount = _this->outputBlockSize;
printf("%d %d\n", _this->_blockSize, _this->outputBlockSize);
float* taps = _this->_taps.data();
int tapCount = _this->_taps.size();
complex_t* delayBuf = new complex_t[tapCount];
complex_t* delayStart = &inBuf[_this->_blockSize - tapCount];
int delaySize = tapCount * sizeof(complex_t);
int interp = _this->_interp;
int decim = _this->_decim;
float correction = (float)sqrt((float)interp);
int afterInterp = _this->_blockSize * interp;
int outIndex = 0;
complex_t val;
while (true) {
if (_this->_input->read(inBuf, _this->_blockSize) < 0) { break; };
for (int i = 0; outIndex < outCount; i += decim) {
outBuf[outIndex].q = 0;
outBuf[outIndex].i = 0;
for (int j = 0; j < tapCount; j++) {
if ((i - j) % interp != 0) {
continue;
}
val = GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp);
outBuf[outIndex].i += val.i * taps[j] * correction;
outBuf[outIndex].q += val.q * taps[j] * correction;
}
outIndex++;
}
outIndex = 0;
memcpy(delayBuf, delayStart, delaySize);
if (_this->output.write(outBuf, _this->outputBlockSize) < 0) { break; };
}
printf("DEBUG: %d\n", delaySize);
delete[] inBuf;
delete[] outBuf;
delete[] delayBuf;
}
std::thread _workerThread;
stream<complex_t>* _input;
std::vector<float> _taps;
int _interp;
int _decim;
int outputBlockSize;
float _outputSampleRate;
float _inputSampleRate;
int _blockSize;
bool running = false;
};
class FloatFIRResampler {
public:
FloatFIRResampler() {
}
void init(stream<float>* in, float inputSampleRate, float outputSampleRate, int blockSize, float passBand = -1.0f, float transWidth = -1.0f) {
_input = in;
_outputSampleRate = outputSampleRate;
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
_blockSize = blockSize;
outputBlockSize = (blockSize * _interp) / _decim;
output.init(outputBlockSize * 2);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
}
void start() {
if (running) {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
if (!running) {
return;
}
_input->stopReader();
output.stopWriter();
_workerThread.join();
_input->clearReadStop();
output.clearWriteStop();
running = false;
}
void setInputSampleRate(float inputSampleRate, int blockSize = -1, float passBand = -1.0f, float transWidth = -1.0f) {
stop();
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)_outputSampleRate);
_interp = _outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
printf("FloatFIRResampler.setInputSampleRate(): %d %d\n", _interp, _decim);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
if (blockSize > 0) {
_blockSize = blockSize;
}
outputBlockSize = (blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
start();
}
void setOutputSampleRate(float outputSampleRate, float passBand = -1.0f, float transWidth = -1.0f) {
stop();
_outputSampleRate = outputSampleRate;
int _gcd = std::gcd((int)_inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = _inputSampleRate / _gcd;
outputBlockSize = (_blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
printf("FloatResampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
}
else {
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, cutoff, cutoff);
}
start();
}
void setFilterParams(float passBand, float transWidth) {
stop();
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, passBand, transWidth);
start();
}
void setBlockSize(int blockSize) {
stop();
_blockSize = blockSize;
outputBlockSize = (_blockSize * _interp) / _decim;
output.setMaxLatency(outputBlockSize * 2);
start();
}
void setInput(stream<float>* input) {
if (running) {
return;
}
_input = input;
}
int getOutputBlockSize() {
return outputBlockSize;
}
stream<float> output;
private:
static void _worker(FloatFIRResampler* _this) {
float* inBuf = new float[_this->_blockSize];
float* outBuf = new float[_this->outputBlockSize];
int outCount = _this->outputBlockSize;
float* taps = _this->_taps.data();
int tapCount = _this->_taps.size();
float* delayBuf = new float[tapCount];
float* delayStart = &inBuf[_this->_blockSize - tapCount];
int delaySize = tapCount * sizeof(float);
int interp = _this->_interp;
int decim = _this->_decim;
float correction = (float)sqrt((float)interp);
printf("FloatResamp: %d %d", _this->_blockSize, _this->outputBlockSize);
int afterInterp = _this->_blockSize * interp;
int outIndex = 0;
while (true) {
if (_this->_input->read(inBuf, _this->_blockSize) < 0) { break; };
for (int i = 0; outIndex < outCount; i += decim) {
outBuf[outIndex] = 0;
for (int j = 0; j < tapCount; j++) {
if ((i - j) % interp != 0) {
continue;
}
outBuf[outIndex] += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, (i - j) / interp) * taps[j] * correction;
}
outIndex++;
}
outIndex = 0;
memcpy(delayBuf, delayStart, delaySize);
if (_this->output.write(outBuf, _this->outputBlockSize) < 0) { break; };
}
delete[] inBuf;
delete[] outBuf;
delete[] delayBuf;
}
std::thread _workerThread;
stream<float>* _input;
std::vector<float> _taps;
int _interp;
int _decim;
int outputBlockSize;
float _outputSampleRate;
float _inputSampleRate;
int _blockSize;
bool running = false;
};
};

View File

@ -47,6 +47,15 @@ namespace dsp {
running = false;
}
void setBlockSize(int blockSize) {
if (running) {
return;
}
_bufferSize = blockSize;
output_a.setMaxLatency(blockSize * 2);
output_b.setMaxLatency(blockSize * 2);
}
stream<complex_t> output_a;
stream<complex_t> output_b;

View File

@ -26,7 +26,21 @@ namespace dsp {
}
void start() {
if (running) {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
if (!running) {
return;
}
_in->stopReader();
_workerThread.join();
_in->clearReadStop();
running = false;
}
bool bypass;
@ -34,7 +48,7 @@ namespace dsp {
private:
static void _worker(HandlerSink* _this) {
while (true) {
_this->_in->read(_this->_buffer, _this->_bufferSize);
if (_this->_in->read(_this->_buffer, _this->_bufferSize) < 0) { break; };
_this->_handler(_this->_buffer);
}
}
@ -44,6 +58,7 @@ namespace dsp {
complex_t* _buffer;
std::thread _workerThread;
void (*_handler)(complex_t*);
bool running = false;
};
class NullSink {

View File

@ -14,6 +14,7 @@ namespace dsp {
SineSource(float frequency, long sampleRate, int blockSize) : output(blockSize * 2) {
_blockSize = blockSize;
_sampleRate = sampleRate;
_frequency = frequency;
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
_phase = 0;
}
@ -22,6 +23,7 @@ namespace dsp {
output.init(blockSize * 2);
_sampleRate = sampleRate;
_blockSize = blockSize;
_frequency = frequency;
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
_phase = 0;
}
@ -53,6 +55,12 @@ namespace dsp {
return;
}
_blockSize = blockSize;
output.setMaxLatency(blockSize * 2);
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / _frequency);
}
stream<complex_t> output;
@ -76,6 +84,7 @@ namespace dsp {
float _phasorSpeed;
float _phase;
long _sampleRate;
float _frequency;
std::thread _workerThread;
bool running = false;
};

View File

@ -15,112 +15,54 @@ namespace dsp {
_input = in;
_outputSampleRate = outputSampleRate;
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
_bandWidth = bandWidth;
_blockSize = blockSize;
output = &decim.output;
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, bandWidth / 2.0f, bandWidth / 2.0f);
output = &resamp.output;
lo.init(offset, inputSampleRate, blockSize);
mixer.init(in, &lo.output, blockSize);
interp.init(&mixer.output, _interp, blockSize);
if (_interp == 1) {
decim.init(&mixer.output, _taps, blockSize, _decim);
}
else {
decim.init(&interp.output, _taps, blockSize * _interp, _decim);
}
resamp.init(&mixer.output, inputSampleRate, outputSampleRate, blockSize, _bandWidth * 0.8f, _bandWidth);
}
void start() {
lo.start();
mixer.start();
if (_interp != 1) {
interp.start();
}
decim.start();
resamp.start();
}
void stop() {
void stop(bool resampler = true) {
lo.stop();
mixer.stop();
interp.stop();
decim.stop();
if (resampler) { resamp.stop(); };
}
void setInputSampleRate(float inputSampleRate, int blockSize = -1) {
interp.stop();
decim.stop();
lo.stop();
lo.setSampleRate(inputSampleRate);
_inputSampleRate = inputSampleRate;
int _gcd = std::gcd((int)inputSampleRate, (int)_outputSampleRate);
_interp = _outputSampleRate / _gcd;
_decim = inputSampleRate / _gcd;
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _bandWidth / 2.0f, _bandWidth / 2.0f);
interp.setInterpolation(_interp);
decim.setDecimation(_decim);
if (blockSize > 0) {
lo.stop();
mixer.stop();
_blockSize = blockSize;
mixer.stop();
lo.setBlockSize(_blockSize);
mixer.setBlockSize(_blockSize);
interp.setBlockSize(_blockSize);
lo.start();
mixer.start();
}
decim.setBlockSize(_blockSize * _interp);
if (_interp == 1) {
decim.setInput(&mixer.output);
}
else {
decim.setInput(&interp.output);
interp.start();
}
decim.start();
resamp.setInputSampleRate(inputSampleRate, _blockSize, _bandWidth * 0.8f, _bandWidth);
lo.start();
}
void setOutputSampleRate(float outputSampleRate, float bandWidth = -1) {
interp.stop();
decim.stop();
if (bandWidth > 0) {
_bandWidth = bandWidth;
}
_outputSampleRate = outputSampleRate;
int _gcd = std::gcd((int)_inputSampleRate, (int)outputSampleRate);
_interp = outputSampleRate / _gcd;
_decim = _inputSampleRate / _gcd;
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, _bandWidth / 2.0f, _bandWidth / 2.0f);
decim.setTaps(_taps);
interp.setInterpolation(_interp);
decim.setDecimation(_decim);
decim.setBlockSize(_blockSize * _interp);
if (_interp == 1) {
decim.setInput(&mixer.output);
}
else {
decim.setInput(&interp.output);
interp.start();
}
decim.start();
resamp.setOutputSampleRate(outputSampleRate, _bandWidth * 0.8f, _bandWidth);
}
void setBandwidth(float bandWidth) {
decim.stop();
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, _bandWidth / 2.0f, _bandWidth / 2.0f);
decim.setTaps(_taps);
decim.start();
_bandWidth = bandWidth;
resamp.setFilterParams(_bandWidth * 0.8f, _bandWidth);
}
void setOffset(float offset) {
@ -128,27 +70,26 @@ namespace dsp {
}
void setBlockSize(int blockSize) {
stop();
stop(false);
_blockSize = blockSize;
lo.setBlockSize(_blockSize);
mixer.setBlockSize(_blockSize);
interp.setBlockSize(_blockSize);
decim.setBlockSize(_blockSize * _interp);
resamp.setBlockSize(_blockSize);
start();
}
int getOutputBlockSize() {
return resamp.getOutputBlockSize();
}
stream<complex_t>* output;
private:
SineSource lo;
Multiplier mixer;
Interpolator<complex_t> interp;
DecimatingFIRFilter decim;
FIRResampler resamp;
stream<complex_t>* _input;
std::vector<float> _taps;
int _interp;
int _decim;
float _outputSampleRate;
float _inputSampleRate;
float _bandWidth;

159
src/frequency_select.cpp Normal file
View File

@ -0,0 +1,159 @@
#include <frequency_select.h>
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
}
FrequencySelect::FrequencySelect() {
}
void FrequencySelect::init() {
font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f);
for (int i = 0; i < 12; i++) {
digits[i] = 0;
}
}
void FrequencySelect::onPosChange() {
int digitHeight = ImGui::CalcTextSize("0").y;
int commaOffset = 0;
for (int i = 0; i < 12; i++) {
digitTopMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y);
digitBottomMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y + (digitHeight / 2));
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + (digitHeight / 2));
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + digitHeight);
if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += 12;
}
}
}
void FrequencySelect::onResize() {
}
void FrequencySelect::incrementDigit(int i) {
if (i < 0) {
return;
}
if (digits[i] < 9) {
digits[i]++;
}
else {
digits[i] = 0;
incrementDigit(i - 1);
}
frequencyChanged = true;
}
void FrequencySelect::decrementDigit(int i) {
if (i < 0) {
return;
}
if (digits[i] > 0) {
digits[i]--;
}
else {
digits[i] = 9;
decrementDigit(i - 1);
}
frequencyChanged = true;
}
void FrequencySelect::draw() {
window = ImGui::GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x + 255;
widgetPos.y += window->Pos.y - 3;
widgetEndPos.x += window->Pos.x + 255;
widgetEndPos.y += window->Pos.y - 3;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
ImGui::PushFont(font);
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
onPosChange();
}
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
lastWidgetSize = widgetSize;
onResize();
}
int commaOffset = 0;
bool zeros = true;
for (int i = 0; i < 12; i++) {
if (digits[i] != 0) {
zeros = false;
}
sprintf(buf, "%d", digits[i]);
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), buf);
if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += 12;
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), ".");
}
}
ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = ImGui::GetIO().MouseWheel;
bool onDigit = false;
for (int i = 0; i < 12; i++) {
onDigit = false;
if (isInArea(mousePos, digitTopMins[i], digitTopMaxs[i])) {
window->DrawList->AddRectFilled(digitTopMins[i], digitTopMaxs[i], IM_COL32(255, 0, 0, 75));
if (leftClick) {
incrementDigit(i);
}
onDigit = true;
}
if (isInArea(mousePos, digitBottomMins[i], digitBottomMaxs[i])) {
window->DrawList->AddRectFilled(digitBottomMins[i], digitBottomMaxs[i], IM_COL32(0, 0, 255, 75));
if (leftClick) {
decrementDigit(i);
}
onDigit = true;
}
if (onDigit) {
if (rightClick) {
for (int j = i; j < 12; j++) {
digits[j] = 0;
}
frequencyChanged = true;
}
if (mw != 0) {
int count = abs(mw);
for (int j = 0; j < count; j++) {
mw > 0 ? incrementDigit(i) : decrementDigit(i);
}
}
}
}
long freq = 0;
for (int i = 0; i < 12; i++) {
freq += digits[i] * pow(10, 11 - i);
}
frequency = freq;
ImGui::PopFont();
ImGui::NewLine();
}
void FrequencySelect::setFrequency(long freq) {
int i = 11;
for (long f = freq; i >= 0; i--) {
digits[i] = f % 10;
f -= digits[i];
f /= 10;
}
frequency = freq;
}

38
src/frequency_select.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
class FrequencySelect {
public:
FrequencySelect();
void init();
void draw();
void setFrequency(long freq);
long frequency;
bool frequencyChanged = false;
private:
void onPosChange();
void onResize();
void incrementDigit(int i);
void decrementDigit(int i);
ImVec2 widgetPos;
ImVec2 widgetEndPos;
ImVec2 widgetSize;
ImVec2 lastWidgetPos;
ImVec2 lastWidgetSize;
ImGuiWindow* window;
ImFont* font;
int digits[12];
ImVec2 digitBottomMins[12];
ImVec2 digitTopMins[12];
ImVec2 digitBottomMaxs[12];
ImVec2 digitTopMaxs[12];
char buf[100];
};

30
src/icons.cpp Normal file
View File

@ -0,0 +1,30 @@
#include <icons.h>
#define STB_IMAGE_IMPLEMENTATION
#include <imgui/stb_image.h>
namespace icons {
ImTextureID PLAY;
ImTextureID STOP;
ImTextureID PLAY_RAW;
ImTextureID STOP_RAW;
GLuint loadTexture(char* path) {
int w,h,n;
stbi_uc* data = stbi_load(path, &w, &h, &n, NULL);
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
stbi_image_free(data);
return texId;
}
void load() {
PLAY_RAW = (ImTextureID)loadTexture("res/icons/play_raw.png");
STOP_RAW = (ImTextureID)loadTexture("res/icons/stop_raw.png");
}
}

14
src/icons.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <imgui/imgui.h>
#include <stdint.h>
#include <GL/glew.h>
namespace icons {
extern ImTextureID PLAY;
extern ImTextureID STOP;
extern ImTextureID PLAY_RAW;
extern ImTextureID STOP_RAW;
GLuint loadTexture(char* path);
void load();
}

2
src/imgui/imutils.h Normal file
View File

@ -0,0 +1,2 @@
#pragma once
#define IS_IN_AREA(pos, min, max) (pos.x >= min.x && pos.x < max.x && pos.y >= min.y && pos.y < max.y)

7762
src/imgui/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,7 @@ namespace io {
outputParams.hostApiSpecificStreamInfo = NULL;
outputParams.device = Pa_GetDefaultOutputDevice();
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
PaError err = Pa_OpenStream(&stream, NULL, &outputParams, 40000.0f, 320, paClipOff, _callback, this);
PaError err = Pa_OpenStream(&stream, NULL, &outputParams, 48000.0f, 64, paClipOff, _callback, this);
printf("%s\n", Pa_GetErrorText(err));
err = Pa_StartStream(stream);
printf("%s\n", Pa_GetErrorText(err));
@ -49,6 +49,12 @@ namespace io {
Pa_CloseStream(stream);
}
void setBlockSize(int blockSize) {
stop();
_bufferSize = blockSize;
start();
}
private:
static int _callback(const void *input,
void *output,
@ -58,10 +64,16 @@ namespace io {
AudioSink* _this = (AudioSink*)userData;
float* outbuf = (float*)output;
_this->_input->read(_this->buffer, frameCount);
float vol = powf(_this->_volume, 2);
for (int i = 0; i < frameCount; i++) {
outbuf[(i * 2) + 0] = _this->buffer[i] * vol;
outbuf[(i * 2) + 1] = _this->buffer[i] * vol;
}
return 0;
}

View File

@ -66,6 +66,7 @@ namespace io {
if (running) {
return;
}
_sampleRate = sampleRate;
dev->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
}
@ -86,12 +87,17 @@ namespace io {
private:
static void _worker(SoapyWrapper* _this) {
dsp::complex_t* buf = new dsp::complex_t[32000];
int blockSize = _this->_sampleRate / 200.0f;
dsp::complex_t* buf = new dsp::complex_t[blockSize];
int flags = 0;
long long timeMs = 0;
while (_this->running) {
_this->dev->readStream(_this->_stream, (void**)&buf, 32000, flags, timeMs);
_this->output.write(buf, 32000);
int res = _this->dev->readStream(_this->_stream, (void**)&buf, blockSize, flags, timeMs);
if (res < 1) {
continue;
}
_this->output.write(buf, res);
}
printf("Read worker terminated\n");
delete[] buf;
@ -102,5 +108,6 @@ namespace io {
SoapySDR::Stream* _stream;
std::thread _workerThread;
bool running = false;
float _sampleRate = 0;
};
};

View File

@ -6,6 +6,7 @@
#include <GLFW/glfw3.h>
#include <main_window.h>
#include <styles.h>
#include <icons.h>
#ifdef _WIN32
@ -58,6 +59,9 @@ int main() {
windowInit();
printf("Loading icons...\n");
icons::load();
// Main loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();

View File

@ -8,13 +8,16 @@
#include <dsp/source.h>
#include <dsp/math.h>
#include <waterfall.h>
#include <frequency_select.h>
#include <fftw3.h>
#include <signal_path.h>
#include <io/soapy.h>
#include <icons.h>
std::thread worker;
std::mutex fft_mtx;
ImGui::WaterFall wtf;
FrequencySelect fSel;
fftwf_complex *fft_in, *fft_out;
fftwf_plan p;
float* tempData;
@ -50,9 +53,12 @@ void fftHandler(dsp::complex_t* samples) {
void windowInit() {
int sampleRate = 8000000;
wtf.setBandwidth(sampleRate);
//wtf.range = 500000;
wtf.setCenterFrequency(90500000);
printf("fft taps: %d\n", fftTaps.size());
wtf.setVFOBandwidth(200000);
wtf.setVFOOffset(0);
fSel.init();
fSel.setFrequency(90500000);
fft_in = (fftwf_complex*) fftw_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*) fftw_malloc(sizeof(fftwf_complex) * fftSize);
@ -72,8 +78,8 @@ int _srId = -1;
bool showExample = false;
int freq = 90500;
int _freq = 90500;
long freq = 90500000;
long _freq = 90500000;
int demod = 0;
@ -90,19 +96,127 @@ float fftMin = -70.0f;
float fftMax = 0.0f;
float offset = 0.0f;
float lastOffset = -1.0f;
float bw = 8000000.0f;
float lastBW = -1.0f;
int sampleRate = 1000000;
bool playing = false;
void setVFO(float freq) {
float currentOff = wtf.getVFOOfset();
float currentTune = wtf.getCenterFrequency() + currentOff;
float delta = freq - currentTune;
float newVFO = currentOff + delta;
float vfoBW = wtf.getVFOBandwidth();
float vfoBottom = newVFO - (vfoBW / 2.0f);
float vfoTop = newVFO + (vfoBW / 2.0f);
float view = wtf.getViewOffset();
float viewBW = wtf.getViewBandwidth();
float viewBottom = view - (viewBW / 2.0f);
float viewTop = view + (viewBW / 2.0f);
float wholeFreq = wtf.getCenterFrequency();
float BW = wtf.getBandwidth();
float bottom = -(BW / 2.0f);
float top = (BW / 2.0f);
// VFO still fints in the view
if (vfoBottom > viewBottom && vfoTop < viewTop) {
sigPath.setVFOFrequency(newVFO);
wtf.setVFOOffset(newVFO);
return;
}
// VFO too low for current SDR tuning
if (vfoBottom < bottom) {
wtf.setViewOffset((BW / 2.0f) - (viewBW / 2.0f));
float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f);
sigPath.setVFOFrequency(newVFOOffset);
wtf.setVFOOffset(newVFOOffset);
wtf.setCenterFrequency(freq - newVFOOffset);
soapy.setFrequency(freq - newVFOOffset);
return;
}
// VFO too high for current SDR tuning
if (vfoTop > top) {
wtf.setViewOffset((viewBW / 2.0f) - (BW / 2.0f));
float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f);
sigPath.setVFOFrequency(newVFOOffset);
wtf.setVFOOffset(newVFOOffset);
wtf.setCenterFrequency(freq - newVFOOffset);
soapy.setFrequency(freq - newVFOOffset);
return;
}
// VFO is still without the SDR's bandwidth
if (delta < 0) {
float newViewOff = vfoTop - (viewBW / 2.0f) + (viewBW / 10.0f);
float newViewBottom = newViewOff - (viewBW / 2.0f);
float newViewTop = newViewOff + (viewBW / 2.0f);
if (newViewBottom > bottom) {
wtf.setVFOOffset(newVFO);
wtf.setViewOffset(newViewOff);
sigPath.setVFOFrequency(newVFO);
return;
}
wtf.setViewOffset((BW / 2.0f) - (viewBW / 2.0f));
float newVFOOffset = (BW / 2.0f) - (vfoBW / 2.0f) - (viewBW / 10.0f);
sigPath.setVFOFrequency(newVFOOffset);
wtf.setVFOOffset(newVFOOffset);
wtf.setCenterFrequency(freq - newVFOOffset);
soapy.setFrequency(freq - newVFOOffset);
}
else {
float newViewOff = vfoBottom + (viewBW / 2.0f) - (viewBW / 10.0f);
float newViewBottom = newViewOff - (viewBW / 2.0f);
float newViewTop = newViewOff + (viewBW / 2.0f);
if (newViewTop < top) {
wtf.setVFOOffset(newVFO);
wtf.setViewOffset(newViewOff);
sigPath.setVFOFrequency(newVFO);
return;
}
wtf.setViewOffset((viewBW / 2.0f) - (BW / 2.0f));
float newVFOOffset = (vfoBW / 2.0f) - (BW / 2.0f) + (viewBW / 10.0f);
sigPath.setVFOFrequency(newVFOOffset);
wtf.setVFOOffset(newVFOOffset);
wtf.setCenterFrequency(freq - newVFOOffset);
soapy.setFrequency(freq - newVFOOffset);
}
}
void drawWindow() {
if (freq != _freq) {
_freq = freq;
wtf.setCenterFrequency(freq * 1000);
soapy.setFrequency(freq * 1000);
if (fSel.frequencyChanged) {
fSel.frequencyChanged = false;
setVFO(fSel.frequency);
}
if (vfoFreq != lastVfoFreq) {
lastVfoFreq = vfoFreq;
sigPath.setVFOFrequency(vfoFreq - (freq * 1000));
if (wtf.centerFreqMoved) {
wtf.centerFreqMoved = false;
soapy.setFrequency(wtf.getCenterFrequency());
fSel.setFrequency(wtf.getCenterFrequency() + wtf.getVFOOfset());
}
if (wtf.vfoFreqChanged) {
wtf.vfoFreqChanged = false;
sigPath.setVFOFrequency(wtf.getVFOOfset());
fSel.setFrequency(wtf.getCenterFrequency() + wtf.getVFOOfset());
}
// vfoFreq = wtf.getVFOOfset() + freq;
// if (vfoFreq != lastVfoFreq) {
// lastVfoFreq = vfoFreq;
// sigPath.setVFOFrequency(vfoFreq - freq);
// }
if (volume != lastVolume) {
lastVolume = volume;
@ -115,23 +229,30 @@ void drawWindow() {
}
if (srId != _srId) {
soapy.setSampleRate(soapy.sampleRates[srId]);
_srId = srId;
sampleRate = soapy.sampleRates[srId];
printf("Setting sample rate to %f\n", (float)soapy.sampleRates[srId]);
soapy.setSampleRate(sampleRate);
wtf.setBandwidth(sampleRate);
wtf.setViewBandwidth(sampleRate);
sigPath.setSampleRate(sampleRate);
bw = sampleRate;
}
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit"))
{
ImGui::MenuItem("Show Example Window", "", &showExample);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
// if (ImGui::BeginMenuBar())
// {
// if (ImGui::BeginMenu("File"))
// {
// ImGui::EndMenu();
// }
// if (ImGui::BeginMenu("Edit"))
// {
// ImGui::MenuItem("Show Example Window", "", &showExample);
// ImGui::EndMenu();
// }
// ImGui::EndMenuBar();
// }
if (showExample) {
ImGui::ShowDemoWindow();
@ -144,8 +265,37 @@ void drawWindow() {
int width = vMax.x - vMin.x;
int height = vMax.y - vMin.y;
ImGui::Columns(2, "WindowColumns", false);
// To Bar
if (playing) {
if (ImGui::ImageButton(icons::STOP_RAW, ImVec2(30, 30))) {
soapy.stop();
playing = false;
}
}
else {
if (ImGui::ImageButton(icons::PLAY_RAW, ImVec2(30, 30))) {
soapy.start();
soapy.setFrequency(wtf.getCenterFrequency());
playing = true;
}
}
ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
ImGui::SetNextItemWidth(200);
ImGui::SliderFloat("##_2_", &volume, 0.0f, 1.0f, "");
ImGui::SameLine();
fSel.draw();
ImGui::Columns(3, "WindowColumns", false);
ImVec2 winSize = ImGui::GetWindowSize();
ImGui::SetColumnWidth(0, 300);
ImGui::SetColumnWidth(1, winSize.x - 300 - 50);
ImGui::SetColumnWidth(2, 50);
// Left Column
ImGui::BeginChild("Left Column");
@ -155,16 +305,6 @@ void drawWindow() {
ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str());
ImGui::Combo("##_1_", &srId, soapy.txtSampleRateList.c_str());
ImGui::SliderFloat("##_2_", &volume, 0.0f, 1.0f, "");
if (ImGui::Button("Start") && !state) {
state = true;
soapy.start();
}
ImGui::SameLine();
if (ImGui::Button("Stop") && state) {
state = false;
soapy.stop();
}
if (ImGui::Button("Refresh")) {
soapy.refresh();
}
@ -174,20 +314,44 @@ void drawWindow() {
ImGui::BeginGroup();
ImGui::Columns(4, "RadioModeColumns", false);
if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) { demod = 0; };
if (ImGui::RadioButton("WFM", demod == 1) && demod != 1) { sigPath.setDemodulator(SignalPath::DEMOD_FM); demod = 1; };
if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) {
sigPath.setDemodulator(SignalPath::DEMOD_NFM); demod = 0;
wtf.setVFOBandwidth(12500);
wtf.setVFOReference(ImGui::WaterFall::REF_CENTER);
}
if (ImGui::RadioButton("WFM", demod == 1) && demod != 1) {
sigPath.setDemodulator(SignalPath::DEMOD_FM);
demod = 1;
wtf.setVFOBandwidth(200000);
wtf.setVFOReference(ImGui::WaterFall::REF_CENTER);
}
ImGui::NextColumn();
if (ImGui::RadioButton("AM", demod == 2) && demod != 2) { sigPath.setDemodulator(SignalPath::DEMOD_AM); demod = 2; };
if (ImGui::RadioButton("AM", demod == 2) && demod != 2) {
sigPath.setDemodulator(SignalPath::DEMOD_AM);
demod = 2;
wtf.setVFOBandwidth(12500);
wtf.setVFOReference(ImGui::WaterFall::REF_CENTER);
}
if (ImGui::RadioButton("DSB", demod == 3) && demod != 3) { demod = 3; };
ImGui::NextColumn();
if (ImGui::RadioButton("USB", demod == 4) && demod != 4) { demod = 4; };
if (ImGui::RadioButton("USB", demod == 4) && demod != 4) {
sigPath.setDemodulator(SignalPath::DEMOD_USB);
demod = 4;
wtf.setVFOBandwidth(3000);
wtf.setVFOReference(ImGui::WaterFall::REF_LOWER);
}
if (ImGui::RadioButton("CW", demod == 5) && demod != 5) { demod = 5; };
ImGui::NextColumn();
if (ImGui::RadioButton("LSB", demod == 6) && demod != 6) { demod = 6; };
if (ImGui::RadioButton("LSB", demod == 6) && demod != 6) {
sigPath.setDemodulator(SignalPath::DEMOD_LSB);
demod = 6;
wtf.setVFOBandwidth(3000);
wtf.setVFOReference(ImGui::WaterFall::REF_UPPER);
}
if (ImGui::RadioButton("RAW", demod == 7) && demod != 7) { demod = 7; };
ImGui::Columns(1, "EndRadioModeColumns", false);
ImGui::InputInt("Frequency (kHz)", &freq);
//ImGui::InputInt("Frequency (kHz)", &freq);
ImGui::Checkbox("DC Bias Removal", &dcbias);
ImGui::EndGroup();
@ -202,31 +366,8 @@ void drawWindow() {
if(ImGui::CollapsingHeader("Debug")) {
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
ImGui::SliderFloat("##_3_", &fftMax, 0.0f, -100.0f, "");
ImGui::SliderFloat("##_4_", &fftMin, 0.0f, -100.0f, "");
if (ImGui::Button("Auto Range")) {
printf("Auto ranging...\n");
wtf.autoRange();
}
ImGui::SliderFloat("##_5_", &offset, -4000000.0f, 4000000.0f, "");
ImGui::SliderFloat("##_6_", &bw, 1.0f, 8000000.0f, "");
wtf.setViewOffset(offset);
wtf.setViewBandwidth(bw);
wtf.setFFTMin(fftMin);
wtf.setFFTMax(fftMax);
wtf.setWaterfallMin(fftMin);
wtf.setWaterfallMax(fftMax);
}
ImVec2 delta = ImGui::GetMouseDragDelta();
ImGui::ResetMouseDragDelta();
//printf("%f %f\n", delta.x, delta.y);
ImGui::EndChild();
// Right Column
@ -235,4 +376,29 @@ void drawWindow() {
ImGui::BeginChild("Waterfall");
wtf.draw();
ImGui::EndChild();
ImGui::NextColumn();
ImGui::Text("Zoom");
ImGui::NewLine();
ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw, 1000.0f, sampleRate, "");
ImGui::Text("Max");
ImGui::NewLine();
ImGui::VSliderFloat("##_8_", ImVec2(20.0f, 150.0f), &fftMax, -100.0f, 0.0f, "");
ImGui::Text("Min");
ImGui::NewLine();
ImGui::VSliderFloat("##_9_", ImVec2(20.0f, 150.0f), &fftMin, -100.0f, 0.0f, "");
if (bw != lastBW) {
lastBW = bw;
wtf.setViewOffset(wtf.getVFOOfset());
wtf.setViewBandwidth(bw);
}
wtf.setFFTMin(fftMin);
wtf.setFFTMax(fftMax);
wtf.setWaterfallMin(fftMin);
wtf.setWaterfallMax(fftMax);
}

View File

@ -6,7 +6,7 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_MenuBar
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
void windowInit();
void drawWindow();

View File

@ -26,11 +26,47 @@ void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream
demod.init(mainVFO.output, 100000, 200000, 800);
amDemod.init(mainVFO.output, 50);
ssbDemod.init(mainVFO.output, 6000, 3000, 22);
audioResamp.init(&demod.output, 200000, 40000, 20000, 800);
audio.init(audioResamp.output, 160);
audioResamp.init(&demod.output, 200000, 48000, 800);
audio.init(&audioResamp.output, 16);
}
ns.init(mainVFO.output, 800);
dsp::DCBiasRemover dcBiasRemover;
dsp::Splitter split;
dsp::BlockDecimator fftBlockDec;
dsp::HandlerSink fftHandlerSink;
dsp::VFO mainVFO;
dsp::FMDemodulator demod;
dsp::AMDemodulator amDemod;
dsp::FloatResampler audioResamp;
io::AudioSink audio;
void SignalPath::setSampleRate(float sampleRate) {
dcBiasRemover.stop();
split.stop();
fftBlockDec.stop();
fftHandlerSink.stop();
demod.stop();
amDemod.stop();
audioResamp.stop();
int inputBlockSize = sampleRate / 200.0f;
dcBiasRemover.setBlockSize(inputBlockSize);
split.setBlockSize(inputBlockSize);
fftBlockDec.setSkip((sampleRate / fftRate) - fftSize);
mainVFO.setInputSampleRate(sampleRate, inputBlockSize);
// // Reset the modulator and audio systems
setDemodulator(_demod);
fftHandlerSink.start();
fftBlockDec.start();
split.start();
dcBiasRemover.start();
}
void SignalPath::setVFOFrequency(long frequency) {
@ -53,27 +89,71 @@ void SignalPath::setDemodulator(int demId) {
printf("Stopping FM demodulator\n");
demod.stop();
}
else if (_demod == DEMOD_NFM) {
printf("Stopping NFM demodulator\n");
demod.stop();
}
else if (_demod == DEMOD_AM) {
printf("Stopping AM demodulator\n");
amDemod.stop();
}
else if (_demod == DEMOD_USB) {
printf("Stopping USB demodulator\n");
ssbDemod.stop();
}
else if (_demod == DEMOD_LSB) {
printf("Stopping LSB demodulator\n");
ssbDemod.stop();
}
_demod = demId;
// Set input of the audio resampler
if (demId == DEMOD_FM) {
printf("Starting FM demodulator\n");
mainVFO.setOutputSampleRate(200000, 200000);
demod.setBlockSize(mainVFO.getOutputBlockSize());
demod.setSampleRate(200000);
demod.setDeviation(100000);
audioResamp.setInput(&demod.output);
audioResamp.setInputSampleRate(200000, 800);
audioResamp.setInputSampleRate(200000, mainVFO.getOutputBlockSize());
demod.start();
}
if (demId == DEMOD_NFM) {
printf("Starting NFM demodulator\n");
mainVFO.setOutputSampleRate(12500, 12500);
demod.setBlockSize(mainVFO.getOutputBlockSize());
demod.setSampleRate(12500);
demod.setDeviation(6250);
audioResamp.setInput(&demod.output);
audioResamp.setInputSampleRate(12500, mainVFO.getOutputBlockSize());
demod.start();
}
else if (demId == DEMOD_AM) {
printf("Starting AM demodulator\n");
mainVFO.setOutputSampleRate(12500, 12500);
amDemod.setBlockSize(mainVFO.getOutputBlockSize());
audioResamp.setInput(&amDemod.output);
audioResamp.setInputSampleRate(12500, 50);
audioResamp.setInputSampleRate(12500, mainVFO.getOutputBlockSize());
amDemod.start();
}
else if (demId == DEMOD_USB) {
printf("Starting USB demodulator\n");
mainVFO.setOutputSampleRate(6000, 3000);
ssbDemod.setBlockSize(mainVFO.getOutputBlockSize());
ssbDemod.setMode(dsp::SSBDemod::MODE_USB);
audioResamp.setInput(&ssbDemod.output);
audioResamp.setInputSampleRate(6000, mainVFO.getOutputBlockSize());
ssbDemod.start();
}
else if (demId == DEMOD_LSB) {
printf("Starting LSB demodulator\n");
mainVFO.setOutputSampleRate(6000, 3000);
ssbDemod.setBlockSize(mainVFO.getOutputBlockSize());
ssbDemod.setMode(dsp::SSBDemod::MODE_LSB);
audioResamp.setInput(&ssbDemod.output);
audioResamp.setInputSampleRate(6000, mainVFO.getOutputBlockSize());
ssbDemod.start();
}
audioResamp.start();
}

View File

@ -26,7 +26,10 @@ public:
enum {
DEMOD_FM,
DEMOD_NFM,
DEMOD_AM,
DEMOD_USB,
DEMOD_LSB,
_DEMOD_COUNT
};
@ -44,14 +47,12 @@ private:
// Demodulators
dsp::FMDemodulator demod;
dsp::AMDemodulator amDemod;
dsp::SSBDemod ssbDemod;
// Audio output
dsp::FloatResampler audioResamp;
dsp::FloatFIRResampler audioResamp;
io::AudioSink audio;
// DEBUG
dsp::NullSink ns;
float sampleRate;
float fftRate;
int fftSize;

View File

@ -9,7 +9,7 @@ void setImguiStyle(ImGuiIO& io) {
ImGui::GetStyle().PopupRounding = 0.0f;
ImGui::GetStyle().ScrollbarRounding = 0.0f;
io.Fonts->AddFontFromFileTTF("../res/fonts/Roboto-Medium.ttf", 16.0f);
io.Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 16.0f);
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();

View File

@ -19,6 +19,15 @@ float COLOR_MAP[][3] = {
};
void doZoom(int offset, int width, int outWidth, std::vector<float> data, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 65535) {
width = 65535;
}
float factor = (float)width / (float)outWidth;
for (int i = 0; i < outWidth; i++) {
out[i] = data[offset + ((float)i * factor)];
@ -26,6 +35,8 @@ void doZoom(int offset, int width, int outWidth, std::vector<float> data, float*
}
float freq_ranges[] = {
10.0f, 20.0f, 25.0f, 50.0f,
100.0f, 200.0f, 250.0f, 500.0f,
1000.0f, 2000.0f, 2500.0f, 5000.0f,
10000.0f, 20000.0f, 25000.0f, 50000.0f,
100000.0f, 200000.0f, 250000.0f, 500000.0f,
@ -34,11 +45,12 @@ float freq_ranges[] = {
};
float findBestFreqRange(float bandwidth) {
for (int i = 0; i < 15; i++) {
if (bandwidth / freq_ranges[i] < 15.0f) {
for (int i = 0; i < 28; i++) {
if (bandwidth / freq_ranges[i] < 25.0f) {
return freq_ranges[i];
}
}
return 50000000.0f;
}
void printAndScale(float freq, char* buf) {
@ -147,8 +159,8 @@ namespace ImGui {
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
IM_COL32(255, 255, 255, 255), 1.0f);
// Y Axis
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 10),
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
IM_COL32(255, 255, 255, 255), 1.0f);
@ -163,6 +175,98 @@ namespace ImGui {
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
}
void WaterFall::drawVFO() {
float width = (vfoBandwidth / viewBandwidth) * (float)dataWidth;
int center = (((vfoOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f);
int left;
int right;
ImVec2 mousePos = ImGui::GetMousePos();
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
bool freqDrag = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && IS_IN_AREA(mousePos, fftAreaMin, fftAreaMax) && !freqDrag) {
int refCenter = mousePos.x - (widgetPos.x + 50);
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
vfoOffset = ((((float)refCenter / ((float)dataWidth / 2.0f)) - 1.0f) * (viewBandwidth / 2.0f)) + viewOffset;
center = refCenter;
}
vfoFreqChanged = true;
}
// if (vfoRef == REF_CENTER) {
// left = center - (width / 2.0f) + 1;
// right = center + (width / 2.0f) + 1;
// }
// if (vfoRef == REF_LOWER) {
// left = center;
// right = center + width + 1;
// }
// if (vfoRef == REF_UPPER) {
// left = center;
// right = center - width + 1;
// }
if (freqDrag) {
float deltax = drag.x - lastDrag;
lastDrag = drag.x;
float viewDelta = deltax * (viewBandwidth / (float)dataWidth);
viewOffset -= viewDelta;
if (viewOffset + (viewBandwidth / 2.0f) > wholeBandwidth / 2.0f) {
float freqOffset = (viewOffset + (viewBandwidth / 2.0f)) - (wholeBandwidth / 2.0f);
viewOffset = (wholeBandwidth / 2.0f) - (viewBandwidth / 2.0f);
centerFreq += freqOffset;
centerFreqMoved = true;
}
if (viewOffset - (viewBandwidth / 2.0f) < -(wholeBandwidth / 2.0f)) {
float freqOffset = (viewOffset - (viewBandwidth / 2.0f)) + (wholeBandwidth / 2.0f);
viewOffset = (viewBandwidth / 2.0f) - (wholeBandwidth / 2.0f);
centerFreq += freqOffset;
centerFreqMoved = true;
}
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
updateWaterfallFb();
}
else {
lastDrag = 0;
}
left = center - (width / 2.0f) + 1;
right = center + (width / 2.0f) + 1;
if ((left < 0 && right < 0) || (left >= dataWidth && right >= dataWidth)) {
return;
}
left = std::clamp<int>(left, 0, dataWidth - 1);
right = std::clamp<int>(right, 0, dataWidth - 1);
window->DrawList->AddRectFilled(ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10),
ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 10), IM_COL32(255, 255, 255, 50));
if (center >= 0 && center < dataWidth) {
if (vfoRef == REF_CENTER) {
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9),
ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9),
IM_COL32(255, 0, 0, 255), 1.0f);
}
else if (vfoRef == REF_LOWER) {
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9),
ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9),
IM_COL32(255, 0, 0, 255), 1.0f);
}
else if (vfoRef == REF_UPPER) {
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9),
ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9),
IM_COL32(255, 0, 0, 255), 1.0f);
}
}
}
void WaterFall::updateWaterfallFb() {
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
int drawDataSize;
@ -197,7 +301,6 @@ namespace ImGui {
}
void WaterFall::onResize() {
printf("Resized\n");
dataWidth = widgetSize.x - 60.0f;
waterfallHeight = widgetSize.y - fftHeight - 52;
delete[] latestFFT;
@ -207,6 +310,14 @@ namespace ImGui {
for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0f; // Hide everything
}
fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9);
fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10);
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
printf("Resized: %d %d\n", dataWidth, waterfallHeight);
updateWaterfallFb();
}
@ -230,11 +341,14 @@ namespace ImGui {
onResize();
}
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0f);
drawFFT();
drawWaterfall();
drawVFO();
buf_mtx.unlock();
}
@ -243,6 +357,7 @@ namespace ImGui {
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
int drawDataStart = (((float)data.size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, dataWidth, data, latestFFT);
rawFFTs.insert(rawFFTs.begin(), data);
if (rawFFTs.size() > waterfallHeight + 300) {
@ -255,7 +370,7 @@ namespace ImGui {
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
waterfallFb[j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
waterfallFb[j] = waterfallPallet[id];
}
waterfallUpdate = true;
buf_mtx.unlock();
@ -290,6 +405,8 @@ namespace ImGui {
void WaterFall::setCenterFrequency(float freq) {
centerFreq = freq;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
}
float WaterFall::getCenterFrequency() {
@ -322,6 +439,13 @@ namespace ImGui {
return vfoBandwidth;
}
void WaterFall::setVFOReference(int ref) {
if (ref < 0 || ref >= _REF_COUNT) {
return;
}
vfoRef = ref;
}
void WaterFall::setViewBandwidth(float bandWidth) {
if (bandWidth == viewBandwidth) {
return;
@ -341,12 +465,19 @@ namespace ImGui {
updateWaterfallFb();
}
float WaterFall::getViewBandwidth() {
return viewBandwidth;
}
void WaterFall::setViewOffset(float offset) {
if (offset == viewOffset) {
return;
}
if (abs(offset) + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
return;
if (offset - (viewBandwidth / 2.0f) < -(wholeBandwidth / 2.0f)) {
offset = (viewBandwidth / 2.0f) - (wholeBandwidth / 2.0f);
}
if (offset + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
offset = (wholeBandwidth / 2.0f) - (viewBandwidth / 2.0f);
}
viewOffset = offset;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
@ -354,6 +485,10 @@ namespace ImGui {
updateWaterfallFb();
}
float WaterFall::getViewOffset() {
return viewOffset;
}
void WaterFall::setFFTMin(float min) {
fftMin = min;
}

View File

@ -4,6 +4,7 @@
#include <vector>
#include <mutex>
#include <GL/glew.h>
#include <imutils.h>
#define WATERFALL_RESOLUTION 1000000
@ -30,8 +31,13 @@ namespace ImGui {
void setVFOBandwidth(float bandwidth);
float getVFOBandwidth();
void setVFOReference(int ref);
void setViewBandwidth(float bandWidth);
float getViewBandwidth();
void setViewOffset(float offset);
float getViewOffset();
void setFFTMin(float min);
float getFFTMin();
@ -50,10 +56,21 @@ namespace ImGui {
void autoRange();
bool centerFreqMoved = false;
bool vfoFreqChanged = false;
enum {
REF_LOWER,
REF_CENTER,
REF_UPPER,
_REF_COUNT
};
private:
void drawWaterfall();
void drawFFT();
void drawVFO();
void onPositionChange();
void onResize();
void updateWaterfallFb();
@ -70,6 +87,13 @@ namespace ImGui {
ImVec2 lastWidgetPos;
ImVec2 lastWidgetSize;
ImVec2 fftAreaMin;
ImVec2 fftAreaMax;
ImVec2 freqAreaMin;
ImVec2 freqAreaMax;
ImVec2 waterfallAreaMin;
ImVec2 waterfallAreaMax;
ImGuiWindow* window;
GLuint textureId;
@ -87,6 +111,10 @@ namespace ImGui {
float upperFreq;
float range;
float lastDrag;
int vfoRef = REF_CENTER;
// Absolute values
float centerFreq;
float wholeBandwidth;