mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-03-26 17:45:28 +01:00
New stuff
This commit is contained in:
parent
370324bc68
commit
cbf0b6290d
@ -44,4 +44,7 @@ target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l)
|
|||||||
|
|
||||||
# PortAudio
|
# PortAudio
|
||||||
find_package(portaudio CONFIG REQUIRED)
|
find_package(portaudio CONFIG REQUIRED)
|
||||||
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
|
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() {
|
void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_workerThread = std::thread(_worker, this);
|
_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;
|
stream<complex_t> output;
|
||||||
@ -37,9 +61,9 @@ namespace dsp {
|
|||||||
float ibias = 0.0f;
|
float ibias = 0.0f;
|
||||||
float qbias = 0.0f;
|
float qbias = 0.0f;
|
||||||
while (true) {
|
while (true) {
|
||||||
_this->_in->read(buf, _this->_bufferSize);
|
if (_this->_in->read(buf, _this->_bufferSize) < 0) { break; };
|
||||||
if (_this->bypass) {
|
if (_this->bypass) {
|
||||||
_this->output.write(buf, _this->_bufferSize);
|
if (_this->output.write(buf, _this->_bufferSize) < 0) { break; };
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _this->_bufferSize; i++) {
|
for (int i = 0; i < _this->_bufferSize; i++) {
|
||||||
@ -52,12 +76,14 @@ namespace dsp {
|
|||||||
buf[i].i -= ibias;
|
buf[i].i -= ibias;
|
||||||
buf[i].q -= qbias;
|
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;
|
stream<complex_t>* _in;
|
||||||
int _bufferSize;
|
int _bufferSize;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
|
bool running = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -2,6 +2,8 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <dsp/stream.h>
|
#include <dsp/stream.h>
|
||||||
#include <dsp/types.h>
|
#include <dsp/types.h>
|
||||||
|
#include <dsp/source.h>
|
||||||
|
#include <dsp/math.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO:
|
TODO:
|
||||||
@ -42,6 +44,8 @@ namespace dsp {
|
|||||||
_input = in;
|
_input = in;
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
_phase = 0.0f;
|
_phase = 0.0f;
|
||||||
|
_deviation = deviation;
|
||||||
|
_sampleRate = sampleRate;
|
||||||
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / deviation);
|
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / deviation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +86,16 @@ namespace dsp {
|
|||||||
output.setMaxLatency(_blockSize * 2);
|
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;
|
stream<float> output;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -109,6 +123,8 @@ namespace dsp {
|
|||||||
int _blockSize;
|
int _blockSize;
|
||||||
float _phase;
|
float _phase;
|
||||||
float _phasorSpeed;
|
float _phasorSpeed;
|
||||||
|
float _deviation;
|
||||||
|
float _sampleRate;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -195,4 +211,104 @@ namespace dsp {
|
|||||||
int _blockSize;
|
int _blockSize;
|
||||||
std::thread _workerThread;
|
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 {
|
namespace dsp {
|
||||||
inline void BlackmanWindow(std::vector<float>& taps, float sampleRate, float cutoff, float transWidth) {
|
inline void BlackmanWindow(std::vector<float>& taps, float sampleRate, float cutoff, float transWidth) {
|
||||||
taps.clear();
|
taps.clear();
|
||||||
|
|
||||||
float fc = cutoff / sampleRate;
|
float fc = cutoff / sampleRate;
|
||||||
|
if (fc > 1.0f) {
|
||||||
|
fc = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
int _M = 4.0f / (transWidth / sampleRate);
|
int _M = 4.0f / (transWidth / sampleRate);
|
||||||
|
if (_M < 4) {
|
||||||
|
_M = 4;
|
||||||
|
}
|
||||||
|
|
||||||
if (_M % 2 == 0) { _M++; }
|
if (_M % 2 == 0) { _M++; }
|
||||||
float M = _M;
|
float M = _M;
|
||||||
float sum = 0.0f;
|
float sum = 0.0f;
|
||||||
@ -131,7 +140,11 @@ namespace dsp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
output.setMaxLatency((_blockSize * 2) / _decim);
|
output.setMaxLatency(getOutputBlockSize() * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return _blockSize / _decim;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<complex_t> output;
|
stream<complex_t> output;
|
||||||
@ -302,6 +315,10 @@ namespace dsp {
|
|||||||
output.setMaxLatency((_blockSize * 2) / _decim);
|
output.setMaxLatency((_blockSize * 2) / _decim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return _blockSize / _decim;
|
||||||
|
}
|
||||||
|
|
||||||
stream<float> output;
|
stream<float> output;
|
||||||
|
|
||||||
private:
|
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* inBuf = new T[_this->_blockSize];
|
||||||
T* outBuf = new T[_this->_blockSize * _this->_interpolation];
|
T* outBuf = new T[_this->_blockSize * _this->_interpolation];
|
||||||
int outCount = _this->_blockSize * _this->_interpolation;
|
int outCount = _this->_blockSize * _this->_interpolation;
|
||||||
|
int interp = _this->_interpolation;
|
||||||
|
int count = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (_this->_input->read(inBuf, _this->_blockSize) < 0) { break; };
|
if (_this->_input->read(inBuf, _this->_blockSize) < 0) { break; };
|
||||||
for (int i = 0; i < outCount; i++) {
|
for (int i = 0; i < outCount; i++) {
|
||||||
outBuf[i] = inBuf[(int)((float)i / _this->_interpolation)];
|
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; };
|
if (_this->output.write(outBuf, outCount) < 0) { break; };
|
||||||
}
|
}
|
||||||
delete[] inBuf;
|
delete[] inBuf;
|
||||||
@ -121,6 +130,7 @@ namespace dsp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_workerThread = std::thread(_worker, this);
|
_workerThread = std::thread(_worker, this);
|
||||||
|
running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
@ -136,9 +146,13 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setBlockSize(int blockSize) {
|
void setBlockSize(int blockSize) {
|
||||||
|
printf("%d\n", blockSize);
|
||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (blockSize < 1 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
output.setMaxLatency(blockSize * 2);
|
output.setMaxLatency(blockSize * 2);
|
||||||
}
|
}
|
||||||
@ -147,6 +161,9 @@ namespace dsp {
|
|||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (skip < 0 ) {
|
||||||
|
skip = 0;
|
||||||
|
}
|
||||||
_skip = skip;
|
_skip = skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +173,11 @@ namespace dsp {
|
|||||||
static void _worker(BlockDecimator* _this) {
|
static void _worker(BlockDecimator* _this) {
|
||||||
complex_t* buf = new complex_t[_this->_blockSize];
|
complex_t* buf = new complex_t[_this->_blockSize];
|
||||||
while (true) {
|
while (true) {
|
||||||
_this->_input->readAndSkip(buf, _this->_blockSize, _this->_skip);
|
int read = _this->_input->readAndSkip(buf, _this->_blockSize, _this->_skip);
|
||||||
_this->output.write(buf, _this->_blockSize);
|
if (read < 0) { break; };
|
||||||
|
if (_this->output.write(buf, _this->_blockSize) < 0) { break; };
|
||||||
}
|
}
|
||||||
|
delete[] buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<complex_t>* _input;
|
stream<complex_t>* _input;
|
||||||
@ -222,6 +241,8 @@ namespace dsp {
|
|||||||
_interp = _outputSampleRate / _gcd;
|
_interp = _outputSampleRate / _gcd;
|
||||||
_decim = inputSampleRate / _gcd;
|
_decim = inputSampleRate / _gcd;
|
||||||
|
|
||||||
|
printf("Resampler.setInputSampleRate(): %d %d\n", _interp, _decim);
|
||||||
|
|
||||||
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
|
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
|
||||||
decim.setTaps(_taps);
|
decim.setTaps(_taps);
|
||||||
|
|
||||||
@ -250,6 +271,8 @@ namespace dsp {
|
|||||||
_interp = outputSampleRate / _gcd;
|
_interp = outputSampleRate / _gcd;
|
||||||
_decim = _inputSampleRate / _gcd;
|
_decim = _inputSampleRate / _gcd;
|
||||||
|
|
||||||
|
printf("Resampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
|
||||||
|
|
||||||
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
|
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
|
||||||
decim.setTaps(_taps);
|
decim.setTaps(_taps);
|
||||||
|
|
||||||
@ -285,6 +308,10 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return decim.getOutputBlockSize();
|
||||||
|
}
|
||||||
|
|
||||||
stream<complex_t>* output;
|
stream<complex_t>* output;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -357,6 +384,8 @@ namespace dsp {
|
|||||||
_interp = _outputSampleRate / _gcd;
|
_interp = _outputSampleRate / _gcd;
|
||||||
_decim = inputSampleRate / _gcd;
|
_decim = inputSampleRate / _gcd;
|
||||||
|
|
||||||
|
printf("FloatResampler.setInputSampleRate(): %d %d\n", _interp, _decim);
|
||||||
|
|
||||||
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
|
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, _outputSampleRate / 2.0f, _outputSampleRate / 2.0f);
|
||||||
decim.setTaps(_taps);
|
decim.setTaps(_taps);
|
||||||
|
|
||||||
@ -384,6 +413,8 @@ namespace dsp {
|
|||||||
_interp = outputSampleRate / _gcd;
|
_interp = outputSampleRate / _gcd;
|
||||||
_decim = _inputSampleRate / _gcd;
|
_decim = _inputSampleRate / _gcd;
|
||||||
|
|
||||||
|
printf("FloatResampler.setOutputSampleRate(): %d %d\n", _interp, _decim);
|
||||||
|
|
||||||
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
|
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, outputSampleRate / 2.0f, outputSampleRate / 2.0f);
|
||||||
decim.setTaps(_taps);
|
decim.setTaps(_taps);
|
||||||
|
|
||||||
@ -419,6 +450,10 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return decim.getOutputBlockSize();
|
||||||
|
}
|
||||||
|
|
||||||
stream<float>* output;
|
stream<float>* output;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -434,6 +469,384 @@ namespace dsp {
|
|||||||
float _blockSize;
|
float _blockSize;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
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_a;
|
||||||
stream<complex_t> output_b;
|
stream<complex_t> output_b;
|
||||||
|
|
||||||
|
@ -26,7 +26,21 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
|
if (running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_workerThread = std::thread(_worker, this);
|
_workerThread = std::thread(_worker, this);
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_in->stopReader();
|
||||||
|
_workerThread.join();
|
||||||
|
_in->clearReadStop();
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bypass;
|
bool bypass;
|
||||||
@ -34,7 +48,7 @@ namespace dsp {
|
|||||||
private:
|
private:
|
||||||
static void _worker(HandlerSink* _this) {
|
static void _worker(HandlerSink* _this) {
|
||||||
while (true) {
|
while (true) {
|
||||||
_this->_in->read(_this->_buffer, _this->_bufferSize);
|
if (_this->_in->read(_this->_buffer, _this->_bufferSize) < 0) { break; };
|
||||||
_this->_handler(_this->_buffer);
|
_this->_handler(_this->_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,6 +58,7 @@ namespace dsp {
|
|||||||
complex_t* _buffer;
|
complex_t* _buffer;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
void (*_handler)(complex_t*);
|
void (*_handler)(complex_t*);
|
||||||
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NullSink {
|
class NullSink {
|
||||||
|
@ -14,6 +14,7 @@ namespace dsp {
|
|||||||
SineSource(float frequency, long sampleRate, int blockSize) : output(blockSize * 2) {
|
SineSource(float frequency, long sampleRate, int blockSize) : output(blockSize * 2) {
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
|
_frequency = frequency;
|
||||||
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
|
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
|
||||||
_phase = 0;
|
_phase = 0;
|
||||||
}
|
}
|
||||||
@ -22,6 +23,7 @@ namespace dsp {
|
|||||||
output.init(blockSize * 2);
|
output.init(blockSize * 2);
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
|
_frequency = frequency;
|
||||||
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
|
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / frequency);
|
||||||
_phase = 0;
|
_phase = 0;
|
||||||
}
|
}
|
||||||
@ -53,6 +55,12 @@ namespace dsp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
|
output.setMaxLatency(blockSize * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSampleRate(float sampleRate) {
|
||||||
|
_sampleRate = sampleRate;
|
||||||
|
_phasorSpeed = (2 * 3.1415926535) / (sampleRate / _frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream<complex_t> output;
|
stream<complex_t> output;
|
||||||
@ -76,6 +84,7 @@ namespace dsp {
|
|||||||
float _phasorSpeed;
|
float _phasorSpeed;
|
||||||
float _phase;
|
float _phase;
|
||||||
long _sampleRate;
|
long _sampleRate;
|
||||||
|
float _frequency;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
};
|
};
|
||||||
|
@ -15,112 +15,54 @@ namespace dsp {
|
|||||||
_input = in;
|
_input = in;
|
||||||
_outputSampleRate = outputSampleRate;
|
_outputSampleRate = outputSampleRate;
|
||||||
_inputSampleRate = inputSampleRate;
|
_inputSampleRate = inputSampleRate;
|
||||||
int _gcd = std::gcd((int)inputSampleRate, (int)outputSampleRate);
|
|
||||||
_interp = outputSampleRate / _gcd;
|
|
||||||
_decim = inputSampleRate / _gcd;
|
|
||||||
_bandWidth = bandWidth;
|
_bandWidth = bandWidth;
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
output = &decim.output;
|
output = &resamp.output;
|
||||||
|
|
||||||
dsp::BlackmanWindow(_taps, inputSampleRate * _interp, bandWidth / 2.0f, bandWidth / 2.0f);
|
|
||||||
|
|
||||||
lo.init(offset, inputSampleRate, blockSize);
|
lo.init(offset, inputSampleRate, blockSize);
|
||||||
mixer.init(in, &lo.output, blockSize);
|
mixer.init(in, &lo.output, blockSize);
|
||||||
interp.init(&mixer.output, _interp, blockSize);
|
resamp.init(&mixer.output, inputSampleRate, outputSampleRate, blockSize, _bandWidth * 0.8f, _bandWidth);
|
||||||
if (_interp == 1) {
|
|
||||||
decim.init(&mixer.output, _taps, blockSize, _decim);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
decim.init(&interp.output, _taps, blockSize * _interp, _decim);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
lo.start();
|
lo.start();
|
||||||
mixer.start();
|
mixer.start();
|
||||||
if (_interp != 1) {
|
resamp.start();
|
||||||
interp.start();
|
|
||||||
}
|
|
||||||
decim.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop(bool resampler = true) {
|
||||||
lo.stop();
|
lo.stop();
|
||||||
mixer.stop();
|
mixer.stop();
|
||||||
interp.stop();
|
if (resampler) { resamp.stop(); };
|
||||||
decim.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInputSampleRate(float inputSampleRate, int blockSize = -1) {
|
void setInputSampleRate(float inputSampleRate, int blockSize = -1) {
|
||||||
interp.stop();
|
lo.stop();
|
||||||
decim.stop();
|
lo.setSampleRate(inputSampleRate);
|
||||||
|
|
||||||
_inputSampleRate = 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) {
|
if (blockSize > 0) {
|
||||||
lo.stop();
|
|
||||||
mixer.stop();
|
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
|
mixer.stop();
|
||||||
lo.setBlockSize(_blockSize);
|
lo.setBlockSize(_blockSize);
|
||||||
mixer.setBlockSize(_blockSize);
|
mixer.setBlockSize(_blockSize);
|
||||||
interp.setBlockSize(_blockSize);
|
|
||||||
lo.start();
|
|
||||||
mixer.start();
|
mixer.start();
|
||||||
}
|
}
|
||||||
decim.setBlockSize(_blockSize * _interp);
|
resamp.setInputSampleRate(inputSampleRate, _blockSize, _bandWidth * 0.8f, _bandWidth);
|
||||||
|
lo.start();
|
||||||
if (_interp == 1) {
|
|
||||||
decim.setInput(&mixer.output);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
decim.setInput(&interp.output);
|
|
||||||
interp.start();
|
|
||||||
}
|
|
||||||
decim.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOutputSampleRate(float outputSampleRate, float bandWidth = -1) {
|
void setOutputSampleRate(float outputSampleRate, float bandWidth = -1) {
|
||||||
interp.stop();
|
|
||||||
decim.stop();
|
|
||||||
|
|
||||||
if (bandWidth > 0) {
|
if (bandWidth > 0) {
|
||||||
_bandWidth = bandWidth;
|
_bandWidth = bandWidth;
|
||||||
}
|
}
|
||||||
|
resamp.setOutputSampleRate(outputSampleRate, _bandWidth * 0.8f, _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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBandwidth(float bandWidth) {
|
void setBandwidth(float bandWidth) {
|
||||||
decim.stop();
|
_bandWidth = bandWidth;
|
||||||
dsp::BlackmanWindow(_taps, _inputSampleRate * _interp, _bandWidth / 2.0f, _bandWidth / 2.0f);
|
resamp.setFilterParams(_bandWidth * 0.8f, _bandWidth);
|
||||||
decim.setTaps(_taps);
|
|
||||||
decim.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOffset(float offset) {
|
void setOffset(float offset) {
|
||||||
@ -128,27 +70,26 @@ namespace dsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setBlockSize(int blockSize) {
|
void setBlockSize(int blockSize) {
|
||||||
stop();
|
stop(false);
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
lo.setBlockSize(_blockSize);
|
lo.setBlockSize(_blockSize);
|
||||||
mixer.setBlockSize(_blockSize);
|
mixer.setBlockSize(_blockSize);
|
||||||
interp.setBlockSize(_blockSize);
|
resamp.setBlockSize(_blockSize);
|
||||||
decim.setBlockSize(_blockSize * _interp);
|
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getOutputBlockSize() {
|
||||||
|
return resamp.getOutputBlockSize();
|
||||||
|
}
|
||||||
|
|
||||||
stream<complex_t>* output;
|
stream<complex_t>* output;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SineSource lo;
|
SineSource lo;
|
||||||
Multiplier mixer;
|
Multiplier mixer;
|
||||||
Interpolator<complex_t> interp;
|
FIRResampler resamp;
|
||||||
DecimatingFIRFilter decim;
|
|
||||||
stream<complex_t>* _input;
|
stream<complex_t>* _input;
|
||||||
|
|
||||||
std::vector<float> _taps;
|
|
||||||
int _interp;
|
|
||||||
int _decim;
|
|
||||||
float _outputSampleRate;
|
float _outputSampleRate;
|
||||||
float _inputSampleRate;
|
float _inputSampleRate;
|
||||||
float _bandWidth;
|
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.hostApiSpecificStreamInfo = NULL;
|
||||||
outputParams.device = Pa_GetDefaultOutputDevice();
|
outputParams.device = Pa_GetDefaultOutputDevice();
|
||||||
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
|
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));
|
printf("%s\n", Pa_GetErrorText(err));
|
||||||
err = Pa_StartStream(stream);
|
err = Pa_StartStream(stream);
|
||||||
printf("%s\n", Pa_GetErrorText(err));
|
printf("%s\n", Pa_GetErrorText(err));
|
||||||
@ -49,6 +49,12 @@ namespace io {
|
|||||||
Pa_CloseStream(stream);
|
Pa_CloseStream(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setBlockSize(int blockSize) {
|
||||||
|
stop();
|
||||||
|
_bufferSize = blockSize;
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int _callback(const void *input,
|
static int _callback(const void *input,
|
||||||
void *output,
|
void *output,
|
||||||
@ -58,10 +64,16 @@ namespace io {
|
|||||||
AudioSink* _this = (AudioSink*)userData;
|
AudioSink* _this = (AudioSink*)userData;
|
||||||
float* outbuf = (float*)output;
|
float* outbuf = (float*)output;
|
||||||
_this->_input->read(_this->buffer, frameCount);
|
_this->_input->read(_this->buffer, frameCount);
|
||||||
|
|
||||||
float vol = powf(_this->_volume, 2);
|
float vol = powf(_this->_volume, 2);
|
||||||
for (int i = 0; i < frameCount; i++) {
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
|
||||||
|
|
||||||
outbuf[(i * 2) + 0] = _this->buffer[i] * vol;
|
outbuf[(i * 2) + 0] = _this->buffer[i] * vol;
|
||||||
outbuf[(i * 2) + 1] = _this->buffer[i] * vol;
|
outbuf[(i * 2) + 1] = _this->buffer[i] * vol;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ namespace io {
|
|||||||
if (running) {
|
if (running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_sampleRate = sampleRate;
|
||||||
dev->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
|
dev->setSampleRate(SOAPY_SDR_RX, 0, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,12 +87,17 @@ namespace io {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static void _worker(SoapyWrapper* _this) {
|
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;
|
int flags = 0;
|
||||||
long long timeMs = 0;
|
long long timeMs = 0;
|
||||||
|
|
||||||
while (_this->running) {
|
while (_this->running) {
|
||||||
_this->dev->readStream(_this->_stream, (void**)&buf, 32000, flags, timeMs);
|
int res = _this->dev->readStream(_this->_stream, (void**)&buf, blockSize, flags, timeMs);
|
||||||
_this->output.write(buf, 32000);
|
if (res < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_this->output.write(buf, res);
|
||||||
}
|
}
|
||||||
printf("Read worker terminated\n");
|
printf("Read worker terminated\n");
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
@ -102,5 +108,6 @@ namespace io {
|
|||||||
SoapySDR::Stream* _stream;
|
SoapySDR::Stream* _stream;
|
||||||
std::thread _workerThread;
|
std::thread _workerThread;
|
||||||
bool running = false;
|
bool running = false;
|
||||||
|
float _sampleRate = 0;
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -6,6 +6,7 @@
|
|||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <main_window.h>
|
#include <main_window.h>
|
||||||
#include <styles.h>
|
#include <styles.h>
|
||||||
|
#include <icons.h>
|
||||||
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -58,6 +59,9 @@ int main() {
|
|||||||
|
|
||||||
windowInit();
|
windowInit();
|
||||||
|
|
||||||
|
printf("Loading icons...\n");
|
||||||
|
icons::load();
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
@ -8,13 +8,16 @@
|
|||||||
#include <dsp/source.h>
|
#include <dsp/source.h>
|
||||||
#include <dsp/math.h>
|
#include <dsp/math.h>
|
||||||
#include <waterfall.h>
|
#include <waterfall.h>
|
||||||
|
#include <frequency_select.h>
|
||||||
#include <fftw3.h>
|
#include <fftw3.h>
|
||||||
#include <signal_path.h>
|
#include <signal_path.h>
|
||||||
#include <io/soapy.h>
|
#include <io/soapy.h>
|
||||||
|
#include <icons.h>
|
||||||
|
|
||||||
std::thread worker;
|
std::thread worker;
|
||||||
std::mutex fft_mtx;
|
std::mutex fft_mtx;
|
||||||
ImGui::WaterFall wtf;
|
ImGui::WaterFall wtf;
|
||||||
|
FrequencySelect fSel;
|
||||||
fftwf_complex *fft_in, *fft_out;
|
fftwf_complex *fft_in, *fft_out;
|
||||||
fftwf_plan p;
|
fftwf_plan p;
|
||||||
float* tempData;
|
float* tempData;
|
||||||
@ -50,9 +53,12 @@ void fftHandler(dsp::complex_t* samples) {
|
|||||||
void windowInit() {
|
void windowInit() {
|
||||||
int sampleRate = 8000000;
|
int sampleRate = 8000000;
|
||||||
wtf.setBandwidth(sampleRate);
|
wtf.setBandwidth(sampleRate);
|
||||||
//wtf.range = 500000;
|
|
||||||
wtf.setCenterFrequency(90500000);
|
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_in = (fftwf_complex*) fftw_malloc(sizeof(fftwf_complex) * fftSize);
|
||||||
fft_out = (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;
|
bool showExample = false;
|
||||||
|
|
||||||
int freq = 90500;
|
long freq = 90500000;
|
||||||
int _freq = 90500;
|
long _freq = 90500000;
|
||||||
|
|
||||||
int demod = 0;
|
int demod = 0;
|
||||||
|
|
||||||
@ -90,19 +96,127 @@ float fftMin = -70.0f;
|
|||||||
float fftMax = 0.0f;
|
float fftMax = 0.0f;
|
||||||
|
|
||||||
float offset = 0.0f;
|
float offset = 0.0f;
|
||||||
|
float lastOffset = -1.0f;
|
||||||
float bw = 8000000.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() {
|
void drawWindow() {
|
||||||
if (freq != _freq) {
|
if (fSel.frequencyChanged) {
|
||||||
_freq = freq;
|
fSel.frequencyChanged = false;
|
||||||
wtf.setCenterFrequency(freq * 1000);
|
setVFO(fSel.frequency);
|
||||||
soapy.setFrequency(freq * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vfoFreq != lastVfoFreq) {
|
if (wtf.centerFreqMoved) {
|
||||||
lastVfoFreq = vfoFreq;
|
wtf.centerFreqMoved = false;
|
||||||
sigPath.setVFOFrequency(vfoFreq - (freq * 1000));
|
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) {
|
if (volume != lastVolume) {
|
||||||
lastVolume = volume;
|
lastVolume = volume;
|
||||||
@ -115,23 +229,30 @@ void drawWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (srId != _srId) {
|
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::BeginMenuBar())
|
||||||
{
|
// {
|
||||||
if (ImGui::BeginMenu("File"))
|
// if (ImGui::BeginMenu("File"))
|
||||||
{
|
// {
|
||||||
ImGui::EndMenu();
|
// ImGui::EndMenu();
|
||||||
}
|
// }
|
||||||
if (ImGui::BeginMenu("Edit"))
|
// if (ImGui::BeginMenu("Edit"))
|
||||||
{
|
// {
|
||||||
ImGui::MenuItem("Show Example Window", "", &showExample);
|
// ImGui::MenuItem("Show Example Window", "", &showExample);
|
||||||
ImGui::EndMenu();
|
// ImGui::EndMenu();
|
||||||
}
|
// }
|
||||||
ImGui::EndMenuBar();
|
// ImGui::EndMenuBar();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (showExample) {
|
if (showExample) {
|
||||||
ImGui::ShowDemoWindow();
|
ImGui::ShowDemoWindow();
|
||||||
@ -144,8 +265,37 @@ void drawWindow() {
|
|||||||
int width = vMax.x - vMin.x;
|
int width = vMax.x - vMin.x;
|
||||||
int height = vMax.y - vMin.y;
|
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(0, 300);
|
||||||
|
ImGui::SetColumnWidth(1, winSize.x - 300 - 50);
|
||||||
|
ImGui::SetColumnWidth(2, 50);
|
||||||
|
|
||||||
// Left Column
|
// Left Column
|
||||||
ImGui::BeginChild("Left Column");
|
ImGui::BeginChild("Left Column");
|
||||||
@ -155,16 +305,6 @@ void drawWindow() {
|
|||||||
ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str());
|
ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str());
|
||||||
ImGui::Combo("##_1_", &srId, soapy.txtSampleRateList.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")) {
|
if (ImGui::Button("Refresh")) {
|
||||||
soapy.refresh();
|
soapy.refresh();
|
||||||
}
|
}
|
||||||
@ -174,20 +314,44 @@ void drawWindow() {
|
|||||||
ImGui::BeginGroup();
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
ImGui::Columns(4, "RadioModeColumns", false);
|
ImGui::Columns(4, "RadioModeColumns", false);
|
||||||
if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) { demod = 0; };
|
if (ImGui::RadioButton("NFM", demod == 0) && demod != 0) {
|
||||||
if (ImGui::RadioButton("WFM", demod == 1) && demod != 1) { sigPath.setDemodulator(SignalPath::DEMOD_FM); demod = 1; };
|
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();
|
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; };
|
if (ImGui::RadioButton("DSB", demod == 3) && demod != 3) { demod = 3; };
|
||||||
ImGui::NextColumn();
|
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; };
|
if (ImGui::RadioButton("CW", demod == 5) && demod != 5) { demod = 5; };
|
||||||
ImGui::NextColumn();
|
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; };
|
if (ImGui::RadioButton("RAW", demod == 7) && demod != 7) { demod = 7; };
|
||||||
ImGui::Columns(1, "EndRadioModeColumns", false);
|
ImGui::Columns(1, "EndRadioModeColumns", false);
|
||||||
|
|
||||||
ImGui::InputInt("Frequency (kHz)", &freq);
|
//ImGui::InputInt("Frequency (kHz)", &freq);
|
||||||
ImGui::Checkbox("DC Bias Removal", &dcbias);
|
ImGui::Checkbox("DC Bias Removal", &dcbias);
|
||||||
|
|
||||||
ImGui::EndGroup();
|
ImGui::EndGroup();
|
||||||
@ -202,31 +366,8 @@ void drawWindow() {
|
|||||||
if(ImGui::CollapsingHeader("Debug")) {
|
if(ImGui::CollapsingHeader("Debug")) {
|
||||||
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
|
ImGui::Text("Frame time: %.3f ms/frame", 1000.0f / ImGui::GetIO().Framerate);
|
||||||
ImGui::Text("Framerate: %.1f FPS", 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();
|
ImGui::EndChild();
|
||||||
|
|
||||||
// Right Column
|
// Right Column
|
||||||
@ -235,4 +376,29 @@ void drawWindow() {
|
|||||||
ImGui::BeginChild("Waterfall");
|
ImGui::BeginChild("Waterfall");
|
||||||
wtf.draw();
|
wtf.draw();
|
||||||
ImGui::EndChild();
|
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 <GL/glew.h>
|
||||||
#include <GLFW/glfw3.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 windowInit();
|
||||||
void drawWindow();
|
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);
|
demod.init(mainVFO.output, 100000, 200000, 800);
|
||||||
amDemod.init(mainVFO.output, 50);
|
amDemod.init(mainVFO.output, 50);
|
||||||
|
ssbDemod.init(mainVFO.output, 6000, 3000, 22);
|
||||||
|
|
||||||
audioResamp.init(&demod.output, 200000, 40000, 20000, 800);
|
audioResamp.init(&demod.output, 200000, 48000, 800);
|
||||||
audio.init(audioResamp.output, 160);
|
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) {
|
void SignalPath::setVFOFrequency(long frequency) {
|
||||||
@ -53,27 +89,71 @@ void SignalPath::setDemodulator(int demId) {
|
|||||||
printf("Stopping FM demodulator\n");
|
printf("Stopping FM demodulator\n");
|
||||||
demod.stop();
|
demod.stop();
|
||||||
}
|
}
|
||||||
|
else if (_demod == DEMOD_NFM) {
|
||||||
|
printf("Stopping NFM demodulator\n");
|
||||||
|
demod.stop();
|
||||||
|
}
|
||||||
else if (_demod == DEMOD_AM) {
|
else if (_demod == DEMOD_AM) {
|
||||||
printf("Stopping AM demodulator\n");
|
printf("Stopping AM demodulator\n");
|
||||||
amDemod.stop();
|
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;
|
_demod = demId;
|
||||||
|
|
||||||
// Set input of the audio resampler
|
// Set input of the audio resampler
|
||||||
if (demId == DEMOD_FM) {
|
if (demId == DEMOD_FM) {
|
||||||
printf("Starting FM demodulator\n");
|
printf("Starting FM demodulator\n");
|
||||||
mainVFO.setOutputSampleRate(200000, 200000);
|
mainVFO.setOutputSampleRate(200000, 200000);
|
||||||
|
demod.setBlockSize(mainVFO.getOutputBlockSize());
|
||||||
|
demod.setSampleRate(200000);
|
||||||
|
demod.setDeviation(100000);
|
||||||
audioResamp.setInput(&demod.output);
|
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();
|
demod.start();
|
||||||
}
|
}
|
||||||
else if (demId == DEMOD_AM) {
|
else if (demId == DEMOD_AM) {
|
||||||
printf("Starting AM demodulator\n");
|
printf("Starting AM demodulator\n");
|
||||||
mainVFO.setOutputSampleRate(12500, 12500);
|
mainVFO.setOutputSampleRate(12500, 12500);
|
||||||
|
amDemod.setBlockSize(mainVFO.getOutputBlockSize());
|
||||||
audioResamp.setInput(&amDemod.output);
|
audioResamp.setInput(&amDemod.output);
|
||||||
audioResamp.setInputSampleRate(12500, 50);
|
audioResamp.setInputSampleRate(12500, mainVFO.getOutputBlockSize());
|
||||||
amDemod.start();
|
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();
|
audioResamp.start();
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,10 @@ public:
|
|||||||
|
|
||||||
enum {
|
enum {
|
||||||
DEMOD_FM,
|
DEMOD_FM,
|
||||||
|
DEMOD_NFM,
|
||||||
DEMOD_AM,
|
DEMOD_AM,
|
||||||
|
DEMOD_USB,
|
||||||
|
DEMOD_LSB,
|
||||||
_DEMOD_COUNT
|
_DEMOD_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,14 +47,12 @@ private:
|
|||||||
// Demodulators
|
// Demodulators
|
||||||
dsp::FMDemodulator demod;
|
dsp::FMDemodulator demod;
|
||||||
dsp::AMDemodulator amDemod;
|
dsp::AMDemodulator amDemod;
|
||||||
|
dsp::SSBDemod ssbDemod;
|
||||||
|
|
||||||
// Audio output
|
// Audio output
|
||||||
dsp::FloatResampler audioResamp;
|
dsp::FloatFIRResampler audioResamp;
|
||||||
io::AudioSink audio;
|
io::AudioSink audio;
|
||||||
|
|
||||||
// DEBUG
|
|
||||||
dsp::NullSink ns;
|
|
||||||
|
|
||||||
float sampleRate;
|
float sampleRate;
|
||||||
float fftRate;
|
float fftRate;
|
||||||
int fftSize;
|
int fftSize;
|
||||||
|
@ -9,7 +9,7 @@ void setImguiStyle(ImGuiIO& io) {
|
|||||||
ImGui::GetStyle().PopupRounding = 0.0f;
|
ImGui::GetStyle().PopupRounding = 0.0f;
|
||||||
ImGui::GetStyle().ScrollbarRounding = 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::StyleColorsDark();
|
||||||
//ImGui::StyleColorsLight();
|
//ImGui::StyleColorsLight();
|
||||||
|
@ -19,6 +19,15 @@ float COLOR_MAP[][3] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void doZoom(int offset, int width, int outWidth, std::vector<float> data, float* out) {
|
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;
|
float factor = (float)width / (float)outWidth;
|
||||||
for (int i = 0; i < outWidth; i++) {
|
for (int i = 0; i < outWidth; i++) {
|
||||||
out[i] = data[offset + ((float)i * factor)];
|
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[] = {
|
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,
|
1000.0f, 2000.0f, 2500.0f, 5000.0f,
|
||||||
10000.0f, 20000.0f, 25000.0f, 50000.0f,
|
10000.0f, 20000.0f, 25000.0f, 50000.0f,
|
||||||
100000.0f, 200000.0f, 250000.0f, 500000.0f,
|
100000.0f, 200000.0f, 250000.0f, 500000.0f,
|
||||||
@ -34,11 +45,12 @@ float freq_ranges[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
float findBestFreqRange(float bandwidth) {
|
float findBestFreqRange(float bandwidth) {
|
||||||
for (int i = 0; i < 15; i++) {
|
for (int i = 0; i < 28; i++) {
|
||||||
if (bandwidth / freq_ranges[i] < 15.0f) {
|
if (bandwidth / freq_ranges[i] < 25.0f) {
|
||||||
return freq_ranges[i];
|
return freq_ranges[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 50000000.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void printAndScale(float freq, char* buf) {
|
void printAndScale(float freq, char* buf) {
|
||||||
@ -147,8 +159,8 @@ namespace ImGui {
|
|||||||
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
|
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
|
||||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||||
// Y Axis
|
// Y Axis
|
||||||
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 10),
|
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
|
||||||
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
|
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
|
||||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||||
|
|
||||||
|
|
||||||
@ -163,6 +175,98 @@ namespace ImGui {
|
|||||||
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
|
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() {
|
void WaterFall::updateWaterfallFb() {
|
||||||
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
||||||
int drawDataSize;
|
int drawDataSize;
|
||||||
@ -197,7 +301,6 @@ namespace ImGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaterFall::onResize() {
|
void WaterFall::onResize() {
|
||||||
printf("Resized\n");
|
|
||||||
dataWidth = widgetSize.x - 60.0f;
|
dataWidth = widgetSize.x - 60.0f;
|
||||||
waterfallHeight = widgetSize.y - fftHeight - 52;
|
waterfallHeight = widgetSize.y - fftHeight - 52;
|
||||||
delete[] latestFFT;
|
delete[] latestFFT;
|
||||||
@ -207,6 +310,14 @@ namespace ImGui {
|
|||||||
for (int i = 0; i < dataWidth; i++) {
|
for (int i = 0; i < dataWidth; i++) {
|
||||||
latestFFT[i] = -1000.0f; // Hide everything
|
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();
|
updateWaterfallFb();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,11 +341,14 @@ namespace ImGui {
|
|||||||
onResize();
|
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->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);
|
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();
|
drawFFT();
|
||||||
drawWaterfall();
|
drawWaterfall();
|
||||||
|
drawVFO();
|
||||||
|
|
||||||
buf_mtx.unlock();
|
buf_mtx.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +357,7 @@ namespace ImGui {
|
|||||||
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
|
||||||
int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
|
int drawDataSize = (viewBandwidth / wholeBandwidth) * data.size();
|
||||||
int drawDataStart = (((float)data.size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
int drawDataStart = (((float)data.size() / 2.0f) * (offsetRatio + 1)) - (drawDataSize / 2);
|
||||||
|
|
||||||
doZoom(drawDataStart, drawDataSize, dataWidth, data, latestFFT);
|
doZoom(drawDataStart, drawDataSize, dataWidth, data, latestFFT);
|
||||||
rawFFTs.insert(rawFFTs.begin(), data);
|
rawFFTs.insert(rawFFTs.begin(), data);
|
||||||
if (rawFFTs.size() > waterfallHeight + 300) {
|
if (rawFFTs.size() > waterfallHeight + 300) {
|
||||||
@ -255,7 +370,7 @@ namespace ImGui {
|
|||||||
for (int j = 0; j < dataWidth; j++) {
|
for (int j = 0; j < dataWidth; j++) {
|
||||||
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
|
||||||
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
|
||||||
waterfallFb[j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
|
waterfallFb[j] = waterfallPallet[id];
|
||||||
}
|
}
|
||||||
waterfallUpdate = true;
|
waterfallUpdate = true;
|
||||||
buf_mtx.unlock();
|
buf_mtx.unlock();
|
||||||
@ -290,6 +405,8 @@ namespace ImGui {
|
|||||||
|
|
||||||
void WaterFall::setCenterFrequency(float freq) {
|
void WaterFall::setCenterFrequency(float freq) {
|
||||||
centerFreq = freq;
|
centerFreq = freq;
|
||||||
|
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||||
|
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float WaterFall::getCenterFrequency() {
|
float WaterFall::getCenterFrequency() {
|
||||||
@ -322,6 +439,13 @@ namespace ImGui {
|
|||||||
return vfoBandwidth;
|
return vfoBandwidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaterFall::setVFOReference(int ref) {
|
||||||
|
if (ref < 0 || ref >= _REF_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vfoRef = ref;
|
||||||
|
}
|
||||||
|
|
||||||
void WaterFall::setViewBandwidth(float bandWidth) {
|
void WaterFall::setViewBandwidth(float bandWidth) {
|
||||||
if (bandWidth == viewBandwidth) {
|
if (bandWidth == viewBandwidth) {
|
||||||
return;
|
return;
|
||||||
@ -341,18 +465,29 @@ namespace ImGui {
|
|||||||
updateWaterfallFb();
|
updateWaterfallFb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float WaterFall::getViewBandwidth() {
|
||||||
|
return viewBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
void WaterFall::setViewOffset(float offset) {
|
void WaterFall::setViewOffset(float offset) {
|
||||||
if (offset == viewOffset) {
|
if (offset == viewOffset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (abs(offset) + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
|
if (offset - (viewBandwidth / 2.0f) < -(wholeBandwidth / 2.0f)) {
|
||||||
return;
|
offset = (viewBandwidth / 2.0f) - (wholeBandwidth / 2.0f);
|
||||||
|
}
|
||||||
|
if (offset + (viewBandwidth / 2.0f) > (wholeBandwidth / 2.0f)) {
|
||||||
|
offset = (wholeBandwidth / 2.0f) - (viewBandwidth / 2.0f);
|
||||||
}
|
}
|
||||||
viewOffset = offset;
|
viewOffset = offset;
|
||||||
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f);
|
||||||
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f);
|
||||||
updateWaterfallFb();
|
updateWaterfallFb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float WaterFall::getViewOffset() {
|
||||||
|
return viewOffset;
|
||||||
|
}
|
||||||
|
|
||||||
void WaterFall::setFFTMin(float min) {
|
void WaterFall::setFFTMin(float min) {
|
||||||
fftMin = min;
|
fftMin = min;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
#include <imutils.h>
|
||||||
|
|
||||||
#define WATERFALL_RESOLUTION 1000000
|
#define WATERFALL_RESOLUTION 1000000
|
||||||
|
|
||||||
@ -30,8 +31,13 @@ namespace ImGui {
|
|||||||
void setVFOBandwidth(float bandwidth);
|
void setVFOBandwidth(float bandwidth);
|
||||||
float getVFOBandwidth();
|
float getVFOBandwidth();
|
||||||
|
|
||||||
|
void setVFOReference(int ref);
|
||||||
|
|
||||||
void setViewBandwidth(float bandWidth);
|
void setViewBandwidth(float bandWidth);
|
||||||
|
float getViewBandwidth();
|
||||||
|
|
||||||
void setViewOffset(float offset);
|
void setViewOffset(float offset);
|
||||||
|
float getViewOffset();
|
||||||
|
|
||||||
void setFFTMin(float min);
|
void setFFTMin(float min);
|
||||||
float getFFTMin();
|
float getFFTMin();
|
||||||
@ -50,10 +56,21 @@ namespace ImGui {
|
|||||||
|
|
||||||
void autoRange();
|
void autoRange();
|
||||||
|
|
||||||
|
bool centerFreqMoved = false;
|
||||||
|
bool vfoFreqChanged = false;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
REF_LOWER,
|
||||||
|
REF_CENTER,
|
||||||
|
REF_UPPER,
|
||||||
|
_REF_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawWaterfall();
|
void drawWaterfall();
|
||||||
void drawFFT();
|
void drawFFT();
|
||||||
|
void drawVFO();
|
||||||
void onPositionChange();
|
void onPositionChange();
|
||||||
void onResize();
|
void onResize();
|
||||||
void updateWaterfallFb();
|
void updateWaterfallFb();
|
||||||
@ -70,6 +87,13 @@ namespace ImGui {
|
|||||||
ImVec2 lastWidgetPos;
|
ImVec2 lastWidgetPos;
|
||||||
ImVec2 lastWidgetSize;
|
ImVec2 lastWidgetSize;
|
||||||
|
|
||||||
|
ImVec2 fftAreaMin;
|
||||||
|
ImVec2 fftAreaMax;
|
||||||
|
ImVec2 freqAreaMin;
|
||||||
|
ImVec2 freqAreaMax;
|
||||||
|
ImVec2 waterfallAreaMin;
|
||||||
|
ImVec2 waterfallAreaMax;
|
||||||
|
|
||||||
ImGuiWindow* window;
|
ImGuiWindow* window;
|
||||||
|
|
||||||
GLuint textureId;
|
GLuint textureId;
|
||||||
@ -87,6 +111,10 @@ namespace ImGui {
|
|||||||
float upperFreq;
|
float upperFreq;
|
||||||
float range;
|
float range;
|
||||||
|
|
||||||
|
float lastDrag;
|
||||||
|
|
||||||
|
int vfoRef = REF_CENTER;
|
||||||
|
|
||||||
// Absolute values
|
// Absolute values
|
||||||
float centerFreq;
|
float centerFreq;
|
||||||
float wholeBandwidth;
|
float wholeBandwidth;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user