diff --git a/.gitignore b/.gitignore index baeaf4bc..5c157ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ build/ root_dev/ Folder.DotSettings.user CMakeSettings.json -poggers_decoder \ No newline at end of file +poggers_decoder +m17_decoder/libcorrect \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 46db9f2b..6d1e028e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Depend # Decoders option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) +option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (no dependencies required)" OFF) option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON) option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON) option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" ON) @@ -133,6 +134,10 @@ if (OPT_BUILD_FALCON9_DECODER) add_subdirectory("falcon9_decoder") endif (OPT_BUILD_FALCON9_DECODER) +if (OPT_BUILD_M17_DECODER) +add_subdirectory("m17_decoder") +endif (OPT_BUILD_M17_DECODER) + if (OPT_BUILD_METEOR_DEMODULATOR) add_subdirectory("meteor_demodulator") endif (OPT_BUILD_METEOR_DEMODULATOR) @@ -191,7 +196,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_custom_target(do_always ALL cp \"$/libsdrpp_core.dylib\" \"$\") endif () -# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON +# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON # Install directives install(TARGETS sdrpp DESTINATION bin) diff --git a/core/src/dsp/correction.h b/core/src/dsp/correction.h index d785a320..133fe75a 100644 --- a/core/src/dsp/correction.h +++ b/core/src/dsp/correction.h @@ -73,4 +73,74 @@ namespace dsp { }; + + class DCBlocker : public generic_block { + public: + DCBlocker() {} + + DCBlocker(stream* in, float rate) { init(in, rate); } + + void init(stream* in, float rate) { + _in = in; + correctionRate = rate; + offset = 0; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + void setCorrectionRate(float rate) { + correctionRate = rate; + } + + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + if (bypass) { + memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t)); + + _in->flush(); + + if (!out.swap(count)) { return -1; } + + return count; + } + + for (int i = 0; i < count; i++) { + out.writeBuf[i] = _in->readBuf[i] - offset; + offset = offset + (out.writeBuf[i] * correctionRate); + } + + _in->flush(); + + if (!out.swap(count)) { return -1; } + + return count; + } + + stream out; + + // TEMPORARY FOR DEBUG PURPOSES + bool bypass = false; + float offset; + + private: + stream* _in; + float correctionRate = 0.00001; + + + }; + + } \ No newline at end of file diff --git a/core/src/dsp/demodulator.h b/core/src/dsp/demodulator.h index 2115c838..25f6f865 100644 --- a/core/src/dsp/demodulator.h +++ b/core/src/dsp/demodulator.h @@ -11,6 +11,7 @@ #include #include #include +#include #define FAST_ATAN2_COEF1 FL_M_PI / 4.0f #define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1 @@ -371,11 +372,11 @@ namespace dsp { }; - class MSKDemod : public generic_hier_block { + class FSKDemod : public generic_hier_block { public: - MSKDemod() {} + FSKDemod() {} - MSKDemod(stream* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + FSKDemod(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, omegaGain, muGain, omegaRelLimit); } @@ -391,47 +392,47 @@ namespace dsp { recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit); out = &recov.out; - generic_hier_block::registerBlock(&demod); - generic_hier_block::registerBlock(&recov); - generic_hier_block::_block_init = true; + generic_hier_block::registerBlock(&demod); + generic_hier_block::registerBlock(&recov); + generic_hier_block::_block_init = true; } void setInput(stream* input) { - assert((generic_hier_block::_block_init)); + assert((generic_hier_block::_block_init)); demod.setInput(input); } void setSampleRate(float sampleRate) { - assert(generic_hier_block::_block_init); - generic_hier_block::tempStop(); + assert(generic_hier_block::_block_init); + generic_hier_block::tempStop(); _sampleRate = sampleRate; demod.setSampleRate(_sampleRate); recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); - generic_hier_block::tempStart(); + generic_hier_block::tempStart(); } void setDeviation(float deviation) { - assert(generic_hier_block::_block_init); + assert(generic_hier_block::_block_init); _deviation = deviation; demod.setDeviation(deviation); } void setBaudRate(float baudRate, float omegaRelLimit) { - assert(generic_hier_block::_block_init); + assert(generic_hier_block::_block_init); _baudRate = baudRate; _omegaRelLimit = omegaRelLimit; recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); } void setMMGains(float omegaGain, float myGain) { - assert(generic_hier_block::_block_init); + assert(generic_hier_block::_block_init); _omegaGain = omegaGain; _muGain = myGain; recov.setGains(_omegaGain, _muGain); } void setOmegaRelLimit(float omegaRelLimit) { - assert(generic_hier_block::_block_init); + assert(generic_hier_block::_block_init); _omegaRelLimit = omegaRelLimit; recov.setOmegaRelLimit(_omegaRelLimit); } @@ -450,6 +451,105 @@ namespace dsp { float _omegaRelLimit; }; + class GFSKDemod : public generic_hier_block { + public: + GFSKDemod() {} + + GFSKDemod(stream* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + init(input, sampleRate, deviation, rrcAlpha, baudRate, omegaGain, muGain, omegaRelLimit); + } + + void init(stream* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) { + _sampleRate = sampleRate; + _deviation = deviation; + _rrcAlpha = rrcAlpha; + _baudRate = baudRate; + _omegaGain = omegaGain; + _muGain = muGain; + _omegaRelLimit = omegaRelLimit; + + demod.init(input, _sampleRate, _deviation); + rrc.init(31, _sampleRate, _baudRate, _rrcAlpha); + fir.init(&demod.out, &rrc); + recov.init(&fir.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit); + out = &recov.out; + + generic_hier_block::registerBlock(&demod); + generic_hier_block::registerBlock(&fir); + generic_hier_block::registerBlock(&recov); + generic_hier_block::_block_init = true; + } + + void setInput(stream* input) { + assert((generic_hier_block::_block_init)); + demod.setInput(input); + } + + void setSampleRate(float sampleRate) { + assert(generic_hier_block::_block_init); + generic_hier_block::tempStop(); + _sampleRate = sampleRate; + demod.setSampleRate(_sampleRate); + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + rrc.setSampleRate(_sampleRate); + fir.updateWindow(&rrc); + generic_hier_block::tempStart(); + } + + void setDeviation(float deviation) { + assert(generic_hier_block::_block_init); + _deviation = deviation; + demod.setDeviation(deviation); + } + + void setRRCAlpha(float rrcAlpha) { + assert(generic_hier_block::_block_init); + _rrcAlpha = rrcAlpha; + rrc.setAlpha(_rrcAlpha); + fir.updateWindow(&rrc); + } + + void setBaudRate(float baudRate, float omegaRelLimit) { + assert(generic_hier_block::_block_init); + _baudRate = baudRate; + _omegaRelLimit = omegaRelLimit; + generic_hier_block::tempStop(); + recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit); + rrc.setBaudRate(_baudRate); + fir.updateWindow(&rrc); + generic_hier_block::tempStart(); + } + + void setMMGains(float omegaGain, float myGain) { + assert(generic_hier_block::_block_init); + _omegaGain = omegaGain; + _muGain = myGain; + recov.setGains(_omegaGain, _muGain); + } + + void setOmegaRelLimit(float omegaRelLimit) { + assert(generic_hier_block::_block_init); + _omegaRelLimit = omegaRelLimit; + recov.setOmegaRelLimit(_omegaRelLimit); + } + + stream* out = NULL; + + private: + FloatFMDemod demod; + RRCTaps rrc; + FIR fir; + MMClockRecovery recov; + + float _sampleRate; + float _deviation; + float _rrcAlpha; + float _baudRate; + float _omegaGain; + float _muGain; + float _omegaRelLimit; + }; + template class PSKDemod : public generic_hier_block> { public: diff --git a/core/src/dsp/routing.h b/core/src/dsp/routing.h index 4a986a6a..58f5e021 100644 --- a/core/src/dsp/routing.h +++ b/core/src/dsp/routing.h @@ -65,6 +65,54 @@ namespace dsp { }; + template + class StreamDoubler : public generic_block> { + public: + StreamDoubler() {} + + StreamDoubler(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&outA); + generic_block::registerOutput(&outB); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + std::lock_guard lck(generic_block::ctrlMtx); + generic_block::tempStop(); + generic_block::unregisterInput(_in); + _in = in; + generic_block::registerInput(_in); + generic_block::tempStart(); + } + + stream outA; + stream outB; + + private: + int run() { + int count = _in->read(); + if (count < 0) { return -1; } + + memcpy(outA.writeBuf, _in->readBuf, count * sizeof(T)); + memcpy(outB.writeBuf, _in->readBuf, count * sizeof(T)); + + _in->flush(); + + if (!outA.swap(count)) { return -1; } + if (!outB.swap(count)) { return -1; } + + return count; + } + + stream* _in; + + }; + // NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works... template diff --git a/m17_decoder/CMakeLists.txt b/m17_decoder/CMakeLists.txt new file mode 100644 index 00000000..b5fd29a7 --- /dev/null +++ b/m17_decoder/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.13) +project(m17_decoder) + +add_subdirectory("libcorrect") + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") +else () + set(CMAKE_CXX_FLAGS "-O3 -std=c++17") +endif () + +file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") + +include_directories("src/") +include_directories("libcorrect/include") + +include_directories("C:/Users/ryzerth/Documents/Code/codec2/src") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") + +add_library(m17_decoder SHARED ${SRC}) +target_link_libraries(m17_decoder PRIVATE sdrpp_core) +target_link_libraries(m17_decoder PRIVATE correct_static) +set_target_properties(m17_decoder PROPERTIES PREFIX "") + +target_link_directories(sdrpp_core PUBLIC "C:/Users/ryzerth/Documents/Code/codec2/build/src") +target_link_libraries(m17_decoder PRIVATE libcodec2) + +# Install directives +install(TARGETS m17_decoder DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/m17_decoder/src/m17dsp.h b/m17_decoder/src/m17dsp.h new file mode 100644 index 00000000..ceebc053 --- /dev/null +++ b/m17_decoder/src/m17dsp.h @@ -0,0 +1,482 @@ +#pragma once +#include +#include +#include +#include + +extern "C" { + #include +} + +#define M17_DEVIATION 2400.0f +#define M17_BAUDRATE 4800.0f +#define M17_RRC_ALPHA 0.5f +#define M17_4FSK_HIGH_CUT 0.52f + +#define M17_SYNC_SIZE 16 +#define M17_LICH_SIZE 96 +#define M17_PAYLOAD_SIZE 144 +#define M17_ENCODED_PAYLOAD_SIZE 296 +#define M17_RAW_FRAME_SIZE 384 +#define M17_CUT_FRAME_SIZE 368 + +const uint8_t M17_LSF_SYNC[16] = { 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1 }; +const uint8_t M17_STF_SYNC[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1 }; +const uint8_t M17_PKF_SYNC[16] = { 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + +const uint8_t M17_SCRAMBLER[368] = { 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, + 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, + 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, + 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 }; + +const uint16_t M17_INTERLEAVER[368] = { 0, 137, 90, 227, 180, 317, 270, 39, 360, 129, 82, 219, 172, 309, 262, 31, + 352, 121, 74, 211, 164, 301, 254, 23, 344, 113, 66, 203, 156, 293, 246, 15, + 336, 105, 58, 195, 148, 285, 238, 7, 328, 97, 50, 187, 140, 277, 230, 367, + 320, 89, 42, 179, 132, 269, 222, 359, 312, 81, 34, 171, 124, 261, 214, 351, + 304, 73, 26, 163, 116, 253, 206, 343, 296, 65, 18, 155, 108, 245, 198, 335, + 288, 57, 10, 147, 100, 237, 190, 327, 280, 49, 2, 139, 92, 229, 182, 319, + 272, 41, 362, 131, 84, 221, 174, 311, 264, 33, 354, 123, 76, 213, 166, 303, + 256, 25, 346, 115, 68, 205, 158, 295, 248, 17, 338, 107, 60, 197, 150, 287, + 240, 9, 330, 99, 52, 189, 142, 279, 232, 1, 322, 91, 44, 181, 134, 271, + 224, 361, 314, 83, 36, 173, 126, 263, 216, 353, 306, 75, 28, 165, 118, 255, + 208, 345, 298, 67, 20, 157, 110, 247, 200, 337, 290, 59, 12, 149, 102, 239, + 192, 329, 282, 51, 4, 141, 94, 231, 184, 321, 274, 43, 364, 133, 86, 223, + 176, 313, 266, 35, 356, 125, 78, 215, 168, 305, 258, 27, 348, 117, 70, 207, + 160, 297, 250, 19, 340, 109, 62, 199, 152, 289, 242, 11, 332, 101, 54, 191, + 144, 281, 234, 3, 324, 93, 46, 183, 136, 273, 226, 363, 316, 85, 38, 175, + 128, 265, 218, 355, 308, 77, 30, 167, 120, 257, 210, 347, 300, 69, 22, 159, + 112, 249, 202, 339, 292, 61, 14, 151, 104, 241, 194, 331, 284, 53, 6, 143, + 96, 233, 186, 323, 276, 45, 366, 135, 88, 225, 178, 315, 268, 37, 358, 127, + 80, 217, 170, 307, 260, 29, 350, 119, 72, 209, 162, 299, 252, 21, 342, 111, + 64, 201, 154, 291, 244, 13, 334, 103, 56, 193, 146, 283, 236, 5, 326, 95, + 48, 185, 138, 275, 228, 365, 318, 87, 40, 177, 130, 267, 220, 357, 310, 79, + 32, 169, 122, 259, 212, 349, 302, 71, 24, 161, 114, 251, 204, 341, 294, 63, + 16, 153, 106, 243, 196, 333, 286, 55, 8, 145, 98, 235, 188, 325, 278, 47 }; + +const uint8_t M17_PUNCTURING_P1[61] = { 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 }; + +const uint8_t M17_PUNCTURING_P2[12] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }; + +static const correct_convolutional_polynomial_t correct_conv_m17_polynomial[] = {0b11001, 0b10111}; + +namespace dsp { + class M17Slice4FSK : public generic_block { + public: + M17Slice4FSK() {} + + M17Slice4FSK(stream* in) { init(in); } + + void init(stream* in) { + _in = in; + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + 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; } + + float val; + for (int i = 0; i < count; i++) { + val = _in->readBuf[i]; + out.writeBuf[i*2] = (val < 0.0f); + out.writeBuf[(i*2)+1] = (fabsf(val) > M17_4FSK_HIGH_CUT); + } + + _in->flush(); + + if (!out.swap(count * 2)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + }; + + class M17FrameDemux : public generic_block { + public: + M17FrameDemux() {} + + M17FrameDemux(stream* in) { init(in); } + + ~M17FrameDemux() { + if (!generic_block::_block_init) { return; } + generic_block::stop(); + delete[] delay; + } + + void init(stream* in) { + _in = in; + + delay = new uint8_t[STREAM_BUFFER_SIZE]; + + generic_block::registerInput(_in); + generic_block::registerOutput(&linkSetupOut); + generic_block::registerOutput(&lichOut); + generic_block::registerOutput(&streamOut); + generic_block::registerOutput(&packetOut); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + 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; } + + memcpy(&delay[M17_SYNC_SIZE], _in->readBuf, count); + + for (int i = 0; i < count;) { + if (detect) { + if (outCount < M17_SYNC_SIZE) { outCount++; i++; } + else { + int id = M17_INTERLEAVER[outCount - M17_SYNC_SIZE]; + + if (type == 0) { + linkSetupOut.writeBuf[id] = delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]; + } + else if (type == 1 && id < M17_LICH_SIZE) { + lichOut.writeBuf[id/8] |= (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]) << (7 - (id%8)); + } + else if (type == 1) { + streamOut.writeBuf[id - M17_LICH_SIZE] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]); + } + else if (type == 2) { + packetOut.writeBuf[id] = (delay[i++] ^ M17_SCRAMBLER[outCount - M17_SYNC_SIZE]); + } + + outCount++; + } + + if (outCount >= M17_RAW_FRAME_SIZE) { + detect = false; + if (type == 0) { + if (!linkSetupOut.swap(M17_CUT_FRAME_SIZE)) { return -1; } + } + else if (type == 1) { + if (!lichOut.swap(12)) {return -1; } + if (!streamOut.swap(M17_CUT_FRAME_SIZE)) {return -1; } + } + else if (type == 2) { + if (!packetOut.swap(M17_CUT_FRAME_SIZE)) { return -1; } + } + } + continue; + } + + // Check for link setup syncword + if (!memcmp(&delay[i], M17_LSF_SYNC, M17_SYNC_SIZE)) { + detect = true; + outCount = 0; + type = 0; + spdlog::warn("Found sync frame"); + continue; + } + + // Check for stream syncword + if (!memcmp(&delay[i], M17_STF_SYNC, M17_SYNC_SIZE)) { + detect = true; + outCount = 0; + type = 1; + memset(lichOut.writeBuf, 0, 12); + spdlog::warn("Found stream frame"); + continue; + } + + // Check for packet syncword + if (!memcmp(&delay[i], M17_PKF_SYNC, M17_SYNC_SIZE)) { + detect = true; + outCount = 0; + type = 2; + spdlog::warn("Found packet frame"); + continue; + } + + i++; + } + + memmove(delay, &delay[count], 16); + + _in->flush(); + + return count; + } + + stream linkSetupOut; + stream lichOut; + stream streamOut; + stream packetOut; + + private: + stream* _in; + + uint8_t* delay; + + bool detect = false; + int type; + + int outCount = 0; + + }; + + class M17PayloadFEC : public generic_block { + public: + M17PayloadFEC() {} + + M17PayloadFEC(stream* in) { init(in); } + + ~M17PayloadFEC() { + if (!generic_block::_block_init) { return; } + generic_block::stop(); + correct_convolutional_destroy(conv); + } + + void init(stream* in) { + _in = in; + + conv = correct_convolutional_create(2, 5, correct_conv_m17_polynomial); + + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + 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; } + + // Depuncture the data + int inOffset = 0; + for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) { + if (!M17_PUNCTURING_P2[i % 12]) { + depunctured[i] = 0; + continue; + } + depunctured[i] = _in->readBuf[inOffset++]; + } + + // Pack into bytes + for (int i = 0; i < M17_ENCODED_PAYLOAD_SIZE; i++) { + if (!(i%8)) { packed[i/8] = 0; } + packed[i/8] |= depunctured[i] << (7 - (i%8)); + } + + // Run through convolutional decoder + correct_convolutional_decode(conv, packed, M17_ENCODED_PAYLOAD_SIZE, out.writeBuf); + + _in->flush(); + + if (!out.swap(M17_PAYLOAD_SIZE / 8)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + uint8_t depunctured[296]; + uint8_t packed[37]; + + correct_convolutional* conv; + + }; + + class M17Codec2Decode : public generic_block { + public: + M17Codec2Decode() {} + + M17Codec2Decode(stream* in) { init(in); } + + ~M17Codec2Decode() { + if (!generic_block::_block_init) { return; } + generic_block::stop(); + codec2_destroy(codec); + delete[] int16Audio; + delete[] floatAudio; + } + + void init(stream* in) { + _in = in; + + codec = codec2_create(CODEC2_MODE_3200); + sampsPerC2Frame = codec2_samples_per_frame(codec); + sampsPerC2FrameDouble = sampsPerC2Frame * 2; + int16Audio = new int16_t[sampsPerC2FrameDouble]; + floatAudio = new float[sampsPerC2FrameDouble]; + + generic_block::registerInput(_in); + generic_block::registerOutput(&out); + generic_block::_block_init = true; + } + + void setInput(stream* in) { + assert(generic_block::_block_init); + 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; } + + // Decode both parts using codec + codec2_decode(codec, int16Audio, &_in->readBuf[2]); + codec2_decode(codec, &int16Audio[sampsPerC2Frame], &_in->readBuf[2+8]); + + // Convert to float + volk_16i_s32f_convert_32f(floatAudio, int16Audio, 32768.0f, sampsPerC2FrameDouble); + + // Interleave into stereo samples + volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, floatAudio, floatAudio, sampsPerC2FrameDouble); + + _in->flush(); + + if (!out.swap(sampsPerC2FrameDouble)) { return -1; } + return count; + } + + stream out; + + private: + stream* _in; + + int16_t* int16Audio; + float* floatAudio; + + CODEC2* codec; + int sampsPerC2Frame = 0; + int sampsPerC2FrameDouble = 0; + + }; + + class M17Decoder : public generic_hier_block { + public: + M17Decoder() {} + + M17Decoder(stream* input, float sampleRate) { + init(input, sampleRate); + } + + void init(stream* input, float sampleRate) { + _sampleRate = sampleRate; + + demod.init(input, _sampleRate, M17_DEVIATION); + rrc.init(31, _sampleRate, M17_BAUDRATE, M17_RRC_ALPHA); + fir.init(&demod.out, &rrc); + recov.init(&fir.out, _sampleRate / M17_BAUDRATE, 1e-6f, 0.01f, 0.01f); + doubler.init(&recov.out); + slice.init(&doubler.outA); + demux.init(&slice.out); + payloadFEC.init(&demux.streamOut); + decodeAudio.init(&payloadFEC.out); + + ns0.init(&demux.linkSetupOut); + ns1.init(&demux.lichOut); + ns2.init(&demux.packetOut); + + diagOut = &doubler.outB; + out = &decodeAudio.out; + + generic_hier_block::registerBlock(&demod); + generic_hier_block::registerBlock(&fir); + generic_hier_block::registerBlock(&recov); + generic_hier_block::registerBlock(&doubler); + generic_hier_block::registerBlock(&slice); + generic_hier_block::registerBlock(&demux); + generic_hier_block::registerBlock(&payloadFEC); + generic_hier_block::registerBlock(&decodeAudio); + + generic_hier_block::registerBlock(&ns0); + generic_hier_block::registerBlock(&ns1); + generic_hier_block::registerBlock(&ns2); + + generic_hier_block::_block_init = true; + } + + void setInput(stream* input) { + assert(generic_hier_block::_block_init); + demod.setInput(input); + } + + stream* diagOut = NULL; + stream* out = NULL; + + private: + FloatFMDemod demod; + RRCTaps rrc; + FIR fir; + MMClockRecovery recov; + StreamDoubler doubler; + M17Slice4FSK slice; + M17FrameDemux demux; + M17PayloadFEC payloadFEC; + M17Codec2Decode decodeAudio; + + NullSink ns0; + NullSink ns1; + NullSink ns2; + + + float _sampleRate; + + }; +} \ No newline at end of file diff --git a/m17_decoder/src/main.cpp b/m17_decoder/src/main.cpp new file mode 100644 index 00000000..4454cd9d --- /dev/null +++ b/m17_decoder/src/main.cpp @@ -0,0 +1,196 @@ +#include +#include +#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: */ "m17_decoder", + /* Description: */ "M17 Digital Voice Decoder for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ -1 +}; + +ConfigManager config; + +#define INPUT_SAMPLE_RATE 14400 + +class M17DecoderModule : public ModuleManager::Instance { +public: + M17DecoderModule(std::string name) : diag(1.0f, 480) { + this->name = name; + + // Load config + + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 9600, INPUT_SAMPLE_RATE, 9600, 9600, true); + vfo->setSnapInterval(500); + + // Intialize DSP here + decoder.init(vfo->output, INPUT_SAMPLE_RATE); + + resampWin.init(4000, 4000, audioSampRate); + resamp.init(decoder.out, &resampWin, 8000, audioSampRate); + resampWin.setSampleRate(8000 * resamp.getInterpolation()); + resamp.updateWindow(&resampWin); + + reshape.init(decoder.diagOut, 480, 0); + diagHandler.init(&reshape.out, _diagHandler, this); + + // Start DSO Here + decoder.start(); + resamp.start(); + reshape.start(); + diagHandler.start(); + + // Setup audio stream + srChangeHandler.ctx = this; + srChangeHandler.handler = sampleRateChangeHandler; + stream.init(&resamp.out, &srChangeHandler, audioSampRate); + sigpath::sinkManager.registerStream(name, &stream); + + stream.start(); + + gui::menu.registerEntry(name, menuHandler, this, this); + } + + ~M17DecoderModule() { + // Stop DSP Here + decoder.stop(); + resamp.stop(); + reshape.stop(); + diagHandler.stop(); + + stream.stop(); + + sigpath::vfoManager.deleteVFO(vfo); + gui::menu.removeEntry(name); + } + + void postInit() {} + + void enable() { + double bw = gui::waterfall.getBandwidth(); + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp(0, -bw/2.0, bw/2.0), 9600, INPUT_SAMPLE_RATE, 9600, 9600, true); + vfo->setSnapInterval(500); + + // Set Input of demod here + decoder.setInput(vfo->output); + + // Start DSP here + decoder.start(); + resamp.start(); + reshape.start(); + diagHandler.start(); + + enabled = true; + } + + void disable() { + // Stop DSP here + decoder.stop(); + resamp.stop(); + reshape.stop(); + diagHandler.stop(); + + sigpath::vfoManager.deleteVFO(vfo); + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + static void menuHandler(void* ctx) { + M17DecoderModule* _this = (M17DecoderModule*)ctx; + + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + if (!_this->enabled) { style::beginDisabled(); } + + ImGui::SetNextItemWidth(menuWidth); + _this->diag.draw(); + + if (!_this->enabled) { style::endDisabled(); } + } + + static void _diagHandler(float* data, int count, void* ctx) { + M17DecoderModule* _this = (M17DecoderModule*)ctx; + float* buf = _this->diag.acquireBuffer(); + memcpy(buf, data, count * sizeof(float)); + _this->diag.releaseBuffer(); + } + + static void sampleRateChangeHandler(float sampleRate, void* ctx) { + M17DecoderModule* _this = (M17DecoderModule*)ctx; + // TODO: If too slow, change all demods here and not when setting + _this->audioSampRate = sampleRate; + _this->resampWin.setCutoff(std::min(sampleRate/2, 4000)); + _this->resamp.tempStop(); + _this->resamp.setOutSampleRate(sampleRate); + _this->resampWin.setSampleRate(8000 * _this->resamp.getInterpolation()); + _this->resamp.updateWindow(&_this->resampWin); + _this->resamp.tempStart(); + } + + std::string name; + bool enabled = true; + + // DSP Chain + VFOManager::VFO* vfo; + + dsp::M17Decoder decoder; + + dsp::Reshaper reshape; + dsp::HandlerSink diagHandler; + + dsp::filter_window::BlackmanWindow resampWin; + dsp::PolyphaseResampler resamp; + + ImGui::SymbolDiagram diag; + + double audioSampRate = 48000; + EventHandler srChangeHandler; + SinkManager::Stream stream; + +}; + +MOD_EXPORT void _INIT_() { + // Create default recording directory + json def = json({}); + config.setPath(options::opts.root + "/m17_decoder_config.json"); + config.load(def); + config.enableAutoSave(); +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new M17DecoderModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (M17DecoderModule*)instance; +} + +MOD_EXPORT void _END_() { + config.disableAutoSave(); + config.save(); +} \ No newline at end of file diff --git a/readme.md b/readme.md index 4002d1a9..163c6046 100644 --- a/readme.md +++ b/readme.md @@ -295,6 +295,7 @@ Modules in beta are still included in releases for the most part but not enabled | Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default | |---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:| | falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ | +| m17_decoder | Unfinished | - | OPT_BUILD_M17_DECODER | ⛔ | ⛔ | ⛔ | | meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ | | radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ | | weather_sat_decoder | Unfinished | - | OPT_BUILD_WEATHER_SAT_DECODER | ⛔ | ⛔ | ⛔ |