From a9cb6bfe79a1f1c658fc5b7e333586b7b5e77eba Mon Sep 17 00:00:00 2001 From: Ryzerth Date: Thu, 1 Apr 2021 16:54:16 +0200 Subject: [PATCH] Bugfix + added meteor demodulator --- CMakeLists.txt | 5 + core/src/dsp/block.h | 2 - core/src/dsp/clock_recovery.h | 30 ++- core/src/dsp/demodulator.h | 174 ++++++++++++++- core/src/dsp/pll.h | 11 +- core/src/dsp/processing.h | 171 ++++++++++++-- core/src/dsp/types.h | 40 ++++ core/src/dsp/window.h | 85 +++++++ .../src/gui/widgets/constellation_diagram.cpp | 43 ++++ core/src/gui/widgets/constellation_diagram.h | 25 +++ meteor_demodulator/CMakeLists.txt | 22 ++ meteor_demodulator/src/main.cpp | 211 ++++++++++++++++++ radio/src/fm_demod.h | 1 + 13 files changed, 789 insertions(+), 31 deletions(-) create mode 100644 core/src/gui/widgets/constellation_diagram.cpp create mode 100644 core/src/gui/widgets/constellation_diagram.h create mode 100644 meteor_demodulator/CMakeLists.txt create mode 100644 meteor_demodulator/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67407800..cd627ae0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhack option(OPT_BUILD_RTL_SDR_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON) option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON) option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder" OFF) +option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module" ON) # Core of SDR++ add_subdirectory("core") @@ -72,6 +73,10 @@ if (OPT_BUILD_FALCON9_DECODER) add_subdirectory("falcon9_decoder") endif (OPT_BUILD_FALCON9_DECODER) +if (OPT_BUILD_METEOR_DEMODULATOR) +add_subdirectory("meteor_demodulator") +endif (OPT_BUILD_METEOR_DEMODULATOR) + if (MSVC) set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") else() diff --git a/core/src/dsp/block.h b/core/src/dsp/block.h index caf5964f..4e0ae35c 100644 --- a/core/src/dsp/block.h +++ b/core/src/dsp/block.h @@ -8,8 +8,6 @@ #include -#define FL_M_PI 3.1415926535f - namespace dsp { class generic_unnamed_block { diff --git a/core/src/dsp/clock_recovery.h b/core/src/dsp/clock_recovery.h index e3c47a0f..ef87a34d 100644 --- a/core/src/dsp/clock_recovery.h +++ b/core/src/dsp/clock_recovery.h @@ -88,6 +88,30 @@ namespace dsp { generic_block>::registerOutput(&out); } + void setOmega(float omega, float omegaRelLimit) { + generic_block>::tempStop(); + omegaMin = _omega - (_omega * _omegaRelLimit); + omegaMax = _omega + (_omega * _omegaRelLimit); + _omega = omega; + _dynOmega = _omega; + generic_block>::tempStart(); + } + + void setGains(float omegaGain, float muGain) { + generic_block>::tempStop(); + _gainOmega = omegaGain; + _muGain = muGain; + generic_block>::tempStart(); + } + + void setOmegaRelLimit(float omegaRelLimit) { + generic_block>::tempStop(); + _omegaRelLimit = omegaRelLimit; + omegaMin = _omega - (_omega * _omegaRelLimit); + omegaMax = _omega + (_omega * _omegaRelLimit); + generic_block>::tempStart(); + } + void setInput(stream* in) { generic_block>::tempStop(); generic_block>::unregisterInput(_in); @@ -138,10 +162,10 @@ namespace dsp { // Perfrom interpolation the same way as for float values if (i < 7) { - volk_32fc_32f_dot_prod_32fc(&_p_0T, &delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8); + volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8); } else { - volk_32fc_32f_dot_prod_32fc(&_p_0T, &_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8); + volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8); } out.writeBuf[outCount++] = _p_0T; @@ -192,7 +216,7 @@ namespace dsp { int count; // Delay buffer - T delay[15]; + T delay[1024]; int nextOffset = 0; // Configuration diff --git a/core/src/dsp/demodulator.h b/core/src/dsp/demodulator.h index fe59c1cf..b610337d 100644 --- a/core/src/dsp/demodulator.h +++ b/core/src/dsp/demodulator.h @@ -26,7 +26,7 @@ inline float fast_arctan2(float y, float x) { if (y < 0.0f) { return -angle; } - return angle; + return angle; } namespace dsp { @@ -499,21 +499,185 @@ namespace dsp { class MSKDemod : public generic_hier_block { public: MSKDemod() {} - MSKDemod(stream* input, float sampleRate, float deviation, float baudRate) { init(input, sampleRate, deviation, baudRate); } + MSKDemod(stream* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + init(input, sampleRate, deviation, baudRate); + } - void init(stream* input, float sampleRate, float deviation, float baudRate) { - demod.init(input, sampleRate, deviation); - recov.init(&demod.out, sampleRate / baudRate, powf(0.01f, 2) / 4.0f, 0.01f, 100e-6f); + void init(stream* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + _sampleRate = sampleRate; + _deviation = deviation; + _baudRate = baudRate; + _omegaGain = omegaGain; + _muGain = muGain; + _omegaRelLimit = omegaRelLimit; + + demod.init(input, _sampleRate, _deviation); + recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit); out = &recov.out; generic_hier_block::registerBlock(&demod); generic_hier_block::registerBlock(&recov); } + void setSampleRate(float sampleRate) { + generic_hier_block::tempStop(); + _sampleRate = sampleRate; + demod.setSampleRate(_sampleRate); + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + generic_hier_block::tempStart(); + } + + void setDeviation(float deviation) { + _deviation = deviation; + demod.setDeviation(deviation); + } + + void setBaudRate(float baudRate, float omegaRelLimit) { + _baudRate = baudRate; + _omegaRelLimit = omegaRelLimit; + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + } + + void setMMGains(float omegaGain, float myGain) { + _omegaGain = omegaGain; + _muGain = myGain; + recov.setGains(_omegaGain, _muGain); + } + + void setOmegaRelLimit(float omegaRelLimit) { + _omegaRelLimit = omegaRelLimit; + recov.setOmegaRelLimit(_omegaRelLimit); + } + stream* out = NULL; private: FloatFMDemod demod; MMClockRecovery recov; + + float _sampleRate; + float _deviation; + float _baudRate; + float _omegaGain; + float _muGain; + float _omegaRelLimit; + }; + + template + class PSKDemod : public generic_hier_block> { + public: + PSKDemod() {} + PSKDemod(stream* input, float sampleRate, float baudRate, int RRCTapCount = 32, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + init(input, sampleRate, deviation, baudRate, RRCTapCount, RRCAlpha, costasLoopBw, omegaGain, muGain, omegaRelLimit); + } + + void init(stream* input, float sampleRate, float baudRate, int RRCTapCount = 32, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + _RRCTapCount = RRCTapCount; + _RRCAlpha = RRCAlpha; + _sampleRate = sampleRate; + _agcRate = agcRate; + _costasLoopBw = costasLoopBw; + _baudRate = baudRate; + _omegaGain = omegaGain; + _muGain = muGain; + _omegaRelLimit = omegaRelLimit; + + agc.init(input, 1.0f, 65535, _agcRate); + taps.init(_RRCTapCount, _sampleRate, _baudRate, _RRCAlpha); + rrc.init(&agc.out, &taps); + demod.init(&rrc.out, _costasLoopBw); + + generic_hier_block>::registerBlock(&agc); + generic_hier_block>::registerBlock(&rrc); + generic_hier_block>::registerBlock(&demod); + + if constexpr (OFFSET) { + delay.init(&demod.out); + recov.init(&delay.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit); + generic_hier_block>::registerBlock(&delay); + } + else { + recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit); + } + + generic_hier_block>::registerBlock(&recov); + + out = &recov.out; + } + + void setInput(stream* input) { + agc.setInput(input); + } + + void setSampleRate(float sampleRate) { + _sampleRate = sampleRate; + rrc.tempStop(); + recov.tempStop(); + taps.setSampleRate(_sampleRate); + rrc.updateWindow(&taps); + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + rrc.tempStart(); + recov.tempStart(); + } + + void setBaudRate(float baudRate) { + _baudRate = baudRate; + rrc.tempStop(); + recov.tempStop(); + taps.setBaudRate(_baudRate); + rrc.updateWindow(&taps); + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + rrc.tempStart(); + recov.tempStart(); + } + + void setRRCParams(int RRCTapCount, float RRCAlpha) { + _RRCTapCount = RRCTapCount; + _RRCAlpha = RRCAlpha; + taps.setTapCount(_RRCTapCount); + taps.setAlpha(RRCAlpha); + rrc.updateWindow(&taps); + } + + void setAgcRate(float agcRate) { + _agcRate = agcRate; + agc.setRate(_agcRate); + } + + void setCostasLoopBw(float costasLoopBw) { + _costasLoopBw = costasLoopBw; + costas.setLoopBandwidth(_costasLoopBw); + } + + void setMMGains(float omegaGain, float myGain) { + _omegaGain = omegaGain; + _muGain = myGain; + recov.setGains(_omegaGain, _muGain); + } + + void setOmegaRelLimit(float omegaRelLimit) { + _omegaRelLimit = omegaRelLimit; + recov.setOmegaRelLimit(_omegaRelLimit); + } + + stream* out = NULL; + + private: + dsp::ComplexAGC agc; + dsp::RRCTaps taps; + dsp::FIR rrc; + CostasLoop demod; + DelayImag delay; + MMClockRecovery recov; + + int _RRCTapCount; + float _RRCAlpha; + float _sampleRate; + float _agcRate; + float _baudRate; + float _costasLoopBw; + float _omegaGain; + float _muGain; + float _omegaRelLimit; }; } \ No newline at end of file diff --git a/core/src/dsp/pll.h b/core/src/dsp/pll.h index 25752208..b88380b0 100644 --- a/core/src/dsp/pll.h +++ b/core/src/dsp/pll.h @@ -54,7 +54,8 @@ namespace dsp { for (int i = 0; i < count; i++) { // Mix the VFO with the input to create the output value - outVal = lastVCO * _in->readBuf[i]; + outVal.re = (lastVCO.re*_in->readBuf[i].re) - (lastVCO.im*_in->readBuf[i].im); + outVal.im = (lastVCO.im*_in->readBuf[i].re) + (lastVCO.re*_in->readBuf[i].im); out.writeBuf[i] = outVal; // Calculate the phase error estimation @@ -77,12 +78,12 @@ namespace dsp { } if (error > 1.0f) { error = 1.0f; } - if (error < -1.0f) { error = -1.0f; } + else if (error < -1.0f) { error = -1.0f; } // Integrate frequency and clamp it vcoFrequency += _beta * error; if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; } - if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; } + else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; } // Calculate new phase and wrap it vcoPhase += vcoFrequency + (_alpha * error); @@ -90,8 +91,8 @@ namespace dsp { while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); } // Calculate output - lastVCO.re = cosf(vcoPhase); - lastVCO.im = sinf(vcoPhase); + lastVCO.re = cosf(-vcoPhase); + lastVCO.im = sinf(-vcoPhase); } diff --git a/core/src/dsp/processing.h b/core/src/dsp/processing.h index dd77bc8d..5f18d3f1 100644 --- a/core/src/dsp/processing.h +++ b/core/src/dsp/processing.h @@ -151,8 +151,14 @@ namespace dsp { FeedForwardAGC(stream* in) { init(in); } + ~FeedForwardAGC() { + generic_block>::stop(); + delete[] buffer; + } + void init(stream* in) { _in = in; + buffer = new T[STREAM_BUFFER_SIZE]; generic_block>::registerInput(_in); generic_block>::registerOutput(&out); } @@ -170,27 +176,108 @@ namespace dsp { int count = _in->read(); if (count < 0) { return -1; } - float level = 1e-4; + float level; + float val; - // TODO: THIS AGC IS BAAAAAD!!!! + // Process buffer + memcpy(&buffer[inBuffer], _in->readBuf, count * sizeof(T)); + inBuffer += count; + + // If there aren't enough samples, wait for more + if (inBuffer < sampleCount) { + _in->flush(); + return count; + } + + int toProcess = (inBuffer - sampleCount) + 1; if constexpr (std::is_same_v) { - for (int i = 0; i < count; i++) { - if (fabs(_in->readBuf[i]) > level) { level = fabs(_in->readBuf[i]); } + for (int i = 0; i < toProcess; i++) { + level = 1e-4; + for (int j = 0; j < sampleCount; j++) { + val = fabsf(buffer[i + j]) + if (val > level) { level = val; } + } + out.writeBuf[i] = buffer[i] / level; } - volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count); } if constexpr (std::is_same_v) { - float reAbs, imAbs, val; - for (int i = 0; i < count; i++) { - reAbs = fabs(_in->readBuf[i].re); - imAbs = fabs(_in->readBuf[i].im); - if (reAbs > imAbs) { val = reAbs + 0.4 * imAbs; } - else { val = imAbs + 0.4 * reAbs; } - if (val > level) { level = val; } + for (int i = 0; i < toProcess; i++) { + level = 1e-4; + for (int j = 0; j < sampleCount; j++) { + val = buffer[i + j].fastAmplitude(); + if (val > level) { level = val; } + } + out.writeBuf[i] = buffer[i] / level; } - lv_32fc_t cplxLvl = {1.0f / level, 1.0f / level}; - volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, cplxLvl, count); + } + + _in->flush(); + + // Move rest of buffer + memmove(buffer, &buffer[toProcess], (sampleCount - 1) * sizeof(T)); + inBuffer -= toProcess; + + if (!out.swap(count)) { return -1; } + return toProcess; + } + + stream out; + + private: + T* buffer; + int inBuffer = 0; + int sampleCount = 1024; + stream* _in; + + }; + + class ComplexAGC : public generic_block { + public: + ComplexAGC() {} + + ComplexAGC(stream* in, float setPoint, float maxGain, float rate) { init(in, setPoint, maxGain, rate); } + + void init(stream* in, float setPoint, float maxGain, float rate) { + _in = in; + _setPoint = setPoint; + _maxGain = maxGain; + _rate = rate; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + } + + void setInput(stream* in) { + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + void setSetPoint(float setPoint) { + _setPoint = setPoint; + } + + void setMaxGain(float maxGain) { + _maxGain = maxGain; + } + + void setRate(float rate) { + _rate = rate; + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + dsp::complex_t val; + for (int i = 0; i < count; i++) { + val = _in->readBuf[i] * _gain; + out.writeBuf[i] = val; + _gain += (_setPoint - val.amplitude()) * _rate; + if (_gain > _maxGain) { _gain = _maxGain; } } _in->flush(); @@ -198,13 +285,65 @@ namespace dsp { return count; } - stream out; + stream out; private: - stream* _in; + float _gain = 1.0f; + float _setPoint = 1.0f; + float _maxGain = 10e4; + float _rate = 10e-4; + + stream* _in; }; + class DelayImag : public generic_block { + public: + DelayImag() {} + + DelayImag(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + } + + void setInput(stream* in) { + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + dsp::complex_t val; + for (int i = 0; i < count; i++) { + val = _in->readBuf[i]; + out.writeBuf[i].re = val.re; + out.writeBuf[i].im = lastIm; + lastIm = val.im; + } + + _in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + stream out; + + private: + float lastIm = 0.0f; + stream* _in; + + }; + + template class Volume : public generic_block> { diff --git a/core/src/dsp/types.h b/core/src/dsp/types.h index af1e35c8..c446f33b 100644 --- a/core/src/dsp/types.h +++ b/core/src/dsp/types.h @@ -1,4 +1,7 @@ #pragma once +#include + +#define FL_M_PI 3.1415926535f namespace dsp { struct complex_t { @@ -6,6 +9,10 @@ namespace dsp { return complex_t{re*b, im*b}; } + complex_t operator/(const float b) { + return complex_t{re/b, im/b}; + } + complex_t operator*(const complex_t& b) { return complex_t{(re*b.re) - (im*b.im), (im*b.re) + (re*b.im)}; } @@ -22,6 +29,39 @@ namespace dsp { return complex_t{re, -im}; } + inline float phase() { + return atan2f(im, re); + } + + inline float fastPhase() { + float abs_im = fabsf(im); + float r, angle; + if (re == 0.0f && im == 0.0f) { return 0.0f; } + if (re>=0.0f) { + r = (re - abs_im) / (re + abs_im); + angle = (FL_M_PI / 4.0f) - (FL_M_PI / 4.0f) * r; + } + else { + r = (re + abs_im) / (abs_im - re); + angle = (3.0f * (FL_M_PI / 4.0f)) - (FL_M_PI / 4.0f) * r; + } + if (im < 0.0f) { + return -angle; + } + return angle; + } + + inline float amplitude() { + return sqrt((re*re) + (im*im)); + } + + inline float fastAmplitude() { + float re_abs = fabsf(re); + float im_abs = fabsf(re); + if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; } + return im_abs + 0.4f * re_abs; + } + float re; float im; }; diff --git a/core/src/dsp/window.h b/core/src/dsp/window.h index 5ba40f35..82714153 100644 --- a/core/src/dsp/window.h +++ b/core/src/dsp/window.h @@ -144,4 +144,89 @@ namespace dsp { }; } + + + class RRCTaps : public filter_window::generic_window { + public: + RRCTaps() {} + RRCTaps(int tapCount, float sampleRate, float baudRate, float alpha) { init(tapCount, sampleRate, baudRate, alpha); } + + void init(int tapCount, float sampleRate, float baudRate, float alpha) { + _tapCount = tapCount; + _sampleRate = sampleRate; + _baudRate = baudRate; + _alpha = alpha; + } + + int getTapCount() { + return _tapCount; + } + + void setSampleRate(float sampleRate) { + _sampleRate = sampleRate; + } + + void setBaudRate(float baudRate) { + _baudRate = baudRate; + } + + void setTapCount(int count) { + _tapCount = count; + } + + void setAlpha(float alpha) { + _alpha = alpha; + } + + void createTaps(float* taps, int tapCount, float factor = 1.0f) { + // ======== CREDIT: GNU Radio ========= + tapCount |= 1; // ensure that tapCount is odd + + double spb = _sampleRate / _baudRate; // samples per bit/symbol + double scale = 0; + for (int i = 0; i < tapCount; i++) + { + double x1, x2, x3, num, den; + double xindx = i - tapCount / 2; + x1 = FL_M_PI * xindx / spb; + x2 = 4 * _alpha * xindx / spb; + x3 = x2 * x2 - 1; + + // Avoid Rounding errors... + if (fabs(x3) >= 0.000001) { + if (i != tapCount / 2) + num = cos((1 + _alpha) * x1) + + sin((1 - _alpha) * x1) / (4 * _alpha * xindx / spb); + else + num = cos((1 + _alpha) * x1) + (1 - _alpha) * FL_M_PI / (4 * _alpha); + den = x3 * FL_M_PI; + } + else { + if (_alpha == 1) + { + taps[i] = -1; + scale += taps[i]; + continue; + } + x3 = (1 - _alpha) * x1; + x2 = (1 + _alpha) * x1; + num = (sin(x2) * (1 + _alpha) * FL_M_PI - + cos(x3) * ((1 - _alpha) * FL_M_PI * spb) / (4 * _alpha * xindx) + + sin(x3) * spb * spb / (4 * _alpha * xindx * xindx)); + den = -32 * FL_M_PI * _alpha * _alpha * xindx / spb; + } + taps[i] = 4 * _alpha * num / den; + scale += taps[i]; + } + + for (int i = 0; i < tapCount; i++) { + taps[i] = taps[i] / scale; + } + } + + private: + int _tapCount; + float _sampleRate, _baudRate, _alpha; + + }; } \ No newline at end of file diff --git a/core/src/gui/widgets/constellation_diagram.cpp b/core/src/gui/widgets/constellation_diagram.cpp new file mode 100644 index 00000000..d078d79e --- /dev/null +++ b/core/src/gui/widgets/constellation_diagram.cpp @@ -0,0 +1,43 @@ +#pragma once +#include + +namespace ImGui { + ConstellationDiagram::ConstellationDiagram() { + memset(buffer, 0, 1024 * sizeof(dsp::complex_t)); + } + + void ConstellationDiagram::draw(const ImVec2& size_arg) { + std::lock_guard lck(bufferMtx); + ImGuiWindow* window = GetCurrentWindow(); + ImGuiStyle& style = GetStyle(); + float pad = style.FramePadding.y; + ImVec2 min = window->DC.CursorPos; + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), CalcItemWidth()); + ImRect bb(min, ImVec2(min.x+size.x, min.y+size.y)); + float lineHeight = size.y; + + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, 0)) { + return; + } + + window->DrawList->AddRectFilled(min, ImVec2(min.x+size.x, min.y+size.y), IM_COL32(0,0,0,255)); + ImU32 col = ImGui::GetColorU32(ImGuiCol_CheckMark, 0.7f); + float increment = size.x / 1024.0f; + for (int i = 0; i < 1024; i++) { + if (buffer[i].re > 1.5f || buffer[i].re < -1.5f) { continue; } + if (buffer[i].im > 1.5f || buffer[i].im < -1.5f) { continue; } + window->DrawList->AddCircleFilled(ImVec2((((buffer[i].re / 1.5f) + 1) * (size.x*0.5f)) + min.x, (((buffer[i].im / 1.5f) + 1) * (size.y*0.5f)) + min.y), 2, col); + } + } + + dsp::complex_t* ConstellationDiagram::aquireBuffer() { + bufferMtx.lock(); + return buffer; + } + + void ConstellationDiagram::releaseBuffer() { + bufferMtx.unlock(); + } + +} \ No newline at end of file diff --git a/core/src/gui/widgets/constellation_diagram.h b/core/src/gui/widgets/constellation_diagram.h new file mode 100644 index 00000000..fa7c9dd1 --- /dev/null +++ b/core/src/gui/widgets/constellation_diagram.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ImGui { + class ConstellationDiagram { + public: + ConstellationDiagram(); + + void draw(const ImVec2& size_arg = ImVec2(0, 0)); + + dsp::complex_t* aquireBuffer(); + + void releaseBuffer(); + + private: + std::mutex bufferMtx; + dsp::complex_t buffer[1024]; + + }; +} \ No newline at end of file diff --git a/meteor_demodulator/CMakeLists.txt b/meteor_demodulator/CMakeLists.txt new file mode 100644 index 00000000..c10d078d --- /dev/null +++ b/meteor_demodulator/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.13) +project(meteor_demodulator) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive") +endif (MSVC) + +file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") + +include_directories("src/") +include_directories("src/libcorrect/") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") + +add_library(meteor_demodulator SHARED ${SRC}) +target_link_libraries(meteor_demodulator PRIVATE sdrpp_core) +set_target_properties(meteor_demodulator PROPERTIES PREFIX "") + +# Install directives +install(TARGETS meteor_demodulator DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/meteor_demodulator/src/main.cpp b/meteor_demodulator/src/main.cpp new file mode 100644 index 00000000..ed24f093 --- /dev/null +++ b/meteor_demodulator/src/main.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +SDRPP_MOD_INFO { + /* Name: */ "meteor_demodulator", + /* Description: */ "Meteor demodulator for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ -1 +}; + +std::string genFileName(std::string prefix, std::string suffix) { + time_t now = time(0); + tm *ltm = localtime(&now); + char buf[1024]; + sprintf(buf, "%s_%02d-%02d-%02d_%02d-%02d-%02d%s", prefix.c_str(), ltm->tm_hour, ltm->tm_min, ltm->tm_sec, ltm->tm_mday, ltm->tm_mon + 1, ltm->tm_year + 1900, suffix.c_str()); + return buf; +} + +#define INPUT_SAMPLE_RATE 150000 + +class MeteorDemodulatorModule : public ModuleManager::Instance { +public: + MeteorDemodulatorModule(std::string name) : folderSelect("%ROOT%/recordings") { + this->name = name; + + writeBuffer = new int8_t[STREAM_BUFFER_SIZE]; + + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 1); + demod.init(vfo->output, INPUT_SAMPLE_RATE, 72000.0f, 32, 0.6f, 0.1f, 0.005f); + split.init(demod.out); + split.bindStream(&symSinkStream); + split.bindStream(&sinkStream); + reshape.init(&symSinkStream, 1024, (72000 / 30) - 1024); + symSink.init(&reshape.out, symSinkHandler, this); + sink.init(&sinkStream, sinkHandler, this); + + demod.start(); + split.start(); + reshape.start(); + symSink.start(); + sink.start(); + + gui::menu.registerEntry(name, menuHandler, this, this); + } + + ~MeteorDemodulatorModule() { + + } + + void enable() { + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 1); + + demod.setInput(vfo->output); + + demod.start(); + split.start(); + reshape.start(); + symSink.start(); + sink.start(); + + enabled = true; + } + + void disable() { + demod.stop(); + split.stop(); + reshape.stop(); + symSink.stop(); + sink.stop(); + + sigpath::vfoManager.deleteVFO(vfo); + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + static void menuHandler(void* ctx) { + MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx; + + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + if (!_this->enabled) { style::beginDisabled(); } + + ImGui::SetNextItemWidth(menuWidth); + _this->constDiagram.draw(); + + _this->folderSelect.render("##meteor-recorder-" + _this->name); + + if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); } + + if (_this->recording) { + if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { + std::lock_guard lck(_this->recMtx); + _this->recording = false; + _this->recFile.close(); + _this->dataWritten = 0; + } + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %.2fMB", (float)_this->dataWritten / 1000000.0f); + } + else { + if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { + std::lock_guard lck(_this->recMtx); + _this->dataWritten = 0; + std::string filename = genFileName(_this->folderSelect.expandString(_this->folderSelect.path) + "/meteor", ".s"); + _this->recFile = std::ofstream(filename, std::ios::binary); + if (_this->recFile.is_open()) { + spdlog::info("Recording to '{0}'", filename); + _this->recording = true; + } + else { + spdlog::error("Could not open file for recording!"); + } + } + ImGui::Text("Idle --.--MB"); + } + + if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::endDisabled(); } + + if (!_this->enabled) { style::endDisabled(); } + } + + static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) { + MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx; + + dsp::complex_t* buf = _this->constDiagram.aquireBuffer(); + memcpy(buf, data, 1024 * sizeof(dsp::complex_t)); + _this->constDiagram.releaseBuffer(); + } + + static void sinkHandler(dsp::complex_t* data, int count, void* ctx) { + MeteorDemodulatorModule* _this = (MeteorDemodulatorModule*)ctx; + std::lock_guard lck(_this->recMtx); + if (!_this->recording) { return; } + for (int i = 0; i < count; i++) { + _this->writeBuffer[(2 * i)] = data[i].re * 64.0f; + _this->writeBuffer[(2 * i) + 1] = data[i].im * 64.0f; + } + _this->recFile.write((char*)_this->writeBuffer, count * 2); + _this->dataWritten += count * 2; + } + + std::string name; + bool enabled = true; + + + + // DSP Chain + VFOManager::VFO* vfo; + dsp::PSKDemod<4, false> demod; + dsp::Splitter split; + + dsp::stream symSinkStream; + dsp::stream sinkStream; + dsp::Reshaper reshape; + dsp::HandlerSink symSink; + dsp::HandlerSink sink; + + ImGui::ConstellationDiagram constDiagram; + + FolderSelect folderSelect; + + std::mutex recMtx; + bool recording = false; + uint64_t dataWritten = 0; + std::ofstream recFile; + + int8_t* writeBuffer; + +}; + +MOD_EXPORT void _INIT_() { + // Nothing +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new MeteorDemodulatorModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (MeteorDemodulatorModule*)instance; +} + +MOD_EXPORT void _END_() { + // Nothing either +} \ No newline at end of file diff --git a/radio/src/fm_demod.h b/radio/src/fm_demod.h index cd8e2ac8..d2c66484 100644 --- a/radio/src/fm_demod.h +++ b/radio/src/fm_demod.h @@ -146,6 +146,7 @@ private: bw = bandWidth; _vfo->setBandwidth(bw); demod.setDeviation(bw / 2.0f); + setAudioSampleRate(audioSampRate); } void setSnapInterval(float snapInt) {