New stuff lmao

This commit is contained in:
Ryzerth 2020-08-20 18:29:23 +02:00
parent 649be86cb6
commit 709627a738
15 changed files with 750 additions and 108 deletions

View File

@ -6,6 +6,7 @@ if (MSVC)
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive -fsanitize=address -g")
# set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
@ -37,6 +38,7 @@ if (MSVC)
endif (MSVC)
add_executable(sdrpp ${SRC} ${IMGUI})
# add_library(sdrpp ${SRC} ${IMGUI})
if (MSVC)
# Glew

View File

@ -11,8 +11,6 @@ struct RadioContext_t {
std::string name;
int demod = 1;
SigPath sigPath;
// watcher<float> volume;
// watcher<int> audioDevice;
};
MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name) {
@ -21,24 +19,12 @@ MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name
ctx->name = _name;
ctx->sigPath.init(_name, 200000, 1000, API->registerVFO(_name, mod::API_t::REF_CENTER, 0, 200000, 200000, 1000));
ctx->sigPath.start();
// ctx->volume.val = 1.0f;
// ctx->volume.markAsChanged();
// API->bindVolumeVariable(&ctx->volume.val);
// ctx->audioDevice.val = ctx->sigPath.audio.getDeviceId();
// ctx->audioDevice.changed(); // clear change
ImGui::SetCurrentContext(imctx);
return ctx;
}
MOD_EXPORT void _NEW_FRAME_(RadioContext_t* ctx) {
// if (ctx->volume.changed()) {
// ctx->sigPath.setVolume(ctx->volume.val);
// }
// if (ctx->audioDevice.changed()) {
// ctx->sigPath.audio.stop();
// ctx->sigPath.audio.setDevice(ctx->audioDevice.val);
// ctx->sigPath.audio.start();
// }
}
MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
@ -48,28 +34,28 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
if (ImGui::RadioButton(CONCAT("NFM##_", ctx->name), ctx->demod == 0) && ctx->demod != 0) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_NFM);
ctx->demod = 0;
API->setVFOBandwidth(ctx->name, 12500);
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
}
if (ImGui::RadioButton(CONCAT("WFM##_", ctx->name), ctx->demod == 1) && ctx->demod != 1) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_FM);
ctx->demod = 1;
API->setVFOBandwidth(ctx->name, 200000);
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("AM##_", ctx->name), ctx->demod == 2) && ctx->demod != 2) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_AM);
ctx->demod = 2;
API->setVFOBandwidth(ctx->name, 12500);
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
}
if (ImGui::RadioButton(CONCAT("DSB##_", ctx->name), ctx->demod == 3) && ctx->demod != 3) { ctx->demod = 3; };
if (ImGui::RadioButton(CONCAT("DSB##_", ctx->name), ctx->demod == 3) && ctx->demod != 3) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_DSB);
ctx->demod = 3;
API->setVFOReference(ctx->name, mod::API_t::REF_CENTER);
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("USB##_", ctx->name), ctx->demod == 4) && ctx->demod != 4) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_USB);
ctx->demod = 4;
API->setVFOBandwidth(ctx->name, 3000);
API->setVFOReference(ctx->name, mod::API_t::REF_LOWER);
}
if (ImGui::RadioButton(CONCAT("CW##_", ctx->name), ctx->demod == 5) && ctx->demod != 5) { ctx->demod = 5; };
@ -77,7 +63,6 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
if (ImGui::RadioButton(CONCAT("LSB##_", ctx->name), ctx->demod == 6) && ctx->demod != 6) {
ctx->sigPath.setDemodulator(SigPath::DEMOD_LSB);
ctx->demod = 6;
API->setVFOBandwidth(ctx->name, 3000);
API->setVFOReference(ctx->name, mod::API_t::REF_UPPER);
}
if (ImGui::RadioButton(CONCAT("RAW##_", ctx->name), ctx->demod == 7) && ctx->demod != 7) { ctx->demod = 7; };
@ -85,21 +70,11 @@ MOD_EXPORT void _DRAW_MENU_(RadioContext_t* ctx) {
ImGui::EndGroup();
// ImGui::PushItemWidth(ImGui::GetWindowSize().x);
// ImGui::Combo(CONCAT("##_audio_dev_", ctx->name), &ctx->audioDevice.val, ctx->sigPath.audio.devTxtList.c_str());
// ImGui::PopItemWidth();
ImGui::Checkbox(CONCAT("Deemphasis##_", ctx->name), &ctx->sigPath.deemp.bypass);
}
MOD_EXPORT void _HANDLE_EVENT_(RadioContext_t* ctx, int eventId) {
// INSTEAD OF EVENTS, REGISTER HANDLER WHEN CREATING VFO
if (eventId == mod::EVENT_STREAM_PARAM_CHANGED) {
ctx->sigPath.updateBlockSize();
}
else if (eventId == mod::EVENT_SELECTED_VFO_CHANGED) {
// if (API->getSelectedVFOName() == ctx->name) {
// API->bindVolumeVariable(&ctx->volume.val);
// }
}
}
MOD_EXPORT void _STOP_(RadioContext_t* ctx) {

View File

@ -6,9 +6,16 @@ SigPath::SigPath() {
int SigPath::sampleRateChangeHandler(void* ctx, float sampleRate) {
SigPath* _this = (SigPath*)ctx;
_this->outputSampleRate = sampleRate;
_this->audioResamp.stop();
_this->audioResamp.setOutputSampleRate(sampleRate, sampleRate / 2.0f, sampleRate / 2.0f);
_this->deemp.stop();
float bw = std::min<float>(_this->bandwidth, sampleRate / 2.0f);
spdlog::warn("New bandwidth: {0}", bw);
_this->audioResamp.setOutputSampleRate(sampleRate, bw, bw);
_this->deemp.setBlockSize(_this->audioResamp.getOutputBlockSize());
_this->deemp.setSamplerate(sampleRate);
_this->audioResamp.start();
_this->deemp.start();
return _this->audioResamp.getOutputBlockSize();
}
@ -18,6 +25,7 @@ void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp:
this->vfoName = vfoName;
_demod = DEMOD_FM;
bandwidth = 200000;
// TODO: Set default VFO options
@ -26,8 +34,11 @@ void SigPath::init(std::string vfoName, uint64_t sampleRate, int blockSize, dsp:
ssbDemod.init(input, 6000, 3000, 22);
audioResamp.init(&demod.output, 200000, 48000, 800);
API->registerMonoStream(&audioResamp.output, vfoName, vfoName, sampleRateChangeHandler, this);
deemp.init(&audioResamp.output, 800, 50e-6, 48000);
outputSampleRate = API->registerMonoStream(&deemp.output, vfoName, vfoName, sampleRateChangeHandler, this);
API->setBlockSize(vfoName, audioResamp.getOutputBlockSize());
setDemodulator(_demod);
}
void SigPath::setSampleRate(float sampleRate) {
@ -43,6 +54,7 @@ void SigPath::setDemodulator(int demId) {
}
audioResamp.stop();
deemp.stop();
// Stop current demodulator
if (_demod == DEMOD_FM) {
@ -65,47 +77,76 @@ void SigPath::setDemodulator(int demId) {
// Set input of the audio resampler
if (demId == DEMOD_FM) {
API->setVFOSampleRate(vfoName, 200000, 200000);
bandwidth = 15000;
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
demod.setSampleRate(200000);
demod.setDeviation(100000);
audioResamp.setInput(&demod.output);
audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName), 15000, 15000);
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(200000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = false;
demod.start();
}
if (demId == DEMOD_NFM) {
API->setVFOSampleRate(vfoName, 12500, 12500);
API->setVFOSampleRate(vfoName, 16000, 16000);
bandwidth = 8000;
demod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
demod.setSampleRate(12500);
demod.setDeviation(6250);
demod.setSampleRate(16000);
demod.setDeviation(8000);
audioResamp.setInput(&demod.output);
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName));
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(16000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = true;
demod.start();
}
else if (demId == DEMOD_AM) {
API->setVFOSampleRate(vfoName, 12500, 12500);
bandwidth = 6250;
amDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
audioResamp.setInput(&amDemod.output);
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName));
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(12500, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = true;
amDemod.start();
}
else if (demId == DEMOD_USB) {
API->setVFOSampleRate(vfoName, 6000, 3000);
bandwidth = 3000;
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
ssbDemod.setMode(dsp::SSBDemod::MODE_USB);
audioResamp.setInput(&ssbDemod.output);
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName));
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = true;
ssbDemod.start();
}
else if (demId == DEMOD_LSB) {
API->setVFOSampleRate(vfoName, 6000, 3000);
bandwidth = 3000;
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
ssbDemod.setMode(dsp::SSBDemod::MODE_LSB);
audioResamp.setInput(&ssbDemod.output);
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName));
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = true;
ssbDemod.start();
}
else if (demId == DEMOD_DSB) {
API->setVFOSampleRate(vfoName, 6000, 6000);
bandwidth = 3000;
ssbDemod.setBlockSize(API->getVFOOutputBlockSize(vfoName));
ssbDemod.setMode(dsp::SSBDemod::MODE_DSB);
audioResamp.setInput(&ssbDemod.output);
float audioBw = std::min<float>(bandwidth, outputSampleRate / 2.0f);
audioResamp.setInputSampleRate(6000, API->getVFOOutputBlockSize(vfoName), audioBw, audioBw);
deemp.bypass = true;
ssbDemod.start();
}
deemp.setBlockSize(audioResamp.getOutputBlockSize());
audioResamp.start();
deemp.start();
}
void SigPath::updateBlockSize() {
@ -115,5 +156,6 @@ void SigPath::updateBlockSize() {
void SigPath::start() {
demod.start();
audioResamp.start();
deemp.start();
API->startStream(vfoName);
}

View File

@ -18,7 +18,7 @@ public:
void start();
void setSampleRate(float sampleRate);
void setVFOFrequency(long frequency);
void setVFOFrequency(uint64_t frequency);
void updateBlockSize();
@ -30,9 +30,12 @@ public:
DEMOD_AM,
DEMOD_USB,
DEMOD_LSB,
DEMOD_DSB,
_DEMOD_COUNT
};
dsp::FMDeemphasis deemp;
private:
static int sampleRateChangeHandler(void* ctx, float sampleRate);
@ -49,6 +52,8 @@ private:
std::string vfoName;
float sampleRate;
float bandwidth;
float outputSampleRate;
int blockSize;
int _demod;
};

