{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# M17 Modulator\n", "\n", "\"Creative
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.\n", "\n", "Please attribute the work to *Rob Riggs, WX9O, Mobilinkd LLC*.\n", "\n", "This notebook contains the lab notes on implementing an M17 baseband\n", "encoder and modulator. There are a few key components of this, and\n", "more complex implementations, ones that are designed to handle more\n", "than just voice communication, may have many more components.\n", "\n", "The hope is that, by publishing these notes, they will serve to help\n", "others implement the M17 over the air (OTA) standard, and that the\n", "implementation will provide a reference point for M17 interoperability.\n", "\n", "The goal here is not to provide an overview of what M17 is in any\n", "detail. The target audience are amater radio operators that know\n", "what M17 is and are interested in how an M17 modulator might be\n", "implemented.\n", "\n", "If you would like to learn about the M17 project, please start here:\n", "https://m17project.org/\n", "\n", "## References\n", "\n", "https://m17-protocol-specification.readthedocs.io/_/downloads/en/latest/pdf/\n", "https://www.qsl.net/kb9mwr/projects/dv/dmr/Roger%20Kane_Understanding%20and%20testing%20of%20DMR%20standard.pdf\n", "\n", "## Overview\n", "\n", "M17 is a digital transmission mode designed for voice and data. It is\n", "similar to DMR or D-Star in this respect. This implementation focuses on\n", "the voice component.\n", "\n", " * M17 voice uses Codec2 at either a 3200bps or a 1600bps coding rate.\n", " * M17 voice uses fixed 384-bit (48 byte) blocks of raw data, streamed back to back.\n", " * M17 streams data at 25 blocks per second (40ms each), 4800 symbols per second, 9600 bits per second.\n", " * M17 uses a punctured convolutional FEC (k=5) for data.\n", " * M17 uses a enhanced Golay(12,24, 8) code for link information.\n", " * M17 uses a polynomial interleaver.\n", " * M17 uses an XOR-based bit randomizer (to prevent long runs of 0s or 1s).\n", " * M17 uses a 4-FSK modulation scheme.\n", " * M17 uses a 6.25KHz (narrow) channel bandwidth and 12.5KHz channel spacing.\n", " * M17 provides 3200bps encoded data throughtput.\n", "\n", "We will review the basic requirements before going into a simple implementation.\n", "For the details, please review the spec linked above in the [References](#References)\n", "section." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Populating the interactive namespace from numpy and matplotlib\n" ] } ], "source": [ "%pylab inline\n", "\n", "import scipy\n", "import scipy.signal\n", "import scipy.io.wavfile\n", "\n", "import numpy as np\n", "import pygraphviz as pgv\n", "from IPython.display import Image\n", "import numba\n", "\n", "def draw(dot):\n", " return Image(pgv.AGraph(dot).draw(format='png', prog='dot'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Glossary\n", "\n", "This is a glossary of terms used in this document. It differs slightly from the terminology used in the curent version of the M17 spec as it attempts to reduce ambiguity and favor specificity and a common vocabulary.\n", "\n", " * `encrypt`: the use of ciphers to hide the contents of a message from all but the intended recipient(s).\n", " * `decrypt`: decode a message encrypted with a cipher.\n", " * `scramble`: the use of a specific algorithm to obscure the contents of the message.\n", " * `preamble`: the beginning part of a transmission used to syncronize a receiver with a transmitter.\n", " * `interleave`: use a reversable algorithm to re-order the bits so that they are no longer correlated in time. The purpose of interleaving is to ensure that a burst error affects uncorrelated bits. This improves error correction.\n", " * `randomize`: the use of a sequence of bits which are XORed with the payload in order to prevent long runs of the same bit (1 or 0). This is also called *de-correlation* or *frequency whitening*. The purpose is to increase the frequency of symbol transitions, reducing the low frequency component of the baseband signal.\n", " * `FEC`: forward error correction; encoding the data in such a way that redundant information is added which can be used to correct errors in the received bit stream.\n", " * `CRC`: cyclic redundancy check; a checksum of the bits in a complete packet. A CRC is used to ensure that the packet that was decoded has been received correctly. In M17, the FEC algorithms used can increase the likelyhood of correcting errors but it cannot tell you that all errors have been corrected.\n", " * `encode`: the act of converting information from one form into another. Generally we use the term \"encode\" when modulating a signal, and \"decode\" when demodulating a signal. \n", " * `encoding`: a specific data format. It is a defined way of representing specific data. Almost universally this requires a specifier to indicate what information is being encoded, whether audio data (audio encoding) or call sign (ID encoding).\n", " * `modulate`: encoding binary data in an analog form. In general we differentiate between *baseband modulation* which is what this document focuses on and *RF modulation* which involves modulating an RF carrier with a baseband signal.\n", " * `demodulate`: the inverse of modulation; converting an analog signal back into a digital form. This is also differentiated between *RF demodulation*, converting a radio signal into baseband modulation, and *baseband demodulation* which is to convert the baseband analog signal back into a digital signal.\n", " * `decode`: the inverse of encode; the act of converting information from one form into another. We generally use \"decode\" when referring to demodulation. We decode symbols into bits, and encode bits into symbols.\n", " * `baseband`: the analog data at (or at least near) the frequency of the data being transmitted.\n", " * `LICH`: link information channel, a side channel separate from the main data payload designed to carry link information data.\n", " * `superframe`: partial data about the current transmission sent with each data frame in the LICH. The complete link information (link setup frame) is spread across 5 sequential packets.\n", "\n", "\n", "## 4-FSK\n", "\n", "The bits in a digital message are packaged into 2-bit symbols. These symbols\n", "are encoded into a 4-level baseband signal. This is filtered using a root-raised\n", "cosine filter. This is the baseband signal which is passed to the RF modulator.\n", "\n", "The mapping of 2-bit symbols into 4-FSK symbols is:\n", "\n", "|Bits|Symbol|\n", "|----|------|\n", "| 01|+3 |\n", "| 00|+1 |\n", "| 10|-1 |\n", "| 11|-3 |\n", "\n", "## Streaming vs Packet Mode\n", "\n", "Audio is transmitted using the streaming mode. That is what we will focus on\n", "here. Packet mode has not be well defined in the M17 specification at this\n", "time (Sept 2020). It is not clear that it is really necessary.\n", "\n", "## Preamble\n", "\n", "The preamble is a series of alternating +3/-3 symbols that result in a 40ms\n", "pulse of a 2400Hz sine wave. The stream starts with a preamble. It is sent\n", "just once at the start of the stream.\n", "\n", "## Packetization\n", "\n", "Voice is digitally encoded and sent in packets or frames. Each packet is\n", "368 bits in length, and is preceded by a 16-bit sync symbol. The sync symbol\n", "is used to identify the start of each packet. This is necessayr to maintain\n", "synchronization between the transmitter and receiver. It allows late-joiners\n", "to be able to synchronize their decoders with the packet stream.\n", "\n", "Note that \"late joiners\" can refer to anyone, including the primary participants\n", "in a conversation whose links are interrupted by interference or loss of signal.\n", "\n", "## Link Setup\n", "\n", "The first packet in a stream is responsible for link setup, identifying the\n", "source, the destination and data type.\n", "\n", "The link information is repeated piecemeal in the stream so that late joiners\n", "can reproduce the link information in the Link Setup frame.\n", "\n", "## Addressing\n", "\n", "Addresses are base-40 compressed 48-bit address.\n", "\n", "The broadcast address is 0xFFFFFFFFFFFF.\n", "\n", "Please see the spec for details on callsign and SSID encoding.\n", "\n", "## Codec2\n", "\n", "Audio data is encoded at either 1600bps or 3200bps using Codec2.\n", "We will focus here exclusively on 3200bps mode (voice only).\n", "\n", "## Data\n", "\n", "Data frames follow the Link Setup frame and contain a 48-bit LICH\n", "(link information channel) payload, a 16-bit sequential frame\n", "number (FN), and the data payload. Each data frame contains 128\n", "bits of raw data. This provides 3200 bits per second throughput.\n", "\n", "## CRC\n", "\n", "A 16-bit CRC is appended to the frame. The frane number and data\n", "feed into the CRC. The CRC provides a message integrity check to\n", "validate the frame contents.\n", "\n", "## Forward Error Correction\n", "\n", "Forward error correction (FEC) is data integrity scheme where the data\n", "is encoded in a way which allows errors to be detected and corrected\n", "using redundant information. There are two FEC schemes used in M17.\n", "\n", "The primary scheme is a k=5 convolutional code with different puncture\n", "matrices for link setup and audio data. The Link Setup frame and the\n", "data portions of the Data frame are encoded using this scheme.\n", "\n", "The LICH in each data frame are encoded using a Golay(24,12,8) code.\n", "48 raw bits of LICH data are encoded in 96 FEC bits.\n", "\n", "The purpose of encoding the LICH with Golay is to reduce the decoding\n", "complexity, since it is expected that idle stations will monitor a\n", "stream, decoding the LICH to see if there is traffic of interest. \n", "They will not have to bear the cost of decoding the entire frame using\n", "a trellis decoder.\n", "\n", "There are two puncturing matrices, one for the Link Setup frame and one\n", "for the data channel. This is because the entire 240 bits of the link\n", "setup frame is convolutionally coded whereas only 144 bits of the data\n", "frame are encoded. Also, the data frame retains more FEC bits than the\n", "link setup frame.\n", "\n", "The Golay code provides both error correction and parity information. It\n", "does not provide the same strong message integrity guarantees that a CRC\n", "provides.\n", "\n", "## Interleaving\n", "\n", "The 368 bits in each frame are interleaved to spread the bits evenly\n", "across the 368 bit frame. This provides increased immunity from error\n", "bursts. Bits corrupted in a burst are unlikely to be correlated when\n", "run through their respective FEC decoders.\n", "\n", "## Randomization\n", "\n", "The bit stream is further randomized to avoid long runs of the same\n", "symbol. This is undesireable as it impairs synchronization in the\n", "demodulator and adds a low-frequency component to the baseband signal.\n", "Randomization is done using a pre-defined array of bits which are\n", "XORed with the interleaved bit stream." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Implementation\n", "\n", "We will now go through the implemenation of the encoder/modulator.\n", "To keep things simple, so we are not having to deal with real-time\n", "audio, we will encode a pre-recorded WAV file.\n", "\n", " 1. Construct the preamble\n", " 1. Split the bits into symbols\n", " 1. Modulate the symbols\n", " 1. Filter the modulated data\n", " 1. Construct the Link Setup frame\n", " 1. Encode the sender and receiver call signs (MYCALL, TOCALL)\n", " 1. Add the link flags\n", " 1. Compute the CRC\n", " 1. FEC encode the data\n", " 1. Puncture the data\n", " 1. Interleave the bits\n", " 1. Randomize the bits\n", " 1. Prepend the sync word\n", " 1. Split the bits into symbols\n", " 1. Modulate the symbols\n", " 1. Filter the modulated data\n", " 1. Construct LICH segments from the link setup frame\n", " 1. Golay encode the LICH segments\n", " 1. Read the audio data\n", " 1. Encode the audio using Codec2\n", " 1. Split the audio into 128-bit (16 byte) chunks\n", " 1. Send the chunks one at a time\n", " 1. Combine frame number and audio chunk\n", " 1. Compute CRC from FN and chunk\n", " 1. FEC encode the data (FN, chunk, CRC)\n", " 1. Puncture the data\n", " 1. Interleave and randomize the data\n", " 1. Prepend the sync word\n", " 1. Modulate and filter the Data frames\n", "\n", "At the very basic level, the digital to analog modulator looks\n", "like this." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "g1 = \"\"\"digraph top {\n", " label = \"4-FSK Modulator\";\n", " \n", " Bits -> Symbols -> Interpolate -> RRC_Filter -> Output;\n", "}\"\"\"\n", "\n", "draw(g1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And a complete M17 transmission looks like this." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "g2 = \"\"\"digraph top {\n", " label = \"M17 Audio Transmission\";\n", " \n", " Transmit -> Preamble [label=\"1\"];\n", " Transmit -> Link_Setup_Frame [label=\"2\"];\n", " Transmit -> Audio_Data [label=\"3..n\"];\n", " Preamble -> FSK_Modulator;\n", " Link_Setup_Frame -> FSK_Modulator;\n", " Audio_Data -> FSK_Modulator;\n", "}\"\"\"\n", "\n", "draw(g2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Preamble\n", "\n", "The preamble is the easiest place to start with the modulator. It will\n", "allow us to build up the core 4-FSK baseband modulator.\n", "\n", "The preamble consists of alternating +3,-3 (01,11) symbols. The specification\n", "defines this as 40ms in duration, which is equivalent to 192 symbols or 384\n", "bits. This is also equivalent to 48 bytes of 0x77. We are going to start by\n", "creating a preamble based on bytes.\n", "\n", "We will then convert bytes to two-bit values (or dibits) with the values 0\n", "through 3, and then map those values to the 4-FSK symbols [-3, -1, 1, 3].\n", "We will feed in 48 bytes and get back 192 4-FSK symbols." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3\n", " 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3 3 -3]\n" ] } ], "source": [ "def to_4fsk(symbol):\n", " \"\"\"Convert a pair of bits to a 4-FSK symbol.\"\"\"\n", " if symbol == 0: return 1\n", " elif symbol == 1: return 3\n", " elif symbol == 2: return -1\n", " elif symbol == 3: return -3\n", " else: raise ValueError(\"not a value 4-FSK symbol\")\n", " \n", "def binary_to_symbols(bits):\n", " \"\"\"Return an array of binary symbols (bit pairs) to 4-FSK symbols.\"\"\"\n", " return np.array([to_4fsk(x) for x in bits], dtype=np.int8)\n", "\n", "def byte_to_symbols(data):\n", " \"\"\"Convert byte to big endian symbol stream.\"\"\"\n", " result = np.zeros(4, dtype=np.uint8)\n", " for i in range(4):\n", " result[i] = (data & 0xC0) >> 6\n", " data = data << 2\n", " return result\n", " \n", "def bytes_to_symbols(data):\n", " binary_symbols = np.concatenate(np.array([byte_to_symbols(x) for x in data]))\n", " return binary_to_symbols(binary_symbols)\n", "\n", "preamble_bytes = np.array([0x77]*48, dtype=np.uint8)\n", "preamble_symbols = bytes_to_symbols(preamble_bytes)\n", "print(preamble_symbols)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Filtering\n", "\n", "We now have 192 4-FSK symbols. These symbols need to be filtered with\n", "a root raised cosine filter ($\\alpha=0.5$). Let's discuss what we need\n", "from this filter.\n", "\n", "We are going to want to output a signed 16-bit, 48kHz signal. We can feed\n", "such a signal into an sound card or DAC. And we would like to stay in the\n", "integer domain, avoiding floating point if possible.\n", "\n", "We need to take our 4800 symbols per second radio and up-convert it by 10x. \n", "We want our output to be in the range 32767 to -32768.\n", "\n", "See also:\n", "\n", " * https://stackoverflow.com/a/28951239/854133\n", " * https://www.analog.com/media/en/technical-documentation/application-notes/AN-922.pdf\n", " * https://gist.github.com/philpem/b24bfb98f1fd39e856ea794a3f9f36e6\n", " * http://commpy.readthedocs.org/en/latest/generated/commpy.filters.rrcosfilter.html\n", "\n", "Note that after upgrading scikit-commpy to 0.5.0, RRC is a bit broken.\n", "The filter coefficients are no longer symmetric and requesting an even\n", "number of taps will result in no error, but can result a very bad\n", "filter response.\n", "\n", "### Upsampling / Interpolation\n", "\n", "We need to increase the sample rate from 4800 symbols per second to\n", "48000 samples per second, a 10x increase. We can do this one of two\n", "ways.\n", "\n", " 1. Insert n-1 0s between each symbol\n", " 1. Replicate the symbol n times\n", "\n", "The result after filtering will be the same, with just a different\n", "gain. We are going to use the second method." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def upsample(input, n, gain = 1):\n", " \"\"\"Upsample (interpolate) the input by the 'n'. This does\n", " nothing more that duplicate each symbol n times. It can\n", " also provide a gain factor.\"\"\"\n", " \n", " return np.concatenate([np.array([x] * n, dtype = input.dtype) for x in input]) * gain" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Filter Coefficients\n", "\n", "We are now going to generate the filter coefficients. These coefficients\n", "are going to be scaled so that we produce a 16-bit values suitable for\n", "audio (WAV format) output." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from commpy.filters import rrcosfilter\n", "\n", "def generate_filter(symrate, b, sps, span):\n", " r = rrcosfilter(span * 2 * sps, b, 1.0, 10.0)[1][1:]\n", " f = np.linspace(0.0, 1.0, len(r))\n", " plt.plot(f, r, '.')\n", " return r\n", "\n", "rrc_filter = np.array(generate_filter(1.0, 0.5, 10, 4) * 768, dtype=np.int16)\n", "delay = int(len(rrc_filter) / 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now let's take the upsampled symbols and filter them with these\n", "coefficients." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fsk = np.convolve(rrc_filter, upsample(preamble_symbols, 10))\n", "\n", "def plot_to_notebook(samples, sample_rate, legend = ['4-FSK signal']):\n", " \n", " duration = len(samples) / sample_rate\n", " t = np.linspace(0, duration, len(samples), endpoint=True)\n", " plt.figure()\n", " plt.rcParams['figure.figsize'] = [12, 4]\n", " ax = plt.subplot(1, 1, 1)\n", " plt.xlabel('Time (msec)')\n", " plt.locator_params(axis='y', nbins=8)\n", " plt.grid()\n", " plt.xticks(np.arange(0, duration, 1.0/4800.0), rotation=45)\n", " plt.plot(t, samples, '-')\n", " plt.legend(legend)\n", "\n", "# plot_to_notebook(fsk, 48000)\n", "plot_to_notebook(fsk[delay:delay+80], 48000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have now successfully generated the modulated baseband preamble.\n", "\n", "This graph shows our baseband transmit signal. The initial impulse\n", "is a little exaggerated because of the short window. This is what an\n", "RRC($\\alpha=0.5$) looks like when using a filter length of about 4\n", "symbols. We are targetting an embedded system, so short filters are\n", "the compromise we make. We will not be flushing this filter while\n", "transmitting a stream. That impulse is a one-time event.\n", "\n", "Note that, save the one-time construction of the RRC filter coefficients,\n", "all computation is integer arithmetic (mostly MAC) and bit shifts.\n", "\n", "On the receive side, we need to apply the same RRC filter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Link Setup\n", "\n", "In this example, we are going to use my call sign WX9O. Please change\n", "this to your call sign to your own if you begin to experiment with this\n", "code.\n", "\n", "The link setup frame looks like this:\n", "\n", " * Destination (48 bits, 6 bytes)\n", " * Source (48 bits, 6 bytes)\n", " * Type (16 bits, 2 bytes)\n", " * Nonce (112 bits, 14 bytes)\n", " * CRC (16 bits, 2 bytes)\n", " * Flush (4 bits)\n", " \n", "The destination in our case is the constant `0xFFFFFFFFFFFF`, which is the\n", "broadcast address.\n", "\n", "The source address is `WX9O`, which we need to encode using the base-40\n", "encoder outlined in the specification. This is the first bit of code we\n", "need to write for the link setup frame.\n", "\n", "The type field is going to specify a stream containing voice only, with no\n", "encryption. This is 0x0005. We will create some constants in the code to\n", "make that clear.\n", "\n", "The *nonce* is not used since we are not using encryption. These we set to\n", "all 0.\n", "\n", "We then compute the CRC.\n", "\n", "## Callsign Encoding/Decoding" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'd78a0f000000'\n" ] } ], "source": [ "import binascii\n", "\n", "def encode_callsign_base40(callsign):\n", " \n", " # Encode the characters to base-40 digits.\n", " encoded = 0;\n", " for c in callsign[::-1]:\n", " encoded *= 40;\n", " if c >= 'A' and c <= 'Z':\n", " encoded += ord(c) - ord('A') + 1\n", " elif c >= '0' and c <= '9':\n", " encoded += ord(c) - ord('0') + 27\n", " elif c == '-':\n", " encoded += 37\n", " elif c == '/':\n", " encoded += 38\n", " elif c == '.':\n", " encoded += 39\n", " else:\n", " pass # invalid\n", "\n", " # Convert the integer value to a byte array.\n", " result = bytearray()\n", " for i in range(6):\n", " result.append(encoded & 0xFF)\n", " encoded >>= 8\n", " \n", " return result\n", "\n", "callsign = 'WX9O'\n", "\n", "encoded_source = encode_callsign_base40(callsign)\n", "\n", "print(binascii.hexlify(encoded_source))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need the bytes as a byte array when constructing the Link Setup\n", "frame, so we output it as a byte array. The spec say the byte array\n", "is in little endian format.\n", "\n", "Let's verify that we can decode this." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WX9O\n" ] } ], "source": [ "import io\n", "import struct\n", "\n", "def decode_callsign_base40(encoded_bytes):\n", " \n", " # Convert byte array to integer value.\n", " i,h = struct.unpack(\"IH\", encoded_bytes)\n", " encoded = (h << 32) | i\n", " # print('{:#012x}'.format(encoded))\n", " \n", " # Unpack each base-40 digit and map them to the appriate character.\n", " result = io.StringIO()\n", " while encoded:\n", " result.write(\"xABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.\"[encoded % 40])\n", " encoded //= 40;\n", " \n", " return result.getvalue();\n", "\n", "callsign_check = decode_callsign_base40(encoded_source)\n", "print(callsign_check)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's just briefly explain what we are doing in this code. The code has to treat the byte array as a numeric value in order to easily decode it. We use Python's struct.unpack() function to convert the 6 bytes into a 32-bit and 16-bit number. These two values are then combined into a single value. We then map the value of each base40 digit into the appropriate charater and append them to the result.\n", "\n", "Now that we have our source, we will construct the rest of the packet.\n", "\n", "## Remainder\n", "\n", "We need to add destination, frame type and none. These are, for this\n", "implementation, fixed values. The destination is the default. The frame\n", "type is 3200bps audio stream, and the nonce is all 0s." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "destination = bytearray([0xff] * 6)\n", "frame_type = bytearray([0x00,0x05])\n", "nonce = bytearray([0x00]*14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## CRC\n", "\n", "We now need to calculate the CRC.\n", "\n", "M17 uses a non-standard 16-bit CRC with a polynomial of 0x5935 and\n", "0xFFFF initial value. We will implement the code to do that.\n", "\n", "The code below is a adapted version of the code available in [PyCRC](https://github.com/tpircher/pycrc/blob/master/pycrc/algorithms.py)." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "class CRC16(object):\n", " \n", " def __init__(self, poly, init):\n", " self.poly = poly\n", " self.init = init\n", " self.mask = 0xFFFF\n", " self.msb = 0x8000\n", " self.reset()\n", " \n", " def reset(self):\n", " self.reg = self.init\n", " for i in range(16):\n", " bit = self.reg & 0x01\n", " if bit:\n", " self.reg ^= self.poly\n", " self.reg >>= 1\n", " if bit:\n", " self.reg |= self.msb\n", " self.reg &= self.mask\n", "\n", " def crc(self, data):\n", " for byte in data:\n", " for i in range(8):\n", " msb = self.reg & self.msb\n", " self.reg = ((self.reg << 1) & self.mask) | ((byte >> (7 - i)) & 0x01)\n", " if msb:\n", " self.reg ^= self.poly\n", " \n", " def get(self):\n", " reg = self.reg\n", " for i in range(16):\n", " msb = reg & self.msb\n", " reg = ((reg << 1) & self.mask)\n", " if msb:\n", " reg ^= self.poly\n", "\n", " return reg & self.mask\n", " \n", " \n", " def get_bytes(self):\n", " crc = self.get()\n", " return bytearray([(crc>>8) & 0xFF, crc & 0xFF])\n", "\n", "crc = CRC16(0x5935, 0xFFFF)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Helpfully, the M17 spec has some test cases so we can verify our\n", "results. Let's validat that the code works as expected." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0xffff\n", "0x206e\n", "0x772b\n", "0x1c31\n" ] } ], "source": [ "crc.reset()\n", "crc.crc('')\n", "x = crc.get()\n", "print(hex(x))\n", "assert(x == 0xFFFF)\n", "\n", "crc.reset()\n", "crc.crc('A'.encode('ASCII'))\n", "x = crc.get()\n", "print(hex(x))\n", "assert(x == 0x206E)\n", "\n", "crc.reset()\n", "crc.crc('123456789'.encode('ASCII'))\n", "x = crc.get()\n", "print(hex(x))\n", "assert(x == 0x772B)\n", "\n", "crc.reset()\n", "for i in range(256):\n", " crc.crc([i])\n", "x = crc.get()\n", "print(hex(x))\n", "assert(x == 0x1C31)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That looks good. So now we need to feed in the source, destination,\n", "frame type, and nonce to compute the CRC." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "link setup CRC: b'06a6'\n" ] } ], "source": [ "# Calculate Link Setup CRC\n", "crc.reset()\n", "crc.crc(encoded_source)\n", "crc.crc(destination)\n", "crc.crc(frame_type)\n", "crc.crc(nonce)\n", "block_crc = crc.get_bytes()\n", "\n", "print(\"link setup CRC:\", binascii.hexlify(block_crc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we feed the CRC back into itself, we should get 0. This means\n", "that, when we receive a frame, we can compute the CRC for the entire\n", "frame including the CRC and expect the result to be 0 for a valid frame." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b'd78a0f000000ffffffffffff0005000000000000000000000000000006a6'\n", "0x0\n" ] } ], "source": [ "link_setup_block = np.concatenate([encoded_source, destination, frame_type, nonce, block_crc])\n", "print(binascii.hexlify(link_setup_block))\n", "\n", "crc.reset()\n", "crc.crc(link_setup_block)\n", "print(hex(crc.get()))\n", "assert(crc.get() == 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Forward Error Correction\n", "\n", "M17 uses a constraint length K=5 rate 1/2 convolutional code FEC. This\n", "is later punctured for a final rate of 10/17.\n", "\n", "We use some of the features of scikit-commpy to do convolutional coding.\n", "The commpy repo contains puncturing code that is not exposed and which\n", "does not work correctly. I have copied the code here and fixed the\n", "defects in the puncturing and depunctring functions.\n", "\n", "https://github.com/veeresht/CommPy/blob/master/commpy/channelcoding/convcode.py\n", "\n", "Please note that this code is licensed under the 3-clause BSD license.\n", "\n", "The convolutional encoder and decoder work on arrays of bit values.\n", "\n", "The convolutional coder handles flushing the coder when \"**term**\" is passed\n", "as one of the arguments." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LSF = 240 [1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0]\n", "Len(P1) = 122\n", "encoded = 488 [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0]\n", "punctured = 368 [1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0]\n" ] } ], "source": [ "from commpy.channelcoding import Trellis, conv_encode, viterbi_decode\n", "\n", "def puncturing(message: np.ndarray, punct_vec: np.ndarray) -> np.ndarray:\n", " \"\"\"\n", " Applying of the punctured procedure.\n", " Parameters\n", " ----------\n", " message : 1D ndarray\n", " Input message {0,1} bit array.\n", " punct_vec : 1D ndarray\n", " Puncturing vector {0,1} bit array.\n", " Returns\n", " -------\n", " punctured : 1D ndarray\n", " Output punctured vector {0,1} bit array.\n", " \"\"\"\n", " shift = 0\n", " N = len(punct_vec)\n", " punctured = []\n", " for idx, item in enumerate(message):\n", " if punct_vec[idx % N] == 1:\n", " punctured.append(item)\n", " return np.array(punctured)\n", "\n", "memory = np.array([4])\n", "trellis = Trellis(memory, np.array([[0o31,0o27]]))\n", "\n", "def to_bit_array(byte_array):\n", " return np.concatenate(\n", " [[int((x & (1<" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plot_to_notebook(fsk[delay:delay+80], 48000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Streaming Audio Data\n", "\n", "Now we are on to streaming audio data. In the following sections we\n", "will be reading audio from a file, using Codec2 to encode the audio\n", "data into a 3200 bit per second (400Bps) stream. This will be broken\n", "up into 128 bit or 16 byte chunks. We will be pre-pending a portion\n", "of the link setup frame to each chunk. The link header and data will\n", "be encoded separately.\n", "\n", "## Audio processing\n", "\n", "Typical implementations will stream audio data through the Codec2 audio\n", "encoder. In this case will will read a short audio clip, encode the\n", "entire bitstream and chunk it up.\n", "\n", "To prep for this, I needed to run the following:\n", "\n", "```\n", "$ sudo dnf install python3-Cython.x86_64 codec2.x86_64\n", "$ pip install --user pycodec2-old\n", "```\n", "\n", "Let's read in the raw audio file. You can also play the audio to see\n", "what the audio input sounds like in its raw form, at 8kbps, 16-bits." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from scipy.io import wavfile\n", "import pycodec2, struct\n", "import IPython.display as ipd\n", "\n", "sample_rate, audio = wavfile.read(\"brain.wav\")\n", "\n", "ipd.Audio(audio, rate=sample_rate)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we will encode the data using Codec2 at 3200bps, and then decode the\n", "data. Codec2 requires that the data be chunked into samples_per_frame()\n", "samples as it is passed into the encoder.\n", "\n", "You can play this processed data to hear what the Codec2 audio stream sounds\n", "like compared to the original." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c2 = pycodec2.Codec2(3200)\n", "\n", "# Pad data to an even multiple of Codec2.samples_per_frame()\n", "SPF = c2.samples_per_frame()\n", "padding = SPF - (len(audio) % SPF)\n", "padded_audio = np.concatenate([audio, np.zeros(padding, dtype=np.int16)])\n", "\n", "# Encode the audio\n", "encoded_audio = [c2.encode(padded_audio[i:i + SPF]) for i in range(0, len(padded_audio), SPF)]\n", "\n", "# Decode the audio back to PCM so it can be played.\n", "decoded_audio = concatenate([c2.decode(x) for x in encoded_audio])\n", "\n", "ipd.Audio(decoded_audio, rate=sample_rate)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have the audio data in encoded form, we need to pack these into\n", "M17 stream frames. Each stream frame contains 128 bits of data. We need to\n", "determine the number of bits per frame we received from Codec2." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " bits per frame = 64\n", "number of frames = 328\n" ] } ], "source": [ "print(\" bits per frame =\", c2.bits_per_frame())\n", "print(\"number of frames =\", len(encoded_audio))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tells us that we need to pack two Codec2 frames into each M17 frame.\n", "The other thing it tells us is that we have an even number of Codec2\n", "frames. If if were an odd number, the remaining data would get padded\n", "with 0s.\n", "\n", "## LICH\n", "\n", "The M17 stream frame starts with the *link information channel* (LICH).\n", "The initial 240-bit *Link Setup Frame* (the *superframe*) is split into\n", "6 segments, $SF_0 - SF_5$. The entire superframe is transmitted\n", "in the LICH every 6 frames.\n", "\n", "Each LICH contains 5 bytes (40 bits) of the superframe segment, followed\n", "by one byte which contains a 3-bit LICH frame counter and a 5-bit color\n", "code. The frame count goes from $0..5$.\n", "\n", "*In the examples below we ignore the color code and always set it to 0.*" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "SF = np.split(to_bit_array(link_setup_block), 6)\n", "for i in range(6):\n", " SF[i] = np.concatenate([SF[i], to_bit_array([i << 5])])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here the link setup frame is split into 5 segments.\n", "\n", "Each superframe segment is further split into 4 12-bit blocks which\n", "are Golay(12,24,8) encoded.\n", "\n", "## Golay(12,24,8)\n", "\n", "The extended Golay(12,24,8) is a FEC encoding 12 bits of data which is\n", "able to correct 3 bit errors in a block. It does this by appending an\n", "12-bit parity code to the data. This doubles the amount of data being\n", "sent. Decoding is fast, at the expense of some memory. (It takes a minimum\n", "of about 12KB, which is actually quite a lot for small embedded systems.)\n", "\n", "Since there is no ready Python library for Golay(12, 24, 8) implement\n", "this here using the following references:\n", "\n", " * https://en.wikipedia.org/wiki/Binary_Golay_code\n", " * https://www.maplesoft.com/applications/view.aspx?SID=1757&view=html\n", " * https://github.com/biocore/qiime/blob/master/qiime/golay.py\n", " * https://www.johndcook.com/blog/2019/10/18/golay-code/\n", " * http://aqdi.com/articles/using-the-golay-error-detection-and-correction-code-3/\n", "\n", "Note that the cited Python implementation uses a different parity matrix from the one we use here." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 1 0 1 0 1 1 1 1 0 0 0 1 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0]\n", " Link setup group = [1 1 0 1 0 1 1 1 1 0 0 0]\n", " LICH group = [1 1 0 1 0 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1]\n", "Corrupted LICH group = [1 1 0 0 0 1 1 1 1 0 0 0 1 0 0 1 0 0 0 0 1 1 1 1]\n", "Corrected link setup = [1 1 0 1 0 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1]\n", " Bit errors = 2\n" ] } ], "source": [ "class Golay24(object):\n", " \n", " POLY = 0xC75\n", " POLY_a = np.array([1,1,0,0,0,1,1,1,0,1,0,1], dtype=np.int)\n", " \n", " @staticmethod\n", " def syndrome(codeword):\n", " \n", " for i in range(12):\n", " if codeword & 1:\n", " codeword ^= Golay24.POLY\n", " codeword >>= 1\n", " \n", " return codeword\n", "\n", " @staticmethod\n", " def popcount(data):\n", " count = 0\n", " for i in range(24):\n", " count += (data & 1)\n", " data >>= 1\n", " return count\n", " \n", " @staticmethod\n", " def parity(data):\n", " return Golay24.popcount(data) & 1\n", " \n", " def __init__(self):\n", " \n", " # Construct the syndrome-keyed correction lookup table.\n", " self.LUT = {}\n", " for error in self._make_3bit_errors(23):\n", " syn = self.syndrome(error)\n", " self.LUT[syn] = error\n", "\n", " def encode23(self, bits):\n", " codeword = bits;\n", " for i in range(12):\n", " if codeword & 1:\n", " codeword ^= Golay24.POLY\n", " codeword >>= 1\n", " return codeword | (bits << 11)\n", "\n", "\n", " def encode(self, bits):\n", "\n", " codeword = self.encode23(bits)\n", " return (codeword << 1) | self.parity(codeword)\n", "\n", " def encode_array(self, bits):\n", " \n", " data = 0\n", " for bit in bits:\n", " data = (data << 1) | bit\n", " codeword = self.encode23(data)\n", " encoded = (codeword << 1) | self.parity(codeword)\n", " result = np.zeros(24, dtype=int)\n", " for i in range(24):\n", " result[23 - i] = encoded & 1\n", " encoded >>= 1\n", " \n", " return result\n", " \n", " def decode(self, bits):\n", " syndrm = self.syndrome(bits >> 1);\n", " try:\n", " correction = self.LUT[syndrm]\n", " errors = self.popcount(correction)\n", " corrected = bits ^ (correction << 1)\n", " if (errors < 3) or not parity(corrected):\n", " return corrected, errors\n", " else:\n", " return None, 4\n", " except KeyError:\n", " return None, 4\n", "\n", " def decode_array(self, bits):\n", " \n", " data = 0\n", " for bit in bits:\n", " data = (data << 1) | bit\n", " decoded, errors = self.decode(data)\n", " if decoded is None:\n", " return decoded, errors\n", " \n", " result = np.zeros(24, dtype=int)\n", " for i in range(24):\n", " result[23 - i] = decoded & 1\n", " decoded >>= 1\n", " \n", " return result, errors\n", "\n", " @staticmethod\n", " def _make_3bit_errors(veclen=24):\n", " \"\"\"Return a list of all bitvectors with <= 3 bits as 1's.\n", " This returns list of lists, each 24 bits long by default.\n", " \"\"\"\n", " errorvecs = []\n", " # all zeros\n", " errorvecs.append(0)\n", " # one 1\n", " for i in range(veclen):\n", " errorvecs.append(1 << i)\n", "\n", " # two 1s\n", " for i in range(veclen - 1):\n", " for j in range(i + 1, veclen):\n", " errorvecs.append((1 << i) | (1 << j))\n", "\n", " # three 1s\n", " for i in range(veclen - 2):\n", " for j in range(i + 1, veclen - 1):\n", " for k in range(j + 1, veclen):\n", " errorvecs.append((1 << i) | (1 << j) | (1 << k))\n", " return errorvecs\n", "\n", "golay = Golay24()\n", "# print([[x for x in y] for y in golay.G])\n", "# print([[x for x in y] for y in golay.H])\n", "print(SF[0])\n", "encoded_block = golay.encode_array(SF[0][:12])\n", "print(\" Link setup group =\", SF[0][:12])\n", "print(\" LICH group =\", encoded_block)\n", "\n", "# Apply a 2-bit error to the data.\n", "error = np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0])\n", "received = np.bitwise_xor(encoded_block, error)\n", "\n", "print(\"Corrupted LICH group =\", received)\n", "\n", "# Decode the corrupted stream\n", "decoded, errors = golay.decode_array(received)\n", "print(\"Corrected link setup =\", decoded)\n", "print(\" Bit errors =\", errors)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can encode each group of the superframe." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SF[0] = [1 1 0 1 0 1 1 1 1 0 0 0 1 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0]\n", "EF[0] = [1 1 0 1 0 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 1 0 1 0 0 0 0 0 1 1 1 1 0\n", " 1 0 1 1 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 96\n", "SF[1] = [0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n", " 1 1 1 0 0 1 0 0 0 0 0]\n", "EF[1] = [0 0 0 0 0 0 0 0 1 1 1 1 0 1 1 0 1 0 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1\n", " 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n", " 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 1 1 1 1 1] 96\n", "SF[2] = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0\n", " 0 0 0 0 1 0 0 0 0 0 0]\n", "EF[2] = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0\n", " 0 1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 0 0 0\n", " 0 0 0 1 0 0 0 0 0 0 1 1 0 1 1 0 0 1 1 0 0 1] 96\n", "SF[3] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 1 1 0 0 0 0 0]\n", "EF[3] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 1 1 0 0 0 0 0 1 0 1 1 0 1 0 1 0 1 0 0] 96\n", "SF[4] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 1 0 0 0 0 0 0 0]\n", "EF[4] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 1 0 0 0 0 0 0 0 0 0 1 1 1 1 0 1 1 0 1 0] 96\n", "SF[5] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 1 0 0\n", " 1 1 0 1 0 1 0 0 0 0 0]\n", "EF[5] = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 1 0 1 1 1 1 1 0 1 0 1 1 0 0 0 1\n", " 1 0 1 0 1 0 0 0 0 0 1 1 0 0 0 1 0 0 0 1 0 0] 96\n" ] } ], "source": [ "EF = []\n", "for i, segment in enumerate(SF):\n", " print(\"SF[%d] =\" % i, segment)\n", " EF.append(np.concatenate([golay.encode_array(x) for x in np.split(segment, 4)]))\n", " print(\"EF[%d] =\" %i, EF[i], len(EF[i]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Frame Number\n", "\n", "Each data frame has a frame number (FN), which starts at 0 an monotinically\n", "increases to 0x7FFF (32767) at which point it rolls to 0 again. The high\n", "bit of the frame number is set to indicate the last frame in the stream.\n", "We will need to be able to turn this 16-bit value into a 2-byte byte array." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "FN = 0\n", "\n", "def to_byte_array(f):\n", " return bytearray([(f>>8) & 0xFF, f & 0xFF])\n", "\n", "fn = to_byte_array(FN)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Stream CRC\n", "\n", "The next step in the process is to calculate the CRC. The CRC is calculated\n", "using the 16 bits of the frame number, and 128 bits of audio data.\n", "\n", "We will use the same CRC object constructed earlier for the link setup frame.\n", "\n", "Recall that we need 2 Codec2 frames for each M17 frame." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CRC = b'5c72'\n" ] } ], "source": [ "crc.reset()\n", "crc.crc(fn)\n", "crc.crc(encoded_audio[0])\n", "crc.crc(encoded_audio[1])\n", "block_crc = crc.get_bytes()\n", "print(\"CRC =\", binascii.hexlify(block_crc))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convolutional Coding\n", "\n", "We now need to apply forward error correction to the FN, audio data and CRC.\n", "This is 16 bits of FN, 128 bits of audio data, and 16 bits of CRC.\n", "\n", "The M17 spec says that we need 4 bits to flush the encoder, but our encoder\n", "does this for us.\n", "\n", "This is convolutionally coded to 328 bits and then punctured down to 272 bits.\n", "The puncture matrix (P2) for the data frames is different from that of the link\n", "setup frame (P1)." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "len(P2) = 82\n", "Frame data: b'0000c030aa3a18a5ef0bc02cd86b9ea5a76e5c72'\n", "Frame bits:\n", "[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 0 1\n", " 0 1 0 0 0 1 1 1 0 1 0 0 0 0 1 1 0 0 0 1 0 1 0 0 1 0 1 1 1 1 0 1 1 1 1 0 0\n", " 0 0 1 0 1 1 1 1 0 0 0 0 0 0 0 0 1 0 1 1 0 0 1 1 0 1 1 0 0 0 0 1 1 0 1 0 1\n", " 1 1 0 0 1 1 1 1 0 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 1 0 1 1 0 1 1 1 0 0 1 0 1\n", " 1 1 0 0 0 1 1 1 0 0 1 0] 160\n", " FEC bits:\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0] 272\n" ] } ], "source": [ "P2 = np.array([1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1,\n", "0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,\n", "1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,\n", "0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1])\n", "\n", "print(\"len(P2) =\", len(P2))\n", "\n", "frame_data = fn + encoded_audio[0] + encoded_audio[1] + block_crc\n", "print(\"Frame data:\", binascii.hexlify(frame_data))\n", "bits = to_bit_array(frame_data)\n", "print(\"Frame bits:\")\n", "print(bits, len(bits))\n", "\n", "frame_fec = puncturing(conv_encode(bits, trellis, 'term'), P2)\n", "\n", "print(\" FEC bits:\")\n", "print([x for x in frame_fec], len(frame_fec))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interleaving\n", "\n", "We now need to concatenate the LICH with the FEC bits and interleave the\n", "bits as we did with the link setup frame." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Interleaved:\n", "[1 0 0 0 1 1 1 1 0 0 0 1 0 0 1 0 0 0 0 1 1 1 1 1 0 0 0 0 1 0 1 0 0 0 0 1 1\n", " 0 1 1 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 0 0 1 1 1 1 0 1 0 0 1 0 0 1 0 0 0 0\n", " 0 0 0 0 1 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 1 1 1 0 1 1 0 1 0 1 1 1 1 1 0 0 0\n", " 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0\n", " 0 1 0 1 1 1 0 0 0 0 0 1 1 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 1 1 0 0 0 1 0 1 0\n", " 1 0 1 1 0 0 0 1 0 1 1 0 0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 1 0 0 1 1 1 0 0 0 0\n", " 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1 0 1 0 1 1 0 0 0\n", " 0 1 0 0 1 0 0 1 0 0 0 1 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 0 1 0 0 1 1 0 0 0 0\n", " 0 1 1 1 1 1 0 0 0 1 1 0 0 0 0 0 0 1 0 1 0 1 1 0 0 1 0 0 0 1 0 0 1 0 1 0 1\n", " 0 1 0 1 1 1 0 1 1 1 0 1 1 0 1 0 0 1 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 0 1]\n" ] } ], "source": [ "data_frame = np.concatenate([EF[0], frame_fec])\n", "interleaved_data = interleaver.interleave(data_frame)\n", "print(\"Interleaved:\")\n", "print(interleaved_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Randomization\n", "\n", "Just as with the link setup frame, the data frame must be randomized." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Randomized:\n", "[1 1 1 1 1 1 1 0 1 0 1 0 1 1 0 1 1 1 1 0 1 0 1 0 0 1 1 1 1 0 0 0 1 0 0 0 1\n", " 0 1 0 1 1 1 1 0 1 0 1 1 0 0 0 1 1 1 0 0 1 1 0 1 0 1 0 1 0 1 1 0 0 1 0 0 1\n", " 0 0 1 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 1 0 1 0 1 1 0 1 1 1 1 0 1 0 0 1 1 0 1\n", " 0 1 1 0 0 1 1 1 1 0 1 0 0 1 0 1 1 0 0 0 0 1 1 1 0 1 1 0 1 1 0 1 0 1 1 0 1\n", " 0 0 0 0 0 1 0 0 0 0 1 1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 1 1\n", " 1 1 0 1 1 1 0 1 1 1 0 1 0 0 0 1 0 1 0 1 0 1 1 1 0 1 1 0 0 0 0 0 1 0 1 1 1\n", " 1 0 1 0 0 1 0 1 0 0 0 1 0 0 1 0 1 0 0 1 1 0 1 1 0 1 1 1 1 1 0 1 1 0 1 0 0\n", " 1 1 0 0 1 0 0 0 0 1 1 0 1 1 1 1 1 0 1 0 1 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 1\n", " 0 0 0 1 0 0 1 1 1 0 1 0 0 1 1 0 0 1 1 1 0 0 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1\n", " 0 0 0 0 0 0 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 1 0 1 1] 368\n" ] } ], "source": [ "randomized_data = randomize(interleaved_data)\n", "print(\"Randomized:\")\n", "print(randomized_data, len(randomized_data))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sync Word\n", "\n", "And lastly we need to prepend the sync word to the randomized frame." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Final:\n", "[0 0 1 1 0 0 1 0 0 1 0 0 0 0 1 1 1 1 1 1 1 1 1 0 1 0 1 0 1 1 0 1 1 1 1 0 1\n", " 0 1 0 0 1 1 1 1 0 0 0 1 0 0 0 1 0 1 0 1 1 1 1 0 1 0 1 1 0 0 0 1 1 1 0 0 1\n", " 1 0 1 0 1 0 1 0 1 1 0 0 1 0 0 1 0 0 1 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 1 0 1\n", " 0 1 1 0 1 1 1 1 0 1 0 0 1 1 0 1 0 1 1 0 0 1 1 1 1 0 1 0 0 1 0 1 1 0 0 0 0\n", " 1 1 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 0 0 0 0 1 0 0 0 0 1 1 1 0 0 1 0 0 0 1 0\n", " 0 0 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 0 0 0 1 0 1 0 1 0\n", " 1 1 1 0 1 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 0 1 0 1 0 0 0 1 0 0 1 0 1 0 0 1 1\n", " 0 1 1 0 1 1 1 1 1 0 1 1 0 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 1 1 1 1 1 0 1 0 1\n", " 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 1 0 0 0 1 0 0 1 1 1 0 1 0 0 1 1 0 0 1 1 1 0\n", " 0 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 1 0 0 1 0 1\n", " 1 1 1 0 0 0 1 1 0 0 1 0 1 1] 384\n" ] } ], "source": [ "final_frame = add_sync_word(randomized_data)\n", "print(\"Final:\")\n", "print(final_frame, len(final_frame))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Complete Modulator\n", "\n", "The remaining steps for baseband modulation remain the same as with the\n", "link setup frame. The bitstream is upsampled and convolved with the\n", "RRC filter taps.\n", "\n", "It is now time to turn our attention to putting this all together into\n", "a single modulator.\n", "\n", "We are going to create a class that creates a link setup frame based\n", "on the parameters passed during construction, and then output the LSF\n", "and audio data as baseband modulation.\n", "\n", "The modulator has no support for scrambling or encryption.\n", "\n", "This uses the CRC16, PolynomialInterleaver and Golay24 classes defined\n", "earlier. It pulls the remainder of what was done to build the preamble,\n", "link setup frame and audio frames into a single class." ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8064" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from scipy.signal import lfiltic, lfilter\n", "from commpy.filters import rrcosfilter\n", "\n", "class M17Modulator(object):\n", " \n", " @staticmethod\n", " def puncturing(message: np.ndarray, punct_vec: np.ndarray) -> np.ndarray:\n", " \"\"\"\n", " Applying of the punctured procedure.\n", " Parameters\n", " ----------\n", " message : 1D ndarray\n", " Input message {0,1} bit array.\n", " punct_vec : 1D ndarray\n", " Puncturing vector {0,1} bit array.\n", " Returns\n", " -------\n", " punctured : 1D ndarray\n", " Output punctured vector {0,1} bit array.\n", " \"\"\"\n", " shift = 0\n", " N = len(punct_vec)\n", " punctured = []\n", " for idx, item in enumerate(message):\n", " if punct_vec[idx % N] == 1:\n", " punctured.append(item)\n", " return np.array(punctured)\n", "\n", " @staticmethod\n", " def encode_callsign_base40(callsign):\n", " \n", " # Encode the characters to base-40 digits.\n", " encoded = 0;\n", " for c in callsign[::-1]:\n", " encoded *= 40;\n", " if c >= 'A' and c <= 'Z':\n", " encoded += ord(c) - ord('A') + 1\n", " elif c >= '0' and c <= '9':\n", " encoded += ord(c) - ord('0') + 27\n", " elif c == '-':\n", " encoded += 37\n", " elif c == '/':\n", " encoded += 38\n", " elif c == '.':\n", " encoded += 39\n", " else:\n", " pass # invalid\n", "\n", " # Convert the integer value to a byte array.\n", " result = bytearray()\n", " for i in range(6):\n", " result.append(encoded & 0xFF)\n", " encoded >>= 8\n", "\n", " return result\n", "\n", " @staticmethod\n", " def decode_callsign_base40(encoded_bytes):\n", " \n", " # Convert byte array to integer value.\n", " i,h = struct.unpack(\"IH\", encoded_bytes)\n", " encoded = (h << 32) | i\n", " print('{:#012x}'.format(encoded))\n", "\n", " # Unpack each base-40 digit and map them to the appriate character.\n", " result = io.StringIO()\n", " while encoded:\n", " result.write(\"xABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.\"[encoded % 40])\n", " encoded //= 40;\n", "\n", " return result.getvalue();\n", " \n", " @staticmethod\n", " def to_4fsk(symbol):\n", " \"\"\"Convert a pair of bits to a 4-FSK symbol.\"\"\"\n", " if symbol == 0: return 1\n", " elif symbol == 1: return 3\n", " elif symbol == 2: return -1\n", " elif symbol == 3: return -3\n", " else: raise ValueError(\"not a value 4-FSK symbol\")\n", "\n", " @staticmethod\n", " def binary_to_symbols(bits):\n", " \"\"\"Return an array of binary symbols (bit pairs) to 4-FSK symbols.\"\"\"\n", " return np.array([to_4fsk(x) for x in bits], dtype=np.int8)\n", "\n", " @staticmethod\n", " def byte_to_symbols(data):\n", " \"\"\"Convert byte to big endian symbol stream.\"\"\"\n", " result = np.zeros(4, dtype=np.uint8)\n", " for i in range(4):\n", " result[i] = (data & 0xC0) >> 6\n", " data = data << 2\n", " return result\n", "\n", " @staticmethod\n", " def bytes_to_symbols(data):\n", " binary_symbols = np.concatenate(np.array([byte_to_symbols(x) for x in data]))\n", " return binary_to_symbols(binary_symbols)\n", " \n", " @staticmethod\n", " def to_bit_array(byte_array):\n", " \"\"\"Convert byte array to big-endian bit array.\"\"\"\n", " return np.concatenate(\n", " [[int((x & (1<>8) & 0xFF, f & 0xFF])\n", "\n", "\n", " # Puncture matrix for link setup frame.\n", " P1 = [1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0,\n", " 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0,\n", " 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1,\n", " 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,\n", " 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,\n", " 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1]\n", "\n", " # Puncture matrix for data frames.\n", " P2 = [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1,\n", " 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,\n", " 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,\n", " 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]\n", " \n", " # Randomization matrix.\n", " DC = [0xd6, 0xb5, 0xe2, 0x30, 0x82, 0xFF, 0x84, 0x62,\n", " 0xba, 0x4e, 0x96, 0x90, 0xd8, 0x98, 0xdd, 0x5d,\n", " 0x0c, 0xc8, 0x52, 0x43, 0x91, 0x1d, 0xf8, 0x6e,\n", " 0x68, 0x2F, 0x35, 0xda, 0x14, 0xea, 0xcd, 0x76,\n", " 0x19, 0x8d, 0xd5, 0x80, 0xd1, 0x33, 0x87, 0x13,\n", " 0x57, 0x18, 0x2d, 0x29, 0x78, 0xc3]\n", "\n", " # Sync word.\n", " SW = [0x32, 0x43]\n", "\n", " def __init__(self, mycall, rate = 48000):\n", " self.mycall = mycall\n", " self.MYCALL = self.encode_callsign_base40(mycall)\n", " self.frame_type = bytearray([0x00,0x05])\n", " self.nonce = bytearray([0x00]*14)\n", " \n", " self.symbol_rate = 4800.0\n", " self.samples_per_second = rate\n", " self.samples_per_symbol = int(self.samples_per_second / self.symbol_rate);\n", " self.taps = int(self.samples_per_symbol * 8 + 1)\n", " self.delay = int((self.taps) // 2)\n", " self.rrc_filter = rrcosfilter(self.taps, 0.5, 1.0/self.symbol_rate, self.samples_per_second)[1][1:]\n", " self.zl = lfiltic(self.rrc_filter, 1.33, [], [])\n", " self.crc = CRC16(0x5935, 0xFFFF)\n", " self.memory = np.array([4])\n", " self.trellis = Trellis(self.memory, np.array([[0o31,0o27]]))\n", " self.interleaver = PolynomialInterleaver(45, 92, 368)\n", " self.dc = self.to_bit_array(self.DC)\n", " self.sw = self.to_bit_array(self.SW)\n", " self.golay = Golay24()\n", "\n", " def transmit(self, audio, tocall = None):\n", " \n", " self.tocall = tocall\n", " self.TOCALL = bytearray([0xff] * 6) if tocall is None else encode_callsign_base40(tocall)\n", " self.frame_number = 0\n", "\n", " yield self.generate_preamble()\n", " yield self.generate_link_setup()\n", " \n", " c2 = pycodec2.Codec2(3200)\n", " for i in range(0, len(audio), c2.samples_per_frame() * 2):\n", " start = i\n", " end = start + c2.samples_per_frame()\n", " chunk = audio[start:end]\n", " if len(chunk) != c2.samples_per_frame():\n", " chunk = np.concatenate([chunk, np.zeros(c2.samples_per_frame() - len(chunk), dtype=chunk.dtype)])\n", " encoded = c2.encode(chunk)\n", " start += c2.samples_per_frame()\n", " end += c2.samples_per_frame()\n", " chunk = audio[start:end]\n", " if len(chunk) != c2.samples_per_frame():\n", " chunk = np.concatenate([chunk, np.zeros(c2.samples_per_frame() - len(chunk), dtype=chunk.dtype)])\n", " encoded += c2.encode(chunk)\n", " yield self.generate_audio_frame(encoded)\n", " \n", " yield self.generate_audio_frame()\n", " yield self.generate_end()\n", " \n", " def upsample(self, input):\n", " \"\"\"Upsample (interpolate) the input by the 'n'. This does\n", " nothing more that duplicate each symbol n times. It can\n", " also provide a gain factor.\"\"\"\n", "\n", " return np.concatenate([[x] + [0] * (self.samples_per_symbol - 1) for x in input])\n", "\n", " def filter(self, symbols):\n", " result, self.zl = lfilter(self.rrc_filter, 1.33, self.upsample(symbols), -1, self.zl)\n", " return result\n", " \n", " def generate_preamble(self):\n", " \"\"\"Generate the baseband signal for the preamble.\"\"\"\n", " \n", " preamble_bytes = np.array([0x77]*48, dtype=np.uint8)\n", " preamble_symbols = bytes_to_symbols(preamble_bytes)\n", " return to_bit_array(preamble_bytes), self.filter(preamble_symbols)\n", " \n", " def generate_end(self):\n", " \"\"\"Generate nothing for the last frame.\"\"\"\n", " \n", " end_bytes = np.array([0x00]*48, dtype=np.uint8)\n", " end_symbols = bytes_to_symbols(end_bytes)\n", " return to_bit_array(end_bytes), self.filter(end_symbols)\n", "\n", " def generate_link_setup(self):\n", " \"\"\"Generate the baseband signal for the link setup frame.\"\"\"\n", " \n", " self.crc.reset()\n", " self.crc.crc(self.MYCALL)\n", " self.crc.crc(self.TOCALL)\n", " self.crc.crc(self.frame_type)\n", " self.crc.crc(self.nonce)\n", " block_crc = self.crc.get_bytes()\n", " link_setup_block = np.concatenate([self.MYCALL, self.TOCALL, self.frame_type, self.nonce, block_crc])\n", " link_setup_bits = self.to_bit_array(link_setup_block)\n", " self.SF = np.split(link_setup_block, 6)\n", " for i in range(6):\n", " self.SF[i] = np.concatenate([self.SF[i], ([i << 5])])\n", " self.sf_index = 0\n", " # Encode and puncture\n", " encoded = self.puncturing(conv_encode(link_setup_bits, self.trellis, 'term'), self.P1)\n", " interleaved = self.interleaver.interleave(encoded)\n", " randomized = np.bitwise_xor(interleaved, self.dc)\n", " complete_frame = np.concatenate([self.sw, randomized])\n", " symbols = [self.to_4fsk(x*2+y) for x, y in np.split(complete_frame, len(complete_frame) // 2)]\n", " # print(symbols)\n", " return complete_frame, self.filter(symbols)\n", "\n", " def generate_audio_frame(self, audio = None):\n", " \"\"\"Generate the baseband signal for the data frame.\"\"\"\n", " \n", " LICH = self.SF[self.sf_index]\n", " self.sf_index += 1\n", " if self.sf_index == len(self.SF):\n", " self.sf_index = 0\n", " lich = self.to_bit_array(LICH)\n", " encoded_lich = np.concatenate([self.golay.encode_array(x) for x in np.split(lich, 4)])\n", " fn = self.to_byte_array(self.frame_number | 0x8000 if audio is None else 0)\n", " self.frame_number += 1\n", " if self.frame_number == 0x8000:\n", " self.frame_number = 0\n", " self.crc.reset()\n", " self.crc.crc(fn)\n", " data = audio if audio is not None else bytearray([0] * 128)\n", " self.crc.crc(data)\n", " block_crc = self.crc.get_bytes()\n", " \n", " frame_block = fn + data + block_crc\n", " frame_bits = to_bit_array(frame_block)\n", " frame_fec = self.puncturing(conv_encode(frame_bits, self.trellis, 'term'), self.P2)\n", " frame_full = np.concatenate([encoded_lich, frame_fec])\n", " interleaved = self.interleaver.interleave(frame_full)\n", " randomized = np.bitwise_xor(interleaved, self.dc)\n", " complete_frame = np.concatenate([self.sw, randomized])\n", " symbols = [self.to_4fsk(x*2+y) for x, y in np.split(complete_frame, len(complete_frame) // 2)]\n", " return complete_frame, self.filter(symbols)\n", " \n", "# Write out baseband modulation as \"m17-4fsk.wav\".\n", "modulator = M17Modulator(\"WX9O\")\n", "sample_rate, audio = wavfile.read(\"brain.wav\")\n", "baseband_data = np.concatenate([baseband for bits, baseband in modulator.transmit(audio)])\n", "# Convert to 16-bit integer\n", "audio_output = np.array(baseband_data * 1000, np.int16)\n", "wavfile.write(\"m17-4fsk.wav\", 48000, audio_output)\n", "\n", "# Write out the encoded bitstream as \"m17.bin\"\n", "modulator = M17Modulator(\"WX9O\")\n", "modulator_bits = np.concatenate([bits for bits, baseband in modulator.transmit(audio)])\n", "def to_byte(bits):\n", " x = byte(0)\n", " for bit in bits:\n", " x <<= 1\n", " x |= bit\n", " return x\n", "modulator_bytes = bytearray([to_byte(bits) for bits in np.split(modulator_bits, len(modulator_bits) // 8)])\n", "open(\"m17.bin\", \"wb\").write(modulator_bytes)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above code has encoded and modulated the audio file. It has\n", "produced two output files: the `m17-4fsk.wav` file containing\n", "the baseband modulation, and `m17.bin` containing the encoded\n", "bits. `m17.bin` can be used as a reference to calculate the\n", "bit error rate (BER) of the demodulated data.\n", "\n", "# Animation\n", "\n", "As one final step, we will animate the baseband waveform." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "
\n", " \n", "
\n", " \n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", "
\n", "
\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "from matplotlib import animation, rc\n", "from IPython.display import HTML\n", "\n", "class Animation(object):\n", " \n", " def __init__(self):\n", "\n", " self.sample_rate = 48000.0\n", " self.modulator = M17Modulator(\"WX9O\", self.sample_rate)\n", " x, self.audio = wavfile.read(\"brain.wav\")\n", " self.generator = self.modulator.transmit(self.audio)\n", " self.symbol_rate = 4800.0\n", " self.samples_per_symbol = int(self.sample_rate / self.symbol_rate)\n", " self.symbols = 48.0\n", " self.duration = self.symbols / self.symbol_rate # 48 symbols, 1/4 of a frame\n", " self.samples_per_frame = int(self.symbols * self.samples_per_symbol)\n", " self.t = np.linspace(0, self.duration, self.samples_per_frame, endpoint=False)\n", " plt.figure()\n", " plt.rcParams['figure.figsize'] = [9, 4]\n", "\n", " self.fig, self.ax = plt.subplots(1, sharex=True)\n", " plt.xlabel('Symbol Periods ({}/sec)'.format(self.symbol_rate))\n", " plt.title(\"Modulated Data\")\n", " plt.rcParams[\"animation.html\"] = \"jshtml\"\n", " plt.rcParams[\"animation.embed_limit\"] = 20.0\n", " self.ax.grid(which='major', alpha=0.5)\n", " plt.xticks(np.arange(0, self.duration, self.duration / self.symbols), rotation=45)\n", " self.ax.set_xticklabels(np.arange(0, int(self.symbols), 4))\n", " self.ax.set_ylim(-4, 4)\n", "\n", " plt.locator_params(axis='y', nbins=9)\n", " plt.locator_params(axis='x', nbins=self.symbols / 4)\n", "\n", " def init(self):\n", " self.fsk = np.zeros(self.samples_per_frame)\n", " self.fsk_line, = self.ax.plot(self.t, self.fsk, '-', label='4-FSK signal')\n", " self.fsk_line.set_data(self.t, self.fsk)\n", " return (self.fsk_line,)\n", "\n", " def animate(self, i):\n", " \n", " if (i % 4) == 0:\n", " self.fsk = next(self.generator)[1]\n", " self.start = 0\n", " \n", " self.end = self.start + self.samples_per_frame\n", " self.fsk_line.set_data(self.t, self.fsk[self.start:self.end])\n", " self.start += self.samples_per_frame\n", " return (self.fsk_line,)\n", "\n", "a = Animation()\n", "# call the animator. blit=True means only re-draw the parts that have changed.\n", "anim = animation.FuncAnimation(\n", " a.fig, a.animate, init_func=a.init,\n", " frames=100, interval=1000/24, blit=True)\n", "\n", "HTML(anim.to_jshtml())\n", "\n", "# metadata = dict(title='M17', artist='Mobilinkd LLC', comment='audio stream')\n", "# anim.save('animation.webm', writer='ffmpeg', fps=60, metadata=metadata, codec='libvpx-vp9')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" } }, "nbformat": 4, "nbformat_minor": 2 }