mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-11-10 04:37:37 +01:00
Bugfix + added meteor demodulator
This commit is contained in:
parent
bcc1744a76
commit
a9cb6bfe79
@ -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()
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#define FL_M_PI 3.1415926535f
|
||||
|
||||
namespace dsp {
|
||||
|
||||
class generic_unnamed_block {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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>> {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
}
|
43
core/src/gui/widgets/constellation_diagram.cpp
Normal file
43
core/src/gui/widgets/constellation_diagram.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
25
core/src/gui/widgets/constellation_diagram.h
Normal file
25
core/src/gui/widgets/constellation_diagram.h
Normal 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];
|
||||
|
||||
};
|
||||
}
|
22
meteor_demodulator/CMakeLists.txt
Normal file
22
meteor_demodulator/CMakeLists.txt
Normal 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)
|
211
meteor_demodulator/src/main.cpp
Normal file
211
meteor_demodulator/src/main.cpp
Normal 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
|
||||
}
|
@ -146,6 +146,7 @@ private:
|
||||
bw = bandWidth;
|
||||
_vfo->setBandwidth(bw);
|
||||
demod.setDeviation(bw / 2.0f);
|
||||
setAudioSampleRate(audioSampRate);
|
||||
}
|
||||
|
||||
void setSnapInterval(float snapInt) {
|
||||
|
Loading…
Reference in New Issue
Block a user