View File

@ -21,6 +21,8 @@ struct RecorderContext_t {
std::string lastNameList;
std::string selectedStreamName;
int selectedStreamId;
uint64_t samplesWritten;
float sampleRate;
};
void _writeWorker(RecorderContext_t* ctx) {
@ -34,6 +36,7 @@ void _writeWorker(RecorderContext_t* ctx) {
sampleBuf[(i * 2) + 0] = floatBuf[i].l * 0x7FFF;
sampleBuf[(i * 2) + 1] = floatBuf[i].r * 0x7FFF;
}
ctx->samplesWritten += 1024;
ctx->writer->writeSamples(sampleBuf, 2048 * sizeof(int16_t));
}
delete[] floatBuf;
@ -60,6 +63,9 @@ MOD_EXPORT void* _INIT_(mod::API_t* _API, ImGuiContext* imctx, std::string _name
API = _API;
RecorderContext_t* ctx = new RecorderContext_t;
ctx->recording = false;
ctx->selectedStreamName = "";
ctx->selectedStreamId = 0;
ctx->lastNameList = "";
ImGui::SetCurrentContext(imctx);
return ctx;
}
@ -78,14 +84,30 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
nameList += '\0';
}
if (nameList == "") {
ImGui::Text("No audio stream available");
return;
}
if (ctx->lastNameList != nameList) {
ctx->lastNameList = nameList;
auto _nameIt = std::find(streamNames.begin(), streamNames.end(), ctx->selectedStreamName);
if (_nameIt == streamNames.end()) {
// TODO: verify if there even is a stream
ctx->selectedStreamId = 0;
ctx->selectedStreamName = streamNames[ctx->selectedStreamId];
}
else {
ctx->selectedStreamId = std::distance(streamNames.begin(), _nameIt);
ctx->selectedStreamName = streamNames[ctx->selectedStreamId];
}
}
ImGui::PushItemWidth(menuColumnWidth);
if (!ctx->recording) {
ImGui::Combo(CONCAT("##_strea_select_", ctx->name), &ctx->selectedStreamId, nameList.c_str());
if (ImGui::Combo(CONCAT("##_strea_select_", ctx->name), &ctx->selectedStreamId, nameList.c_str())) {
ctx->selectedStreamName = nameList[ctx->selectedStreamId];
}
}
else {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
@ -99,8 +121,10 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
if (!ctx->recording) {
if (ImGui::Button("Record", ImVec2(menuColumnWidth, 0))) {
ctx->samplesWritten = 0;
ctx->sampleRate = 48000;
ctx->writer = new WavWriter("recordings/" + genFileName("audio_"), 16, 2, 48000);
ctx->stream = API->bindToStreamStereo("Radio", streamRemovedHandler, sampleRateChanged, ctx);
ctx->stream = API->bindToStreamStereo(ctx->selectedStreamName, streamRemovedHandler, sampleRateChanged, ctx);
ctx->workerThread = std::thread(_writeWorker, ctx);
ctx->recording = true;
ctx->startTime = time(0);
@ -112,12 +136,13 @@ MOD_EXPORT void _DRAW_MENU_(RecorderContext_t* ctx) {
ctx->stream->stopReader();
ctx->workerThread.join();
ctx->stream->clearReadStop();
API->unbindFromStreamStereo("Radio", ctx->stream);
API->unbindFromStreamStereo(ctx->selectedStreamName, ctx->stream);
ctx->writer->close();
delete ctx->writer;
ctx->recording = false;
}
time_t diff = time(0) - ctx->startTime;
uint64_t seconds = ctx->samplesWritten / (uint64_t)ctx->sampleRate;
time_t diff = seconds;
tm *dtm = gmtime(&diff);
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
}

View File

@ -260,6 +260,9 @@ namespace dsp {
else if (mode == MODE_LSB) {
lo.setFrequency(-_bandWidth / 2.0f);
}
else if (mode == MODE_LSB) {
lo.setFrequency(0);
}
}
stream<float> output;
@ -267,6 +270,7 @@ namespace dsp {
enum {
MODE_USB,
MODE_LSB,
MODE_DSB,
_MODE_COUNT
};
@ -309,4 +313,102 @@ namespace dsp {
int _mode;
bool running = false;
};
// class CWDemod {
// public:
// CWDemod() {
// }
// 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;
// private:
// static void _worker(CWDemod* _this) {
// complex_t* inBuf = new complex_t[_this->_blockSize];
// float* outBuf = new float[_this->_blockSize];
// float min, max, factor;
// while (true) {
// if (_this->mixer.output.read(inBuf, _this->_blockSize) < 0) { break; };
// min = INFINITY;
// max = -INFINITY;
// for (int i = 0; i < _this->_blockSize; i++) {
// outBuf[i] = inBuf[i].q;
// if (inBuf[i].q < min) {
// min = inBuf[i].q;
// }
// if (inBuf[i].q > max) {
// max = inBuf[i].q;
// }
// }
// factor = (max - min) / 2;
// for (int i = 0; i < _this->_blockSize; i++) {
// outBuf[i] /= factor;
// }
// if (_this->output.write(outBuf, _this->_blockSize) < 0) { break; };
// }
// delete[] inBuf;
// delete[] outBuf;
// }
// std::thread _workerThread;
// SineSource lo;
// Multiplier mixer;
// int _blockSize;
// float _bandWidth;
// int _mode;
// bool running = false;
// };
};

View File

@ -4,11 +4,12 @@
#include <dsp/types.h>
#include <vector>
#include <dsp/math.h>
#include <spdlog/spdlog.h>
#define GET_FROM_RIGHT_BUF(buffer, delayLine, delayLineSz, n) (((n) < 0) ? delayLine[(delayLineSz) + (n)] : buffer[(n)])
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, int addedTaps = 0) {
taps.clear();
float fc = cutoff / sampleRate;
@ -16,7 +17,7 @@ namespace dsp {
fc = 1.0f;
}
int _M = 4.0f / (transWidth / sampleRate);
int _M = (4.0f / (transWidth / sampleRate)) + (float)addedTaps;
if (_M < 4) {
_M = 4;
}
@ -377,4 +378,113 @@ namespace dsp {
float* _taps;
bool running;
};
class FMDeemphasis {
public:
FMDeemphasis() {
}
FMDeemphasis(stream<float>* input, int bufferSize, float tau, float sampleRate) : output(bufferSize * 2) {
_in = input;
_bufferSize = bufferSize;
bypass = false;
_tau = tau;
_sampleRate = sampleRate;
}
void init(stream<float>* input, int bufferSize, float tau, float sampleRate) {
output.init(bufferSize * 2);
_in = input;
_bufferSize = bufferSize;
bypass = false;
_tau = tau;
_sampleRate = sampleRate;
}
void start() {
if (running) {
return;
}
_workerThread = std::thread(_worker, this);
running = true;
}
void stop() {
if (!running) {
return;
}
_in->stopReader();
output.stopWriter();
_workerThread.join();
_in->clearReadStop();
output.clearWriteStop();
running = false;
}
void setBlockSize(int blockSize) {
if (running) {
return;
}
_bufferSize = blockSize;
output.setMaxLatency(blockSize * 2);
}
void setSamplerate(float sampleRate) {
if (running) {
return;
}
_sampleRate = sampleRate;
}
void setTau(float tau) {
if (running) {
return;
}
_tau = tau;
}
stream<float> output;
bool bypass;
private:
static void _worker(FMDeemphasis* _this) {
float* inBuf = new float[_this->_bufferSize];
float* outBuf = new float[_this->_bufferSize];
int count = _this->_bufferSize;
float lastOut = 0.0f;
float dt = 1.0f / _this->_sampleRate;
float alpha = dt / (_this->_tau + dt);
spdlog::warn("Deemp filter started: {0}, {1}", _this->_tau * 1000000.0, _this->_sampleRate);
while (true) {
if (_this->_in->read(inBuf, count) < 0) { break; };
if (_this->bypass) {
if (_this->output.write(inBuf, count) < 0) { break; };
continue;
}
if (isnan(lastOut)) {
lastOut = 0.0f;
}
outBuf[0] = (alpha * inBuf[0]) + ((1-alpha) * lastOut);
for (int i = 1; i < count; i++) {
outBuf[i] = (alpha * inBuf[i]) + ((1 - alpha) * outBuf[i - 1]);
}
lastOut = outBuf[count - 1];
if (_this->output.write(outBuf, count) < 0) { break; };
}
delete[] inBuf;
delete[] outBuf;
}
stream<float>* _in;
int _bufferSize;
std::thread _workerThread;
bool running = false;
float _sampleRate;
float _tau;
};
};

View File

@ -564,4 +564,212 @@ namespace dsp {
int _blockSize;
bool running = false;
};
class FloatPolyphaseFIRResampler {
public:
FloatPolyphaseFIRResampler() {
}
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, _outputSampleRate, passBand, transWidth, _interp - 1);
}
else {
dsp::BlackmanWindow(_taps, _outputSampleRate, cutoff, cutoff, _interp - 1);
}
}
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;
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
}
else {
dsp::BlackmanWindow(_taps,_outputSampleRate, cutoff, cutoff, _interp - 1);
}
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);
float cutoff = std::min<float>(_outputSampleRate / 2.0f, _inputSampleRate / 2.0f);
if (passBand > 0.0f && transWidth > 0.0f) {
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
}
else {
dsp::BlackmanWindow(_taps, _outputSampleRate, cutoff, cutoff, _interp - 1);
}
start();
}
void setFilterParams(float passBand, float transWidth) {
stop();
dsp::BlackmanWindow(_taps, _outputSampleRate, passBand, transWidth, _interp - 1);
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(FloatPolyphaseFIRResampler* _this) {
float* inBuf = new float[_this->_blockSize];
float* outBuf = new float[_this->outputBlockSize];
int inCount = _this->_blockSize;
int outCount = _this->outputBlockSize;
int interp = _this->_interp;
int decim = _this->_decim;
float correction = interp;//(float)sqrt((float)interp);
int tapCount = _this->_taps.size();
float* taps = new float[tapCount];
for (int i = 0; i < tapCount; i++) {
taps[i] = _this->_taps[i] * correction;
}
float* delayBuf = new float[tapCount];
float* delayStart = &inBuf[std::max<int>(inCount - tapCount, 0)];
int delaySize = tapCount * sizeof(float);
float* delayBufEnd = &delayBuf[std::max<int>(tapCount - inCount, 0)];
int moveSize = std::min<int>(inCount, tapCount - inCount) * sizeof(float);
int inSize = inCount * sizeof(float);
int afterInterp = inCount * interp;
int outIndex = 0;
tapCount -= interp - 1;
while (true) {
if (_this->_input->read(inBuf, inCount) < 0) { break; };
for (int i = 0; i < outCount; i++) {
outBuf[i] = 0;
int filterId = (i * decim) % interp;
int inputId = (i * decim) / interp;
for (int j = 0; j < tapCount; j++) {
outBuf[i] += GET_FROM_RIGHT_BUF(inBuf, delayBuf, tapCount, inputId - j) * taps[j + filterId];
}
}
if (tapCount > inCount) {
memmove(delayBuf, delayBufEnd, moveSize);
memcpy(delayBufEnd, delayStart, inSize);
}
else {
memcpy(delayBuf, delayStart, delaySize);
}
if (_this->output.write(outBuf, _this->outputBlockSize) < 0) { break; };
}
delete[] inBuf;
delete[] outBuf;
delete[] delayBuf;
}
std::thread _workerThread;
stream<float>* _input;
std::vector<float> _taps;
int _interp;
int _decim;
int outputBlockSize;
float _outputSampleRate;
float _inputSampleRate;
int _blockSize;
bool running = false;
};
};

