In the previous notebook, we designed a simple frame structure for aligning data into packets which can be decoded independently. The frame consisted of a header and a payload, where the payload was protected with a channel code and a checksum to ensure successful detection. We implemented the packet creating and decoding in a Packeting
class.
In this notebook, we will use this class and all the previous blocks to create an end-to-end digital transmission using your soundcard. However, in this notebook, we will keep the transmitted data constant. We will extend the system to textual data received from a UDP stream in the next notebook.
First, let's have some standard imports:
import matplotlib
import matplotlib.pyplot as plt
%matplotlib ipympl
%load_ext autoreload
%autoreload 2
import numpy as np
import sys; sys.path.append('..')
try:
%load_ext tikzmagic
except ModuleNotFoundError:
print ("Did not find tikzmagic. You will not be able to compile the tex code!")
from audioComms import Environment, TX
from audioComms.utils import get_filter, StatefulFilter, get_filter
from audioComms.buffers import RingBuffer
from audioComms.channels import AudioChannel, SimulatedChannel, IdentityChannelEffect, HighpassChannelEffect
from audioComms.plotting import PlotEyeDiagram
from audioComms.components import Component, Upconversion, Downconversion, TimingRecovery
from audioComms.packeting import Packeting
Then, let's define our transmitter. For now, we fix the payload it transmits to a given value of [1,1,1,1,0,0,0,0,1,1,0,0,1,0,1,0]
. The principle is simple: Whenever the transmitter is asked to generate a transmit signal, it creates a frame with the constant payload given in the createFrame
method.
# Source code has been redacted in online version
# Omitting 35 lines of source code
At the receiver side, we create a component which reads a stream of bits and attempts to decode the packets from the stream. In order to do this, for each received bit the decoder is run on the last N bits, where N is the length of one PHY packet. If the decoder found a packet, the contained payload data is forwarded to the next component:
class DetectFrames(Component):
def __init__(self, environment, packeting):
super().__init__(environment, None)
self._buffer = RingBuffer(packeting._packetLength, dtype=np.int8)
self._packeting = packeting
def receive(self, data):
bits = np.sign(data) == -1
for b in bits:
self._buffer.add(np.array([b]))
d = self._buffer.data()
packet = self._packeting.decodePacket(d)
if packet is not None:
self._send(packet)
As the data sink, we simple write a component that just prints out the bits in a received frame:
class PrintFrameBits(Component):
def __init__(self, environment):
super().__init__(environment, None)
def receive(self, data): # Called for each received Frame
print ("Received Frame bits:", data)
Already now, we can set up our transmission chain. Within the chain, we use all the blocks from the previous notebooks: Upconversion, Downconversion and Timing Recovery. Pictorially, we have the following connections, with the blocks relevant from this notebook marked:
%%tikz -l positioning, --size=800,240
\input{tex/08-1.tex}
# Source code has been redacted in online version
# Omitting 28 lines of source code
Let's try our system in the simulated channel. If everything works well, the PrintFrames
component should print frames with exactly the payload we setup in the TransmitSignal
component.
runTransmission(18000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
Great! After some settling of the internal gain parameters, the eye diagram stabilizes and the receiver prints out the expected payload! We have successfully created a rudimentary digital transmission system!
Let's try the system in the real audio channel:
runTransmission(8000, AudioChannel)
Yeah! It also works over the audio channel. After moving the microphone towards the speaker and letting the gains settle, the eye diagram stabilizes. Then, the frames are correctly detected!
In this notebook, we have successfully integrated our packet encoding and decoding into the transmission system. With this packeting mechanism, we are now able to really transmit digital information over our audio channel.
In the next notebook, we will extend this system to receive data from a UDP network stream, such that we can control the transmitted payload on the fly.
Copyright (C) 2025 - dspillustrations.com