diff --git a/core/src/core.cpp b/core/src/core.cpp index 7ac6cc2a..07d13488 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -47,13 +47,15 @@ namespace core { GLFWwindow* window; void setInputSampleRate(double samplerate) { + sigpath::signalPath.sourceSampleRate = samplerate; + double effectiveSr = samplerate / ((double)(1 << sigpath::signalPath.decimation)); // NOTE: Zoom controls won't work - spdlog::info("New DSP samplerate: {0}", samplerate); - gui::waterfall.setBandwidth(samplerate); + spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate); + gui::waterfall.setBandwidth(effectiveSr); gui::waterfall.setViewOffset(0); - gui::waterfall.setViewBandwidth(samplerate); - sigpath::signalPath.setSampleRate(samplerate); - gui::mainWindow.setViewBandwidthSlider(samplerate); + gui::waterfall.setViewBandwidth(effectiveSr); + sigpath::signalPath.setSampleRate(effectiveSr); + gui::mainWindow.setViewBandwidthSlider(effectiveSr); } }; @@ -202,6 +204,7 @@ int sdrpp_main(int argc, char *argv[]) { defConfig["showMenu"] = true; defConfig["showWaterfall"] = true; defConfig["source"] = ""; + defConfig["decimationPower"] = 0; defConfig["streams"]["Radio"]["muted"] = false; defConfig["streams"]["Radio"]["sink"] = "Audio"; diff --git a/core/src/dsp/decimation.h b/core/src/dsp/decimation.h new file mode 100644 index 00000000..5cdf4abe --- /dev/null +++ b/core/src/dsp/decimation.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include +#include +#include + +namespace dsp { + template + class HalfDecimator : public generic_block> { + public: + HalfDecimator() {} + + HalfDecimator(stream* in, dsp::filter_window::generic_window* window) { init(in, window); } + + ~HalfDecimator() { + if (!generic_block>::_block_init) { return; } + generic_block>::stop(); + volk_free(buffer); + volk_free(taps); + generic_block>::_block_init = false; + } + + void init(stream* in, dsp::filter_window::generic_window* window) { + _in = in; + + tapCount = window->getTapCount(); + taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); + window->createTaps(taps, tapCount); + + buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment()); + bufStart = &buffer[tapCount]; + generic_block>::registerInput(_in); + generic_block>::registerOutput(&out); + generic_block>::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block>::_block_init); + std::lock_guard lck(generic_block>::ctrlMtx); + generic_block>::tempStop(); + generic_block>::unregisterInput(_in); + _in = in; + generic_block>::registerInput(_in); + generic_block>::tempStart(); + } + + void updateWindow(dsp::filter_window::generic_window* window) { + assert(generic_block>::_block_init); + std::lock_guard lck(generic_block>::ctrlMtx); + _window = window; + volk_free(taps); + tapCount = window->getTapCount(); + taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment()); + bufStart = &buffer[tapCount]; + window->createTaps(taps, tapCount); + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + generic_block>::ctrlMtx.lock(); + + memcpy(bufStart, _in->readBuf, count * sizeof(T)); + _in->flush(); + + int inIndex = _inIndex; + int outIndex = 0; + if constexpr (std::is_same_v) { + while (inIndex < count) { + volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[outIndex], (float*)&buffer[inIndex+1], taps, tapCount); + inIndex += 2; + outIndex++; + } + } + if constexpr (std::is_same_v) { + while (inIndex < count) { + volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[inIndex+1], taps, tapCount); + inIndex += 2; + outIndex++; + } + } + _inIndex = inIndex - count; + + if (!out.swap(outIndex)) { return -1; } + + memmove(buffer, &buffer[count], tapCount * sizeof(T)); + + generic_block>::ctrlMtx.unlock(); + + return count; + } + + stream out; + + private: + stream* _in; + + dsp::filter_window::generic_window* _window; + + T* bufStart; + T* buffer; + int tapCount; + float* taps; + int _inIndex = 0; + + }; +} \ No newline at end of file diff --git a/core/src/gui/menus/source.cpp b/core/src/gui/menus/source.cpp index ce38948b..46dd9685 100644 --- a/core/src/gui/menus/source.cpp +++ b/core/src/gui/menus/source.cpp @@ -11,6 +11,7 @@ namespace sourecmenu { int sourceId = 0; double customOffset = 0.0; double effectiveOffset = 0.0; + int decimationPower = 0; EventHandler sourceRegisteredHandler; EventHandler sourceUnregisterHandler; @@ -39,6 +40,14 @@ namespace sourecmenu { "Ku LNB (9750MHz)\0" "Ku LNB (10700MHz)\0"; + const char* decimationStages = "None\0" + "2\0" + "4\0" + "8\0" + "16\0" + "32\0" + "64\0"; + void updateOffset() { if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; } else if (offsetMode == OFFSET_MODE_SPYVERTER) { effectiveOffset = 120000000; } // 120MHz Up-conversion @@ -113,10 +122,12 @@ namespace sourecmenu { std::string selected = core::configManager.conf["source"]; customOffset = core::configManager.conf["offset"]; offsetMode = core::configManager.conf["offsetMode"]; + decimationPower = core::configManager.conf["decimationPower"]; updateOffset(); refreshSources(); selectSource(selected); + sigpath::signalPath.setDecimation(decimationPower); sourceRegisteredHandler.handler = onSourceRegistered; sourceUnregisterHandler.handler = onSourceUnregister; @@ -130,8 +141,9 @@ namespace sourecmenu { void draw(void* ctx) { float itemWidth = ImGui::GetContentRegionAvailWidth(); + bool running = gui::mainWindow.sdrIsRunning(); - if (gui::mainWindow.sdrIsRunning()) { style::beginDisabled(); } + if (running) { style::beginDisabled(); } ImGui::SetNextItemWidth(itemWidth); if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) { @@ -141,7 +153,7 @@ namespace sourecmenu { core::configManager.release(true); } - if (gui::mainWindow.sdrIsRunning()) { style::endDisabled(); } + if (running) { style::endDisabled(); } sigpath::sourceManager.showSelectedMenu(); @@ -171,5 +183,17 @@ namespace sourecmenu { ImGui::InputDouble("##freq_offset", &effectiveOffset, 1.0, 100.0); style::endDisabled(); } + + if (running) { style::beginDisabled(); } + ImGui::Text("Decimation"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); + if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) { + sigpath::signalPath.setDecimation(decimationPower); + core::configManager.acquire(); + core::configManager.conf["decimationPower"] = decimationPower; + core::configManager.release(true); + } + if (running) { style::endDisabled(); } } } diff --git a/core/src/signal_path/dsp.cpp b/core/src/signal_path/dsp.cpp index 67d63df4..3584c15c 100644 --- a/core/src/signal_path/dsp.cpp +++ b/core/src/signal_path/dsp.cpp @@ -1,4 +1,5 @@ #include +#include SignalPath::SignalPath() { @@ -6,10 +7,13 @@ SignalPath::SignalPath() { void SignalPath::init(uint64_t sampleRate, int fftRate, int fftSize, dsp::stream* input, dsp::complex_t* fftBuffer, void fftHandler(dsp::complex_t*,int,void*), void* fftHandlerCtx) { this->sampleRate = sampleRate; + this->sourceSampleRate = sampleRate; this->fftRate = fftRate; this->fftSize = fftSize; inputBlockSize = sampleRate / 200.0f; + halfBandWindow.init(1000000, 200000, 4000000); + // split.init(input); inputBuffer.init(input); split.init(&inputBuffer.out); @@ -47,17 +51,25 @@ double SignalPath::getSampleRate() { } void SignalPath::start() { + for (auto& decimator : decimators) { + decimator->start(); + } inputBuffer.start(); split.start(); reshape.start(); fftHandlerSink.start(); + running = true; } void SignalPath::stop() { + for (auto& decimator : decimators) { + decimator->stop(); + } inputBuffer.stop(); split.stop(); reshape.stop(); fftHandlerSink.stop(); + running = false; } dsp::VFO* SignalPath::addVFO(std::string name, double outSampleRate, double bandwidth, double offset) { @@ -87,7 +99,6 @@ void SignalPath::removeVFO(std::string name) { } void SignalPath::setInput(dsp::stream* input) { - // split.setInput(input); inputBuffer.setInput(input); } @@ -120,4 +131,38 @@ void SignalPath::stopFFT() { void SignalPath::setBuffering(bool enabled) { inputBuffer.bypass = !enabled; +} + +void SignalPath::setDecimation(int dec) { + decimation = dec; + + // Stop existing decimators + if (!decimators.empty()) { + for (auto& decimator : decimators) { + decimator->stop(); + delete decimator; + } + } + decimators.clear(); + + // If no decimation, reconnect + if (!dec) { + split.setInput(&inputBuffer.out); + core::setInputSampleRate(sourceSampleRate); + return; + } + + // Create new decimators + if (running) { split.stop(); } + for (int i = 0; i < dec; i++) { + dsp::HalfDecimator* decimator = new dsp::HalfDecimator((i == 0) ? &inputBuffer.out : &decimators[i-1]->out, &halfBandWindow); + if (running) { decimator->start(); } + // TODO: ONLY start if running + decimators.push_back(decimator); + } + split.setInput(&decimators[decimators.size()-1]->out); + if (running) { split.start(); } + + // Update the DSP sample rate + core::setInputSampleRate(sourceSampleRate); } \ No newline at end of file diff --git a/core/src/signal_path/dsp.h b/core/src/signal_path/dsp.h index 03c8b25f..1d3b6ed1 100644 --- a/core/src/signal_path/dsp.h +++ b/core/src/signal_path/dsp.h @@ -3,6 +3,7 @@ #include #include #include +#include class SignalPath { public: @@ -21,8 +22,11 @@ public: void startFFT(); void stopFFT(); void setBuffering(bool enabled); + void setDecimation(int dec); dsp::SampleFrameBuffer inputBuffer; + double sourceSampleRate = 0; + int decimation = 0; private: struct VFO_t { @@ -39,10 +43,13 @@ private: // VFO std::map vfos; + std::vector*> decimators; + dsp::filter_window::BlackmanWindow halfBandWindow; double sampleRate; double fftRate; int fftSize; int inputBlockSize; bool bufferingEnabled = false; + bool running = false; }; \ No newline at end of file