mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	New stuff lmao
This commit is contained in:
		| @@ -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; | ||||
|     }; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user