View File

@ -141,7 +141,7 @@ void FrequencySelect::draw() {
}
}
long freq = 0;
uint64_t freq = 0;
for (int i = 0; i < 12; i++) {
freq += digits[i] * pow(10, 11 - i);
}
@ -151,9 +151,9 @@ void FrequencySelect::draw() {
ImGui::NewLine();
}
void FrequencySelect::setFrequency(long freq) {
void FrequencySelect::setFrequency(uint64_t freq) {
int i = 11;
for (long f = freq; i >= 0; i--) {
for (uint64_t f = freq; i >= 0; i--) {
digits[i] = f % 10;
f -= digits[i];
f /= 10;

View File

@ -1,15 +1,16 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <stdint.h>
class FrequencySelect {
public:
FrequencySelect();
void init();
void draw();
void setFrequency(long freq);
void setFrequency(uint64_t freq);
long frequency;
uint64_t frequency;
bool frequencyChanged = false;
private:

View File

@ -21,10 +21,21 @@
#include <Windows.h>
#endif
bool maximized = false;
static void glfw_error_callback(int error, const char* description) {
spdlog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
int main() {
#ifdef _WIN32
//FreeConsole();
@ -32,6 +43,11 @@ int main() {
spdlog::info("SDR++ v" VERSION_STR);
// Load config
spdlog::info("Loading config");
config::load("config.json");
config::startAutoSave();
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
@ -43,14 +59,23 @@ int main() {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
int winWidth = config::config["windowSize"]["w"];
int winHeight = config::config["windowSize"]["h"];
maximized = config::config["maximized"];
// Create window with graphics context
GLFWwindow* window = glfwCreateWindow(1280, 720, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (window == NULL)
return 1;
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
if (maximized) {
glfwMaximizeWindow(window);
}
glfwSetWindowMaximizeCallback(window, maximized_callback);
// Load app icon
GLFWimage icons[10];
icons[0].pixels = stbi_load("res/icons/sdrpp.png", &icons[0].width, &icons[0].height, 0, 4);
@ -94,11 +119,6 @@ int main() {
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 150");
// Load config
spdlog::info("Loading config");
config::load("config.json");
config::startAutoSave();
style::setDarkStyle();
spdlog::info("Loading icons");
@ -114,6 +134,8 @@ int main() {
spdlog::info("Ready.");
bool _maximized = maximized;
// Main loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
@ -123,15 +145,31 @@ int main() {
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if (_maximized != maximized) {
_maximized = maximized;
config::config["maximized"]= _maximized;
config::configModified = true;
if (!maximized) {
glfwSetWindowSize(window, config::config["windowSize"]["w"], config::config["windowSize"]["h"]);
}
}
int wwidth, wheight;
glfwGetWindowSize(window, &wwidth, &wheight);
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(wwidth, wheight));
drawWindow();
int _winWidth, _winHeight;
glfwGetWindowSize(window, &_winWidth, &_winHeight);
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
winWidth = _winWidth;
winHeight = _winHeight;
config::config["windowSize"]["w"] = winWidth;
config::config["windowSize"]["h"] = winHeight;
config::configModified = true;
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
drawWindow();
}
// Rendering
ImGui::Render();

View File

@ -41,7 +41,7 @@ dsp::NullSink sink;
int devId = 0;
int srId = 0;
watcher<int> bandplanId(0, true);
watcher<long> freq(90500000L);
watcher<uint64_t> freq(90500000Ui64);
int demod = 1;
watcher<float> vfoFreq(92000000.0f);
float dummyVolume = 1.0f;
@ -57,6 +57,11 @@ watcher<bool> bandPlanEnabled(true, false);
bool showCredits = false;
std::string audioStreamName = "";
std::string sourceName = "";
int menuWidth = 300;
bool grabbingMenu = false;
int newWidth = 300;
bool showWaterfall = true;
int fftHeight = 300;
void saveCurrentSource() {
int i = 0;
@ -188,25 +193,23 @@ void windowInit() {
}
if (!settingsFound) {
sampleRate = soapy.getSampleRate();
sigPath.setSampleRate(sampleRate);
}
// Search for the first source in the list to have a config
// If no pre-defined source, selected default device
}
// Load last band plan configuration
// TODO: Save/load config window size/fullscreen
// Also add a loading screen
// And a module add/remove/change order menu
// get rid of watchers and use if() instead
// Adjustable "snap to grid" for each VFO
// Finish the recorder module
// Add squelsh
// Bandwidth ajustment
// DSB / CW and RAW modes;
// Write a recorder
// Adjustable "snap to grid" for each VFO
// Bring VFO to a visible place when changing sample rate if it's smaller
// Possibility to resize waterfall and menu
// Have a proper root directory
// And a module add/remove/change order menu
// get rid of watchers and use if() instead
// Switch to double for all frequecies and bandwidth
// Update UI settings
@ -265,6 +268,17 @@ void windowInit() {
if (audioStreamName != "") {
volume = &audio::streams[audioStreamName]->volume;
}
menuWidth = config::config["menuWidth"];
newWidth = menuWidth;
showWaterfall = config::config["showWaterfall"];
if (!showWaterfall) {
wtf.hideWaterfall();
}
fftHeight = config::config["fftHeight"];
wtf.setFFTHeight(fftHeight);
}
void setVFO(float freq) {
@ -409,6 +423,13 @@ void drawWindow() {
wtf.bandplan = bandPlanEnabled.val ? &bandplan::bandplans[bandplan::bandplanNames[bandplanId.val]] : NULL;
}
int _fftHeight = wtf.getFFTHeight();
if (fftHeight != _fftHeight) {
fftHeight = _fftHeight;
config::config["fftHeight"] = fftHeight;
config::configModified = true;
}
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
@ -466,10 +487,36 @@ void drawWindow() {
showCredits = false;
}
ImGui::Columns(3, "WindowColumns", false);
// Handle menu resize
float curY = ImGui::GetCursorPosY();
ImVec2 winSize = ImGui::GetWindowSize();
ImGui::SetColumnWidth(0, 300);
ImGui::SetColumnWidth(1, winSize.x - 300 - 60);
ImVec2 mousePos = ImGui::GetMousePos();
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (grabbingMenu) {
newWidth = mousePos.x;
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
if (click) {
grabbingMenu = true;
}
}
else {
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
}
if(!down && grabbingMenu) {
grabbingMenu = false;
menuWidth = newWidth;
config::config["menuWidth"] = menuWidth;
config::configModified = true;
}
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
ImGui::SetColumnWidth(2, 60);
// Left Column
@ -668,7 +715,10 @@ void drawWindow() {
ImGui::Spacing();
}
if (ImGui::CollapsingHeader("Display")) {
if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::Checkbox("Show waterfall", &showWaterfall)) {
showWaterfall ? wtf.showWaterfall() : wtf.hideWaterfall();
}
ImGui::Spacing();
}
@ -693,6 +743,7 @@ void drawWindow() {
ImGui::EndChild();
ImGui::NextColumn();
ImGui::BeginChild("WaterfallControls");
@ -735,6 +786,7 @@ void drawWindow() {
wtf.setWaterfallMin(fftMin);
wtf.setWaterfallMax(fftMax);
ImGui::End();
if (showCredits) {

View File

@ -58,7 +58,9 @@ namespace vfoman {
if (vfos.find(name) == vfos.end()) {
return;
}
vfos[name].dspVFO->setOutputSampleRate(sampleRate, bandwidth);
VFO_t vfo = vfos[name];
vfo.dspVFO->setOutputSampleRate(sampleRate, bandwidth);
vfo.wtfVFO->setBandwidth(bandwidth);
}
void setReference(std::string name, int ref){

View File

@ -83,7 +83,9 @@ namespace ImGui {
fftMax = 0.0f;
waterfallMin = -70.0f;
waterfallMax = 0.0f;
fftHeight = 250;
FFTAreaHeight = 300;
newFFTAreaHeight = FFTAreaHeight;
fftHeight = FFTAreaHeight - 50;
dataWidth = 600;
lastWidgetPos.x = 0;
lastWidgetPos.y = 0;
@ -113,8 +115,8 @@ namespace ImGui {
// Vertical scale
for (float line = startLine; line > fftMin; line -= vRange) {
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, roundf(yPos)),
ImVec2(widgetPos.x + dataWidth + 50, roundf(yPos)),
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
IM_COL32(50, 50, 50, 255), 1.0f);
sprintf(buf, "%d", (int)line);
ImVec2 txtSz = ImGui::CalcTextSize(buf);
@ -263,6 +265,9 @@ namespace ImGui {
}
void WaterFall::updateWaterfallFb() {
if (!waterfallVisible) {
return;
}
float offsetRatio = viewOffset / (wholeBandwidth / 2.0f);
int drawDataSize;
int drawDataStart;
@ -357,14 +362,31 @@ namespace ImGui {
}
void WaterFall::onResize() {
// return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) {
return;
}
if (waterfallVisible) {
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
fftHeight = FFTAreaHeight - 50;
waterfallHeight = widgetSize.y - fftHeight - 52;
}
else {
fftHeight = widgetSize.y - 50;
}
dataWidth = widgetSize.x - 60.0f;
waterfallHeight = widgetSize.y - fftHeight - 52;
delete[] latestFFT;
delete[] waterfallFb;
if (waterfallVisible) {
delete[] waterfallFb;
}
latestFFT = new float[dataWidth];
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
if (waterfallVisible) {
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
}
for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0f; // Hide everything
}
@ -389,15 +411,8 @@ namespace ImGui {
buf_mtx.lock();
window = GetCurrentWindow();
// Fix for weird ImGui bug
ImVec2 tmpWidgetEndPos = ImGui::GetWindowContentRegionMax();
if (tmpWidgetEndPos.x < 100 || tmpWidgetEndPos.y < fftHeight + 100) {
buf_mtx.unlock();
return;
}
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = tmpWidgetEndPos;
widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x;
widgetPos.y += window->Pos.y;
widgetEndPos.x += window->Pos.x - 4; // Padding
@ -422,12 +437,44 @@ namespace ImGui {
processInputs();
drawFFT();
drawWaterfall();
if (waterfallVisible) {
drawWaterfall();
}
drawVFOs();
if (bandplan != NULL) {
drawBandPlan();
}
if (!waterfallVisible) {
buf_mtx.unlock();
return;
}
// Handle fft resize
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
mousePos.x -= widgetPos.x;
mousePos.y -= widgetPos.y;
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (draggingFW) {
newFFTAreaHeight = mousePos.y;
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
if (click) {
draggingFW = true;
}
}
if(!down && draggingFW) {
draggingFW = false;
FFTAreaHeight = newFFTAreaHeight;
onResize();
}
buf_mtx.unlock();
}
@ -443,15 +490,18 @@ namespace ImGui {
rawFFTs.resize(waterfallHeight);
}
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
waterfallFb[j] = waterfallPallet[id];
if (waterfallVisible) {
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
waterfallFb[j] = waterfallPallet[id];
}
waterfallUpdate = true;
}
waterfallUpdate = true;
buf_mtx.unlock();
}
@ -710,5 +760,24 @@ namespace ImGui {
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
}
};
void WaterFall::showWaterfall() {
waterfallVisible = true;
onResize();
}
void WaterFall::hideWaterfall() {
waterfallVisible = false;
onResize();
}
void WaterFall::setFFTHeight(int height) {
FFTAreaHeight = height;
onResize();
}
int WaterFall::getFFTHeight() {
return FFTAreaHeight;
}
};

View File

@ -87,6 +87,12 @@ namespace ImGui {
void selectFirstVFO();
void showWaterfall();
void hideWaterfall();
void setFFTHeight(int height);
int getFFTHeight();
bool centerFreqMoved = false;
bool vfoFreqChanged = false;
bool bandplanEnabled = false;
@ -175,5 +181,10 @@ namespace ImGui {
uint32_t* waterfallFb;
bool draggingFW = false;
int FFTAreaHeight;
int newFFTAreaHeight;
bool waterfallVisible = true;
};
};