Bugfix + added meteor demodulator

This commit is contained in:
Ryzerth 2021-04-01 16:54:16 +02:00
parent bcc1744a76
commit a9cb6bfe79
13 changed files with 789 additions and 31 deletions

View File

@ -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()

View File

@ -8,8 +8,6 @@
#include <spdlog/spdlog.h>
#define FL_M_PI 3.1415926535f
namespace dsp {
class generic_unnamed_block {

View File

@ -88,6 +88,30 @@ namespace dsp {
generic_block<MMClockRecovery<T>>::registerOutput(&out);
}
void setOmega(float omega, float omegaRelLimit) {
generic_block<MMClockRecovery<T>>::tempStop();
omegaMin = _omega - (_omega * _omegaRelLimit);
omegaMax = _omega + (_omega * _omegaRelLimit);
_omega = omega;
_dynOmega = _omega;
generic_block<MMClockRecovery<T>>::tempStart();
}
void setGains(float omegaGain, float muGain) {
generic_block<MMClockRecovery<T>>::tempStop();
_gainOmega = omegaGain;
_muGain = muGain;
generic_block<MMClockRecovery<T>>::tempStart();
}
void setOmegaRelLimit(float omegaRelLimit) {
generic_block<MMClockRecovery<T>>::tempStop();
_omegaRelLimit = omegaRelLimit;
omegaMin = _omega - (_omega * _omegaRelLimit);
omegaMax = _omega + (_omega * _omegaRelLimit);
generic_block<MMClockRecovery<T>>::tempStart();
}
void setInput(stream<T>* in) {
generic_block<MMClockRecovery<T>>::tempStop();
generic_block<MMClockRecovery<T>>::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

View File

@ -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<MSKDemod> {
public:
MSKDemod() {}
MSKDemod(stream<complex_t>* input, float sampleRate, float deviation, float baudRate) { init(input, sampleRate, deviation, baudRate); }
MSKDemod(stream<complex_t>* 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<complex_t>* 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<complex_t>* 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<MSKDemod>::registerBlock(&demod);
generic_hier_block<MSKDemod>::registerBlock(&recov);
}
void setSampleRate(float sampleRate) {
generic_hier_block<MSKDemod>::tempStop();
_sampleRate = sampleRate;
demod.setSampleRate(_sampleRate);
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
generic_hier_block<MSKDemod>::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<float>* out = NULL;
private:
FloatFMDemod demod;
MMClockRecovery<float> recov;
float _sampleRate;
float _deviation;
float _baudRate;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
template<int ORDER, bool OFFSET>
class PSKDemod : public generic_hier_block<PSKDemod<ORDER, OFFSET>> {
public:
PSKDemod() {}
PSKDemod(stream<complex_t>* 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<complex_t>* 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<PSKDemod<ORDER, OFFSET>>::registerBlock(&agc);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&rrc);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&demod);
if constexpr (OFFSET) {
delay.init(&demod.out);
recov.init(&delay.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&delay);
}
else {
recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
}
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&recov);
out = &recov.out;
}
void setInput(stream<complex_t>* 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<complex_t>* out = NULL;
private:
dsp::ComplexAGC agc;
dsp::RRCTaps taps;
dsp::FIR<dsp::complex_t> rrc;
CostasLoop<ORDER> demod;
DelayImag delay;
MMClockRecovery<dsp::complex_t> recov;
int _RRCTapCount;
float _RRCAlpha;
float _sampleRate;
float _agcRate;
float _baudRate;
float _costasLoopBw;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
}

View File

@ -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);
}

View File

@ -151,8 +151,14 @@ namespace dsp {
FeedForwardAGC(stream<T>* in) { init(in); }
~FeedForwardAGC() {
generic_block<FeedForwardAGC<T>>::stop();
delete[] buffer;
}
void init(stream<T>* in) {
_in = in;
buffer = new T[STREAM_BUFFER_SIZE];
generic_block<FeedForwardAGC<T>>::registerInput(_in);
generic_block<FeedForwardAGC<T>>::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<T, float>) {
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<T, complex_t>) {
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<T> out;
private:
T* buffer;
int inBuffer = 0;
int sampleCount = 1024;
stream<T>* _in;
};
class ComplexAGC : public generic_block<ComplexAGC> {
public:
ComplexAGC() {}
ComplexAGC(stream<complex_t>* in, float setPoint, float maxGain, float rate) { init(in, setPoint, maxGain, rate); }
void init(stream<complex_t>* in, float setPoint, float maxGain, float rate) {
_in = in;
_setPoint = setPoint;
_maxGain = maxGain;
_rate = rate;
generic_block<ComplexAGC>::registerInput(_in);
generic_block<ComplexAGC>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexAGC>::ctrlMtx);
generic_block<ComplexAGC>::tempStop();
generic_block<ComplexAGC>::unregisterInput(_in);
_in = in;
generic_block<ComplexAGC>::registerInput(_in);
generic_block<ComplexAGC>::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<T> out;
stream<complex_t> out;
private:
stream<T>* _in;
float _gain = 1.0f;
float _setPoint = 1.0f;
float _maxGain = 10e4;
float _rate = 10e-4;
stream<complex_t>* _in;
};
class DelayImag : public generic_block<DelayImag> {
public:
DelayImag() {}
DelayImag(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<DelayImag>::registerInput(_in);
generic_block<DelayImag>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<DelayImag>::ctrlMtx);
generic_block<DelayImag>::tempStop();
generic_block<DelayImag>::unregisterInput(_in);
_in = in;
generic_block<DelayImag>::registerInput(_in);
generic_block<DelayImag>::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<complex_t> out;
private:
float lastIm = 0.0f;
stream<complex_t>* _in;
};
template <class T>
class Volume : public generic_block<Volume<T>> {

View File

@ -1,4 +1,7 @@
#pragma once
#include <math.h>
#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;
};

View File

@ -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;
};
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <gui/widgets/constellation_diagram.h>
namespace ImGui {
ConstellationDiagram::ConstellationDiagram() {
memset(buffer, 0, 1024 * sizeof(dsp::complex_t));
}
void ConstellationDiagram::draw(const ImVec2& size_arg) {
std::lock_guard<std::mutex> 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();
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <dsp/stream.h>
#include <mutex>
#include <dsp/types.h>
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];
};
}

View File

@ -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)

View File

@ -0,0 +1,211 @@
#include <imgui.h>
#include <watcher.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h>
#include <fstream>
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<dsp::complex_t> split;
dsp::stream<dsp::complex_t> symSinkStream;
dsp::stream<dsp::complex_t> sinkStream;
dsp::Reshaper<dsp::complex_t> reshape;
dsp::HandlerSink<dsp::complex_t> symSink;
dsp::HandlerSink<dsp::complex_t> 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
}

View File

@ -146,6 +146,7 @@ private:
bw = bandWidth;
_vfo->setBandwidth(bw);
demod.setDeviation(bw / 2.0f);
setAudioSampleRate(audioSampRate);
}
void setSnapInterval(float snapInt) {