diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d3e51fc..02230180 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ set(CMAKE_BUILD_TYPE "RelWithDebInfo") # Compiler config set(CMAKE_CXX_FLAGS "-O2 /std:c++17") +# PothosSDR +link_directories(sdrpp "C:/Program Files/PothosSDR/lib/") + # Volk include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/") link_libraries(volk) diff --git a/readme.md b/readme.md index 05ce7b3b..193d05d1 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,19 @@ SDR++ is a cross-platform and open source SDR software with the aim of being blo * Recording * Light theme (I know you weirdos exist lol) * Waterfall color scheme editor -* +* Switchable fft size +* Bias-T enable/disable +* other small customisation options +* Save waterfall and demod settings between sessions +* "Hide sidebar" option + +## Known issues (please check before reporting) +* Random crashes (yikes) +* Gains aren't stepped +* The default gains might contain a bogus value before being adjusted +* Clicks in the audio +* In some cases, it takes a long time to select a device (RTL-SDR in perticular) +* Min and Max buttons can get unachievable values (eg. min > max or min = max); # Building on Windows ## Requirements diff --git a/src/dsp/demodulator.h b/src/dsp/demodulator.h index 82971c74..30600de1 100644 --- a/src/dsp/demodulator.h +++ b/src/dsp/demodulator.h @@ -196,9 +196,9 @@ namespace dsp { max = outBuf[i]; } } - amp = (max - min); + amp = (max - min) / 2.0f; for (int i = 0; i < _this->_blockSize; i++) { - outBuf[i] = (outBuf[i] - min) / (max - min); + outBuf[i] = (outBuf[i] - min - amp) / amp; } if (_this->output.write(outBuf, _this->_blockSize) < 0) { break; }; } diff --git a/src/io/soapy.h b/src/io/soapy.h index f46ea778..2a91a014 100644 --- a/src/io/soapy.h +++ b/src/io/soapy.h @@ -9,6 +9,7 @@ namespace io { public: SoapyWrapper() { output.init(64000); + currentGains = new float[1]; refresh(); setDevice(devList[0]); } @@ -60,6 +61,18 @@ namespace io { txtSampleRateList += std::to_string((int)sampleRates[i]); txtSampleRateList += '\0'; } + + gainList = dev->listGains(SOAPY_SDR_RX, 0); + gainRanges.clear(); + if (gainList.size() == 0) { + return; + } + delete[] currentGains; + currentGains = new float[gainList.size()]; + for (int i = 0; i < gainList.size(); i++) { + gainRanges.push_back(dev->getGainRange(SOAPY_SDR_RX, 0, gainList[i])); + currentGains[i] = dev->getGain(SOAPY_SDR_RX, 0, gainList[i]); + } } void setSampleRate(float sampleRate) { @@ -74,6 +87,11 @@ namespace io { dev->setFrequency(SOAPY_SDR_RX, 0, freq); } + void setGain(int gainId, float gain) { + currentGains[gainId] = gain; + dev->setGain(SOAPY_SDR_RX, 0, gainList[gainId], gain); + } + bool isRunning() { return running; } @@ -83,6 +101,10 @@ namespace io { std::vector sampleRates; std::string txtSampleRateList; + std::vector gainList; + std::vector gainRanges; + float* currentGains; + dsp::stream output; private: diff --git a/src/main_window.cpp b/src/main_window.cpp index fce138af..8bc56ec2 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -21,6 +21,8 @@ FrequencySelect fSel; fftwf_complex *fft_in, *fft_out; fftwf_plan p; float* tempData; +float* uiGains; +char buf[1024]; int fftSize = 8192 * 8; @@ -66,6 +68,8 @@ void windowInit() { sigPath.init(sampleRate, 20, fftSize, &soapy.output, (dsp::complex_t*)fft_in, fftHandler); sigPath.start(); + + uiGains = new float[1]; } int devId = 0; @@ -79,7 +83,7 @@ bool showExample = false; long freq = 90500000; long _freq = 90500000; -int demod = 0; +int demod = 1; bool state = false; bool mulstate = true; @@ -220,6 +224,17 @@ void drawWindow() { if (devId != _devId) { _devId = devId; soapy.setDevice(soapy.devList[devId]); + srId = 0; + _srId = -1; + soapy.setSampleRate(soapy.sampleRates[0]); + if (soapy.gainList.size() == 0) { + return; + } + delete[] uiGains; + uiGains = new float[soapy.gainList.size()]; + for (int i = 0; i < soapy.gainList.size(); i++) { + uiGains[i] = soapy.currentGains[i]; + } } if (srId != _srId) { @@ -269,11 +284,6 @@ void drawWindow() { fSel.draw(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 8); - ImGui::SameLine(ImGui::GetWindowWidth() - 40); - ImGui::Image(icons::LOGO, ImVec2(30, 30)); - - ImGui::Columns(3, "WindowColumns", false); ImVec2 winSize = ImGui::GetWindowSize(); ImGui::SetColumnWidth(0, 300); @@ -287,17 +297,34 @@ void drawWindow() { ImGui::PushItemWidth(ImGui::GetWindowSize().x); ImGui::Combo("##_0_", &devId, soapy.txtDevList.c_str()); + ImGui::PopItemWidth(); if (!playing) { ImGui::Combo("##_1_", &srId, soapy.txtSampleRateList.c_str()); } else { ImGui::Text("%s Samples/s", soapy.txtSampleRateList.c_str()); } - + ImGui::SameLine(); if (ImGui::Button("Refresh")) { soapy.refresh(); } + + for (int i = 0; i < soapy.gainList.size(); i++) { + ImGui::Text("%s gain", soapy.gainList[i].c_str()); + ImGui::SameLine(); + sprintf(buf, "##_gain_slide_%d_", i); + ImGui::SliderFloat(buf, &uiGains[i], soapy.gainRanges[i].minimum(), soapy.gainRanges[i].maximum()); + + // float step = soapy.gainRanges[i].step(); + // printf("%f\n", step); + + // uiGains[i] = roundf(uiGains[i] / soapy.gainRanges[i].step()) * soapy.gainRanges[i].step(); + + if (uiGains[i] != soapy.currentGains[i]) { + soapy.setGain(i, uiGains[i]); + } + } } if (ImGui::CollapsingHeader("Radio")) { @@ -370,7 +397,7 @@ void drawWindow() { ImGui::NextColumn(); ImGui::Text("Zoom"); - ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw, 1000.0f, sampleRate, ""); + ImGui::VSliderFloat("##_7_", ImVec2(20.0f, 150.0f), &bw, sampleRate, 1000.0f, ""); ImGui::NewLine(); diff --git a/src/signal_path.cpp b/src/signal_path.cpp index 30614049..a8033d2e 100644 --- a/src/signal_path.cpp +++ b/src/signal_path.cpp @@ -160,4 +160,8 @@ void SignalPath::start() { audioResamp.start(); audio.start(); +} + +void SignalPath::setDCBiasCorrection(bool enabled) { + dcBiasRemover.bypass = !enabled; } \ No newline at end of file diff --git a/src/waterfall.cpp b/src/waterfall.cpp index ff7d4ae1..87241bf2 100644 --- a/src/waterfall.cpp +++ b/src/waterfall.cpp @@ -35,6 +35,7 @@ void doZoom(int offset, int width, int outWidth, std::vector data, float* } float freq_ranges[] = { + 1.0f, 2.0f, 2.5f, 5.0f, 10.0f, 20.0f, 25.0f, 50.0f, 100.0f, 200.0f, 250.0f, 500.0f, 1000.0f, 2000.0f, 2500.0f, 5000.0f, @@ -44,9 +45,9 @@ float freq_ranges[] = { 10000000.0f, 20000000.0f, 25000000.0f, 50000000.0f }; -float findBestFreqRange(float bandwidth) { - for (int i = 0; i < 28; i++) { - if (bandwidth / freq_ranges[i] < 25.0f) { +float findBestRange(float bandwidth, int maxSteps) { + for (int i = 0; i < 32; i++) { + if (bandwidth / freq_ranges[i] < (float)maxSteps) { return freq_ranges[i]; } } @@ -104,14 +105,14 @@ namespace ImGui { void WaterFall::drawFFT() { // Calculate scaling factor - float startLine = floorf(fftMax / 10.0f) * 10.0f; + float startLine = floorf(fftMax / vRange) * vRange; float vertRange = fftMax - fftMin; float scaleFactor = fftHeight / vertRange; char buf[100]; // Vertical scale - for (float line = startLine; line > fftMin; line -= 10.0f) { + 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)), @@ -177,7 +178,7 @@ namespace ImGui { void WaterFall::drawVFO() { float width = (vfoBandwidth / viewBandwidth) * (float)dataWidth; - int center = (((vfoOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f); + int center = roundf((((vfoOffset - viewOffset) / (viewBandwidth / 2.0f)) + 1.0f) * ((float)dataWidth / 2.0f)); int left; int right; @@ -316,6 +317,13 @@ namespace ImGui { freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11); freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50); + maxHSteps = dataWidth / 50; + maxVSteps = fftHeight / 15; + + range = findBestRange(viewBandwidth, maxHSteps); + vRange = findBestRange(fftMax - fftMin, maxVSteps); + vRange = 10.0f; + printf("Resized: %d %d\n", dataWidth, waterfallHeight); updateWaterfallFb(); @@ -461,7 +469,7 @@ namespace ImGui { viewBandwidth = bandWidth; lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0f); upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0f); - range = findBestFreqRange(bandWidth); + range = findBestRange(bandWidth, maxHSteps); updateWaterfallFb(); } @@ -491,6 +499,7 @@ namespace ImGui { void WaterFall::setFFTMin(float min) { fftMin = min; + vRange = findBestRange(fftMax - fftMin, maxVSteps); } float WaterFall::getFFTMin() { @@ -499,6 +508,7 @@ namespace ImGui { void WaterFall::setFFTMax(float max) { fftMax = max; + vRange = findBestRange(fftMax - fftMin, maxVSteps); } float WaterFall::getFFTMax() { diff --git a/src/waterfall.h b/src/waterfall.h index 95ce195d..974c6a6d 100644 --- a/src/waterfall.h +++ b/src/waterfall.h @@ -100,6 +100,11 @@ namespace ImGui { std::mutex buf_mtx; + float vRange; + + int maxVSteps; + int maxHSteps; + int dataWidth; // Width of the FFT and waterfall int fftHeight; // Height of the fft graph int waterfallHeight; // Height of the waterfall