diff --git a/README.md b/README.md index 7abb54b..9ab1641 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ stream from STDIN and writes out an M17 4-FSK baseband stream at 48k SPS, This code requires the codec2-devel, boost-devel and gtest-devel packages be installed. -It also requires a modern C++17 compiler (GCC 8 minimum). +It also requires a modern C++20 compiler (GCC 8 minimum). ### Build Steps diff --git a/apps/m17-demod.cpp b/apps/m17-demod.cpp index 0dd7649..10db9fe 100644 --- a/apps/m17-demod.cpp +++ b/apps/m17-demod.cpp @@ -1,6 +1,7 @@ // Copyright 2020 Mobilinkd LLC. #include "M17Demodulator.h" +#include "M17BitDemodulator.h" #include "CRC16.h" #include "ax25_frame.h" #include "FirFilter.h" @@ -27,6 +28,9 @@ bool quiet = false; bool debug = false; bool noise_blanker = false; +enum class InputType {SYM, BIN, RRC}; +InputType inputType = InputType::RRC; + struct CODEC2 *codec2; std::vector current_packet; @@ -377,6 +381,9 @@ struct Config bool invert = false; bool lsf = false; bool noise_blanker = false; + bool bin = false; + bool sym = false; + bool rrc = true; // default is rrc static std::optional parse(int argc, char* argv[]) { @@ -393,6 +400,9 @@ struct Config ("invert,i", po::bool_switch(&result.invert), "invert the received baseband") ("noise-blanker,b", po::bool_switch(&result.noise_blanker), "noise blanker -- silence likely corrupt audio") ("lsf,l", po::bool_switch(&result.lsf), "display the decoded LSF") + ("bin,x", po::bool_switch(&result.bin), "input packed dibits (default is rrc).") + ("rrc,r", po::bool_switch(&result.rrc), "input rrc filtered and scaled symbols (default).") + ("sym,s", po::bool_switch(&result.sym), "input symbols (default is rrc).") ("verbose,v", po::bool_switch(&result.verbose), "verbose output") ("debug,d", po::bool_switch(&result.debug), "debug-level output") ("quiet,q", po::bool_switch(&result.quiet), "silence all output -- no BERT output") @@ -430,10 +440,17 @@ struct Config return std::nullopt; } + if (result.sym + result.bin + result.rrc > 1) + { + std::cerr << "Only one of sym, bin or rrc may be chosen." << std::endl; + return std::nullopt; + } + return result; } }; + int main(int argc, char* argv[]) { using namespace mobilinkd; @@ -448,6 +465,16 @@ int main(int argc, char* argv[]) debug = config->debug; noise_blanker = config->noise_blanker; + if (config->sym) { + inputType = InputType::SYM; + } + else if (config->bin) { + inputType = InputType::BIN; + } + else { + inputType = InputType::RRC; + } + codec2 = ::codec2_create(CODEC2_MODE_3200); using FloatType = float; @@ -483,10 +510,55 @@ int main(int argc, char* argv[]) while (std::cin) { - int16_t sample; - std::cin.read(reinterpret_cast(&sample), 2); - if (invert_input) sample *= -1; - demod(sample / 44000.0); + switch(inputType) + { + case InputType::SYM: + { + char c; + std::array symbol; // symbol to read in + std::array samples; // array to hold 10 samples + //std::cin.read(reinterpret_cast(&symbol), 1); + std::cin.read(reinterpret_cast(&c), 1); + symbol[0] = (int8_t)c; + samples = symbols_to_baseband(symbol); + for (int i = 0; i < 10; i++) + { + int16_t s = samples[i]; + if (invert_input) s *= -1; + demod(s / 44000.0); + } + } + break; + case InputType::BIN: + { + char c; + std::array packedSymbols; // dibit packed byte to read in + std::array symbols; // converted to 4 symbols + std::array samples; // array to hold 40 samples + //std::cin.read(reinterpret_cast(&packedSymbols), 1); + std::cin.read(reinterpret_cast(&c), 1); + packedSymbols[0] = (uint8_t)c; + symbols = bytes_to_symbols(packedSymbols); + samples = symbols_to_baseband(symbols); + for (int i = 0; i < 40; i++) + { + int16_t s = samples[i]; + if (invert_input) s *= -1; + demod(s / 44000.0); + } + } + break; + default: // InputType::RRC + { + int16_t sample; + std::cin.read(reinterpret_cast(&sample), 2); + if (invert_input) sample *= -1; + demod(sample / 44000.0); + } + break; + } + + } std::cerr << std::endl; diff --git a/apps/m17-mod.cpp b/apps/m17-mod.cpp index 2c1bd43..5e6eb96 100644 --- a/apps/m17-mod.cpp +++ b/apps/m17-mod.cpp @@ -52,7 +52,11 @@ struct Config bool verbose = false; bool debug = false; bool quiet = false; - bool bitstream = false; // default is baseband audio + + bool bin = false; + bool sym = false; + bool rrc = true; // default is rrc + bool bert = false; // Bit error rate testing. bool invert = false; int can = 10; @@ -81,11 +85,12 @@ struct Config "event device (default is C-Media Electronics Inc. USB Audio Device).") ("key,k", po::value(&result.key)->default_value(385), "Linux event code for PTT (default is RADIO).") - ("bitstream,b", po::bool_switch(&result.bitstream), - "output bitstream (default is baseband).") + ("bin,x", po::bool_switch(&result.bin), "output packed dibits (default is rrc).") + ("rrc,r", po::bool_switch(&result.rrc), "output rrc filtered and scaled symbols (default).") + ("sym,s", po::bool_switch(&result.sym), "output symbols (default is rrc).") ("bert,B", po::bool_switch(&result.bert), "output a bit error rate test stream (default is read audio from STDIN).") - ("invert,i", po::bool_switch(&result.invert), "invert the output baseband (ignored for bitstream)") + ("invert,i", po::bool_switch(&result.invert), "invert the output baseband (only for rrc)") ("verbose,v", po::bool_switch(&result.verbose), "verbose output") ("debug,d", po::bool_switch(&result.debug), "debug-level output") ("quiet,q", po::bool_switch(&result.quiet), "silence all output") @@ -140,6 +145,13 @@ struct Config return std::nullopt; } + if (result.sym + result.bin + result.rrc > 1) + { + std::cerr << "Only one of sym, bin or rrc may be chosen." << std::endl; + return std::nullopt; + } + + return result; } }; @@ -150,7 +162,9 @@ using lsf_t = std::array; std::atomic running{false}; -bool bitstream = false; +enum class OutputType {SYM, BIN, RRC}; + +OutputType outputType = OutputType::RRC; bool invert = false; int8_t can = 10; @@ -226,6 +240,7 @@ std::array symbols_to_baseband(std::array symbols) using bitstream_t = std::array; +// bin void output_bitstream(std::array sync_word, const bitstream_t& frame) { for (auto c : sync_word) std::cout << c; @@ -241,6 +256,20 @@ void output_bitstream(std::array sync_word, const bitstream_t& frame } } +// sym +void output_symbols(std::array sync_word, const bitstream_t& frame) +{ + auto symbols = bits_to_symbols(frame); + auto sw = bytes_to_symbols(sync_word); + + std::array temp; + auto fit = std::copy(sw.begin(), sw.end(), temp.begin()); + std::copy(symbols.begin(), symbols.end(), fit); + for (auto b : temp) std::cout << b; +} + + +// rrc void output_baseband(std::array sync_word, const bitstream_t& frame) { auto symbols = bits_to_symbols(frame); @@ -257,8 +286,18 @@ void output_baseband(std::array sync_word, const bitstream_t& frame) void output_frame(std::array sync_word, const bitstream_t& frame) { - if (bitstream) output_bitstream(sync_word, frame); - else output_baseband(sync_word, frame); + switch(outputType) + { + case OutputType::SYM: + output_symbols(sync_word, frame); + break; + case OutputType::BIN: + output_bitstream(sync_word, frame); + break; + default: // OutputType::RRC + output_baseband(sync_word, frame); + break; + } } void send_preamble() @@ -267,15 +306,23 @@ void send_preamble() std::cerr << "Sending preamble." << std::endl; std::array preamble_bytes; preamble_bytes.fill(0x77); - if (bitstream) - { - for (auto c : preamble_bytes) std::cout << c; - } - else // baseband - { - auto preamble_symbols = bytes_to_symbols(preamble_bytes); - auto preamble_baseband = symbols_to_baseband(preamble_symbols); - for (auto b : preamble_baseband) std::cout << uint8_t(b & 0xFF) << uint8_t(b >> 8); + switch(outputType) { + case OutputType::SYM: + { + auto preamble_symbols = bytes_to_symbols(preamble_bytes); + for (auto b : preamble_symbols) std::cout << b; + } + break; + case OutputType::BIN: + for (auto c : preamble_bytes) std::cout << c; + break; + default: + { // OutputType::RRC + auto preamble_symbols = bytes_to_symbols(preamble_bytes); + auto preamble_baseband = symbols_to_baseband(preamble_symbols); + for (auto b : preamble_baseband) std::cout << uint8_t(b & 0xFF) << uint8_t(b >> 8); + } + break; } } @@ -288,22 +335,36 @@ constexpr std::array EOT_SYNC = { 0x55, 0x5D }; void output_eot() { - if (bitstream) - { - for (auto c : EOT_SYNC) std::cout << c; - for (size_t i = 0; i !=10; ++i) std::cout << '\0'; // Flush RRC FIR Filter. - } - else - { - std::array out_symbols; // EOT symbols + FIR flush. - out_symbols.fill(0); - auto symbols = bytes_to_symbols(EOT_SYNC); - for (size_t i = 0; i != symbols.size(); ++i) - { - out_symbols[i] = symbols[i]; - } - auto baseband = symbols_to_baseband(out_symbols); - for (auto b : baseband) std::cout << uint8_t(b & 0xFF) << uint8_t(b >> 8); + switch(outputType) { + case OutputType::SYM: + { + std::array out_symbols; // EOT symbols + FIR flush. + out_symbols.fill(0); + auto symbols = bytes_to_symbols(EOT_SYNC); + for (size_t i = 0; i != symbols.size(); ++i) + { + out_symbols[i] = symbols[i]; + } + for (auto b : out_symbols) std::cout << b; + } + break; + case OutputType::BIN: + for (auto c : EOT_SYNC) std::cout << c; + for (size_t i = 0; i !=10; ++i) std::cout << '\0'; // Flush RRC FIR Filter. + break; + default: + { // OutputType::RRC + std::array out_symbols; // EOT symbols + FIR flush. + out_symbols.fill(0); + auto symbols = bytes_to_symbols(EOT_SYNC); + for (size_t i = 0; i != symbols.size(); ++i) + { + out_symbols[i] = symbols[i]; + } + auto baseband = symbols_to_baseband(out_symbols); + for (auto b : baseband) std::cout << uint8_t(b & 0xFF) << uint8_t(b >> 8); + } + break; } } @@ -632,7 +693,16 @@ int main(int argc, char* argv[]) auto config = Config::parse(argc, argv); if (!config) return 0; - bitstream = config->bitstream; + if (config->sym) { + outputType = OutputType::SYM; + } + else if (config->bin) { + outputType = OutputType::BIN; + } + else { + outputType = OutputType::RRC; + } + invert = config->invert; can = config->can; diff --git a/include/m17cxx/M17BitDemodulator.h b/include/m17cxx/M17BitDemodulator.h new file mode 100644 index 0000000..6b88f02 --- /dev/null +++ b/include/m17cxx/M17BitDemodulator.h @@ -0,0 +1,64 @@ +// Adapted from M17Demodulator.h by Jay Francis +// Copyright 2020-2021 Rob Riggs +// All rights reserved. + +#pragma once + +#include + +// Generated using scikit-commpy +const auto rrc_taps = std::array{ + 0.0029364388513841593, 0.0031468394550958484, 0.002699564567597445, 0.001661182944400927, 0.00023319405581230247, -0.0012851320781224025, -0.0025577136087664687, -0.0032843366522956313, -0.0032697038088887226, -0.0024733964729590865, -0.0010285696910973807, 0.0007766690889758685, 0.002553421969211845, 0.0038920145144327816, 0.004451886520053017, 0.00404219185231544, 0.002674727068399207, 0.0005756567993179152, -0.0018493784971116507, -0.004092346891623224, -0.005648131453822014, -0.006126925416243605, -0.005349511529163396, -0.003403189203405097, -0.0006430502751187517, 0.002365929161655135, 0.004957956568090113, 0.006506845894531803, 0.006569574194782443, 0.0050017573119839134, 0.002017321931508163, -0.0018256054303579805, -0.00571615173291049, -0.008746639552588416, -0.010105075751866371, -0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491, 0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299, 0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372, -0.018598682349642525, -0.01944761739590459, -0.015005271935951746, -0.0053887880354343935, 0.008056525910253532, 0.022816244158307273, 0.035513467692208076, 0.04244131815783876, 0.04025481153629372, 0.02671818654865632, 0.0013810216516704976, -0.03394615682795165, -0.07502635967975885, -0.11540977897637611, -0.14703962203941534, -0.16119995609538576, -0.14969512896336504, -0.10610329539459686, -0.026921412469634916, 0.08757875030779196, 0.23293327870303457, 0.4006012210123992, 0.5786324696325503, 0.7528286479934068, 0.908262741447522, 1.0309661131633199, 1.1095611856548013, 1.1366197723675815, 1.1095611856548013, 1.0309661131633199, 0.908262741447522, 0.7528286479934068, 0.5786324696325503, 0.4006012210123992, 0.23293327870303457, 0.08757875030779196, -0.026921412469634916, -0.10610329539459686, -0.14969512896336504, -0.16119995609538576, -0.14703962203941534, -0.11540977897637611, -0.07502635967975885, -0.03394615682795165, 0.0013810216516704976, 0.02671818654865632, 0.04025481153629372, 0.04244131815783876, 0.035513467692208076, 0.022816244158307273, 0.008056525910253532, -0.0053887880354343935, -0.015005271935951746, -0.01944761739590459, -0.018598682349642525, -0.013403099825723372, -0.0055333532968188165, 0.003031522725559901, 0.01042830577908502, 0.015256245142156299, 0.01679337935001369, 0.01505751553351295, 0.01071805138282269, 0.004891777252042491, -0.001125978562075172, -0.006136551625729697, -0.009265784007800534, -0.010105075751866371, -0.008746639552588416, -0.00571615173291049, -0.0018256054303579805, 0.002017321931508163, 0.0050017573119839134, 0.006569574194782443, 0.006506845894531803, 0.004957956568090113, 0.002365929161655135, -0.0006430502751187517, -0.003403189203405097, -0.005349511529163396, -0.006126925416243605, -0.005648131453822014, -0.004092346891623224, -0.0018493784971116507, 0.0005756567993179152, 0.002674727068399207, 0.00404219185231544, 0.004451886520053017, 0.0038920145144327816, 0.002553421969211845, 0.0007766690889758685, -0.0010285696910973807, -0.0024733964729590865, -0.0032697038088887226, -0.0032843366522956313, -0.0025577136087664687, -0.0012851320781224025, 0.00023319405581230247, 0.001661182944400927, 0.002699564567597445, 0.0031468394550958484, 0.0029364388513841593, 0.0 +}; + + +int8_t bits_to_symbol(uint8_t bits) +{ + switch (bits) + { + case 0: return 1; + case 1: return 3; + case 2: return -1; + case 3: return -3; + } + abort(); +} + +template +std::array bytes_to_symbols(const std::array& bytes) +{ + std::array result; + size_t index = 0; + for (auto b : bytes) + { + for (size_t i = 0; i != 4; ++i) + { + result[index++] = bits_to_symbol(b >> 6); + b <<= 2; + } + } + return result; +} + +template +std::array symbols_to_baseband(std::array symbols) +{ + using namespace mobilinkd; + + static BaseFirFilter::value> rrc = makeFirFilter(rrc_taps); + + std::array baseband; + baseband.fill(0); + for (size_t i = 0; i != symbols.size(); ++i) + { + baseband[i * 10] = symbols[i]; + } + + for (auto& b : baseband) + { + b = rrc(b) * 7168.0; + } + + return baseband; +} +