kopia lustrzana https://github.com/AlexandreRouma/SDRPlusPlus
New stuff lmao
rodzic
649be86cb6
commit
709627a738
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
// };
|
||||
};
|
114
src/dsp/filter.h
114
src/dsp/filter.h
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
66
src/main.cpp
66
src/main.cpp
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
Ładowanie…
Reference in New Issue