diff --git a/decoder_modules/meteor_demodulator/src/main.cpp b/decoder_modules/meteor_demodulator/src/main.cpp index 300f3ad6..94ecb6ae 100644 --- a/decoder_modules/meteor_demodulator/src/main.cpp +++ b/decoder_modules/meteor_demodulator/src/main.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include "meteor_demod.h" #include #include #include @@ -47,16 +47,20 @@ public: // Load config config.acquire(); - bool created = false; + // Note: this first one may not be needed but I'm paranoid if (!config.conf.contains(name)) { - config.conf[name]["recPath"] = "%ROOT%/recordings"; - created = true; + config.conf[name] = json({}); } - folderSelect.setPath(config.conf[name]["recPath"]); - config.release(created); + if (config.conf[name].contains("recPath")) { + folderSelect.setPath(config.conf[name]["recPath"]); + } + if (config.conf[name].contains("brokenModulation")) { + brokenModulation = config.conf[name]["brokenModulation"]; + } + config.release(); vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true); - demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, 1e-6, 0.01); + demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, 1e-6, 0.01); split.init(&demod.out); split.bindStream(&symSinkStream); split.bindStream(&sinkStream); @@ -132,7 +136,7 @@ private: ImGui::SetNextItemWidth(menuWidth); _this->constDiagram.draw(); - if (_this->folderSelect.render("##meteor-recorder-" + _this->name)) { + if (_this->folderSelect.render("##meteor_rec" + _this->name)) { if (_this->folderSelect.pathIsValid()) { config.acquire(); config.conf[_this->name]["recPath"] = _this->folderSelect.path; @@ -140,16 +144,23 @@ private: } } + if (ImGui::Checkbox(CONCAT("Broken modulation##meteor_rec", _this->name), &_this->brokenModulation)) { + _this->demod.setBrokenModulation(_this->brokenModulation); + config.acquire(); + config.conf[_this->name]["brokenModulation"] = _this->brokenModulation; + config.release(true); + } + if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); } if (_this->recording) { - if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) { + if (ImGui::Button(CONCAT("Stop##meteor_rec_", _this->name), ImVec2(menuWidth, 0))) { _this->stopRecording(); } 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))) { + if (ImGui::Button(CONCAT("Record##meteor_rec_", _this->name), ImVec2(menuWidth, 0))) { _this->startRecording(); } ImGui::TextUnformatted("Idle --.--MB"); @@ -216,7 +227,7 @@ private: // DSP Chain VFOManager::VFO* vfo; - dsp::demod::PSK<4> demod; + dsp::demod::Meteor demod; dsp::routing::Splitter split; dsp::stream symSinkStream; @@ -233,6 +244,7 @@ private: bool recording = false; uint64_t dataWritten = 0; std::ofstream recFile; + bool brokenModulation = false; int8_t* writeBuffer; }; diff --git a/decoder_modules/meteor_demodulator/src/meteor_costas.h b/decoder_modules/meteor_demodulator/src/meteor_costas.h new file mode 100644 index 00000000..7c65c5d8 --- /dev/null +++ b/decoder_modules/meteor_demodulator/src/meteor_costas.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +namespace dsp::loop { + class MeteorCostas : public PLL { + using base_type = PLL; + public: + MeteorCostas() {} + + MeteorCostas(stream* in, double bandwidth, bool brokenModulation, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) { init(in, bandwidth, brokenModulation, initFreq, initPhase, minFreq, maxFreq); } + + void init(stream* in, double bandwidth, bool brokenModulation, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) { + _broken = brokenModulation; + base_type::init(in, bandwidth, initPhase, initFreq, minFreq, maxFreq); + } + + void setBrokenModulation(bool enabled) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + _broken = enabled; + } + + inline int process(int count, complex_t* in, complex_t* out) { + for (int i = 0; i < count; i++) { + out[i] = in[i] * math::phasor(-pcl.phase); + pcl.advance(errorFunction(out[i])); + } + return count; + } + + protected: + inline float errorFunction(complex_t val) { + float err; + if (_broken) { + const float PHASE1 = 0.47439988279190737; + const float PHASE2 = 2.1777839908413044; + const float PHASE3 = 3.8682349942715186; + const float PHASE4 = -0.29067248091319986; + + float phase = val.phase(); + float dp1 = math::normPhaseDiff(phase - PHASE1); + float dp2 = math::normPhaseDiff(phase - PHASE2); + float dp3 = math::normPhaseDiff(phase - PHASE3); + float dp4 = math::normPhaseDiff(phase - PHASE4); + float lowest = dp1; + if (fabsf(dp2) < fabsf(lowest)) { lowest = dp2; } + if (fabsf(dp3) < fabsf(lowest)) { lowest = dp3; } + if (fabsf(dp4) < fabsf(lowest)) { lowest = dp4; } + err = lowest * val.amplitude(); + } + else { + err = (math::step(val.re) * val.im) - (math::step(val.im) * val.re); + } + return std::clamp(err, -1.0f, 1.0f); + } + + bool _broken; + }; +} \ No newline at end of file diff --git a/decoder_modules/meteor_demodulator/src/meteor_demod.h b/decoder_modules/meteor_demodulator/src/meteor_demod.h new file mode 100644 index 00000000..9cc8594f --- /dev/null +++ b/decoder_modules/meteor_demodulator/src/meteor_demod.h @@ -0,0 +1,176 @@ +#pragma once +#include +#include +#include +#include "meteor_costas.h" +#include + +namespace dsp::demod { + class Meteor : public Processor { + using base_type = Processor; + public: + Meteor() {} + + Meteor(stream* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) { + init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, omegaGain, muGain); + } + + ~Meteor() { + if (!base_type::_block_init) { return; } + base_type::stop(); + taps::free(rrcTaps); + } + + void init(stream* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) { + _symbolrate = symbolrate; + _samplerate = samplerate; + _rrcTapCount = rrcTapCount; + _rrcBeta = rrcBeta; + + rrcTaps = taps::rootRaisedCosine(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); + rrc.init(NULL, rrcTaps); + agc.init(NULL, 1.0, 10e6, agcRate); + costas.init(NULL, costasBandwidth, brokenModulation); + recov.init(NULL, _samplerate / _symbolrate, omegaGain, muGain, omegaRelLimit); + + rrc.out.free(); + agc.out.free(); + costas.out.free(); + recov.out.free(); + + base_type::init(in); + } + + void setSymbolrate(double symbolrate) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + base_type::tempStop(); + _symbolrate = symbolrate; + taps::free(rrcTaps); + rrcTaps = taps::rootRaisedCosine(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); + rrc.setTaps(rrcTaps); + recov.setOmega(_samplerate / _symbolrate); + base_type::tempStart(); + } + + void setSamplerate(double samplerate) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + base_type::tempStop(); + _samplerate = samplerate; + taps::free(rrcTaps); + rrcTaps = taps::rootRaisedCosine(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); + rrc.setTaps(rrcTaps); + recov.setOmega(_samplerate / _symbolrate); + base_type::tempStart(); + } + + void setRRCParams(int rrcTapCount, double rrcBeta) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + base_type::tempStop(); + _rrcTapCount = rrcTapCount; + _rrcBeta = rrcBeta; + taps::free(rrcTaps); + rrcTaps = taps::rootRaisedCosine(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate); + rrc.setTaps(rrcTaps); + base_type::tempStart(); + } + + void setRRCTapCount(int rrcTapCount) { + setRRCParams(rrcTapCount, _rrcBeta); + } + + void setRRCBeta(int rrcBeta) { + setRRCParams(_rrcTapCount, rrcBeta); + } + + void setAGCRate(double agcRate) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + agc.setRate(agcRate); + } + + void setCostasBandwidth(double bandwidth) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + costas.setBandwidth(bandwidth); + } + + void setMMParams(double omegaGain, double muGain, double omegaRelLimit = 0.01) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + recov.setOmegaGain(omegaGain); + recov.setMuGain(muGain); + recov.setOmegaRelLimit(omegaRelLimit); + } + + void setOmegaGain(double omegaGain) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + recov.setOmegaGain(omegaGain); + } + + void setMuGain(double muGain) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + recov.setMuGain(muGain); + } + + void setOmegaRelLimit(double omegaRelLimit) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + recov.setOmegaRelLimit(omegaRelLimit); + } + + void setBrokenModulation(bool enabled) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + costas.setBrokenModulation(enabled); + } + + void reset() { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + base_type::tempStop(); + rrc.reset(); + agc.reset(); + costas.reset(); + recov.reset(); + base_type::tempStart(); + } + + inline int process(int count, const complex_t* in, complex_t* out) { + rrc.process(count, in, out); + agc.process(count, out, out); + costas.process(count, out, out); + return recov.process(count, out, out); + } + + int run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf); + + // Swap if some data was generated + base_type::_in->flush(); + if (outCount) { + if (!base_type::out.swap(outCount)) { return -1; } + } + return outCount; + } + + protected: + double _symbolrate; + double _samplerate; + int _rrcTapCount; + double _rrcBeta; + + tap rrcTaps; + filter::FIR rrc; + loop::FastAGC agc; + loop::MeteorCostas costas; + clock_recovery::MM recov; + }; +} \ No newline at end of file