mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-06 02:37:32 +01:00
New stuff
This commit is contained in:
parent
370324bc68
commit
cbf0b6290d
@ -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
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
BIN
res/icons/stop_raw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -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;
|
||||
};
|
||||
};
|
@ -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;
|
||||
};
|
||||
};
|
@ -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:
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
159
src/frequency_select.cpp
Normal 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
38
src/frequency_select.h
Normal 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
30
src/icons.cpp
Normal 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
14
src/icons.h
Normal 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
2
src/imgui/imutils.h
Normal 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
7762
src/imgui/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
@ -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();
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user