Linux Software

On Linux, a single program does encoding and decoding.

Source Download

The program is only available as source. You must compile it yourself which is very easy.

Source code for Linux: ebnaut.c.

Compile the program with something like

gcc -std=gnu99 -Wall -O3 -o /usr/local/bin/ebnaut ebnaut.c -lm -lpthread
You will need to be super user, or on Ubuntu or similar, prefix the command with sudo. There should be no errors or warnings from the compiler.

The command ebnaut -? will give you a summary of the command line options.

You can give the decoder a quick try with the weak demo signal in demo.vt. It contains a 32 character message at 2700.1 Hz encoded with 8K19A using 6.25mS symbols. Decode with

vtmult -f2700.1 demo.vt |
   vtresample -r800 |
   vtraw -oa |
   ebnaut -dp8K19A -N32 -S0.00625 -r800 -PS -c2 -L20000 -v
View the spectrogram with
vtrsgram -ox demo.vt
Can you see the signal?


ebnaut only generates an encoded message, it does not drive any output ports. You must arrange a program to modulate your carrier by some means. If you want to both encode and send, you can use the Windows ebnaut-tx.exe which works fine under Wine.

To generate an encoded message, pipe the text into ebnaut with a -et option. For example

   echo 'TEST MESSAGE' | ebnaut -et -N12 -p 4K19A > encoded.txt
You must specify the number of characters with a -N option and the coding with a -p option. You can also supply the message with a -M instead of using stdin:
   ebnaut -et -N12 -p 4K19A -M 'TEST MESSAGE' > encoded.txt
The message must of course be quoted if it contains any white space.

The file encoded.txt will contain (NC * 6 + 16 + K - 1)/R digits, where NC is the message length, R is the rate of the convolutional code and K is the constraint length. The example above produces 4 * (12 * 6 + 16 + 19 - 1) = 424 digits. Each digit is an ASCII character '0' or '1' and there is no newline character at the end.


The decoder expects a two or three column ASCII data stream to supply the input signal. The data stream represents a baseband I/Q signal pair with an optional timestamp in column one (which is not used).

The examples given assume you are using vlfrx tools for signal processing, and that you are using vtcard, vttime, and vtwrite to record raw timestamped received signal into a local directory, say /raw.

To decode a signal, first work out the message length using the Signal Calculator. For example a 12 character message using code 8K19A with 5 second symbols has a duration of 4240 seconds. Use vtread to extract the signal from your signal database. Begin the extraction 60 seconds early and continue until 60 seconds or so after the message ends. Therefore add 120 or more seconds to the duration. Use vtfilter and vtblank to remove sferics, vtmult to mix down to baseband and vtresample to reduce the sample rate. Then vtraw to produce ASCII output to pipe into ebnaut -d.

For example, if a transmission at 8270Hz began at 23:00 on the 4th Feb 2015, use the following pipeline to run the decoder

   vtread -T2015-02-04_22:59:00,+4360 /raw | # Begin 60 secs early
      vtfilter -h bp,f=8270,w=3000 |         # Pre-filter before blanking
      vtblank -a20 -d0 -t50 |                # Typical daytime sferic blanker options
      vtmult -f 8270 |                       # Mix to baseband I/Q
      vtresample -r 240 |                    # Reduce to 240 sample pairs/sec
      vtraw -oa |                            # Convert to 3-columns of ASCII
      ebnaut -d -N12 -p 8K19A -S5 -r240 -c4 -L20000 -PS -v -T60  # Decode
The extra 60 seconds in front of the message gives the blanker time to settle its automatic threshold, and also gives you some room to adjust the start time via the ebnaut -T option to allow for some error in the transmit bit clock.

If your signal database /raw contains two channels from a pair of orthogonal loops, you will want to insert a vtmix additive mixer stage between vtread and vtfilter. For example, if you have channel one carrying an east/west loop signal and channel two carrying a north/south signal, and a signal arriving from, say, 223 degrees, you would mix with

   vtread ... /raw |            # Extract a 2 channel stream from /raw
      vtmix -c -0.682,-0.731 |  #  sin(223) = -0.682,  cos(223) = -0.731
      vtfilter ...
In practice you usually need to 'aim off' to get the best S/N, except when you're lucky enough to have the background noise coming in broadside to the wanted signal.

If you have any interruptions to your recording in the signal database due to timing breaks or other problems, insert a vtcat -p stage into the pipeline immediately after vtread. This will pad over the timing breaks with zero samples to maintain the overall timing.

It is worth playing with the beam heading and blanker settings to squeeze out the best S/N in any situation. It is helpful for this if the transmitter sends some carrier before the message. You can examine this in a narrow bandwidth with vtnspec and refine the setting to maximise the carrier S/N.

You can also decode a signal exported by WAV file from Spectrum Lab. Use the Sox utility to convert WAV to ASCII (known as 'dat' format in Sox terminology). You have to remove the headers from the dat output. Sox introduces each header line with a semicolon which makes them easy to remove with, say, sed:

   sox signal.wav -t dat - |   # Convert WAV to ASCII
     sed '/;/d' |              # Remove the unwanted header that Sox creates
     ebnaut -d ....
This assumes that Spectrum Lab did all the sferic blanking and mixing to baseband I/Q before recording the WAV file.

You will probably want to write shell scripts to automate the signal extraction and decoding. You can also script something to search for the optimum beam and blanker settings.

If you find that the decoder wants to use more memory than your available RAM, then use an online computing service such as AWS EC2. AWS is very easy to use and cheap too if you use the spot requests. It only takes a couple of minutes to start up a large computer (up to 36 cores and 240 Gbyte of RAM) and install ebnaut. Gzip the output of vtraw -oa and scp this to the AWS computer. You can decode messages of 100 characters or more with the strongest code 16K25A and use list sizes up to 20 million.

Diagnostic Functions

Some functions are in ebnaut which may be helpful if you're experimenting with your own polynomials.

Catastrophic Code Check

Option -f3 checks the given polynomial for catastrophic cycles. If a catastrophic cycle is detected, an error message is printed and ebnaut exits with non-zero exit status. Otherwise the exit status is zero.

  ebnaut -f3 -p 07575751,05643523,05366627,07066421 && echo 'no catastrophic cycles'
  ebnaut -f3 -p 16K25A && echo 'no catastrophic cycles'

Interleave Map

Option -f6 outputs two columns of integers representing the interleaving map.

  ebnaut -f6 -N6 -p 4K17A | less
The first column is the position in the transmitted/received signal sequence. The second column is the offset into the encoded message bits.

Distance Spectrum

The cascaded code is linear so the terms 'distance spectrum' and 'weight spectrum' are synonymous.

Option -f8 outputs the complete distance spectrum for the specified number of message characters.

   ebnaut -f8 -N4 -p 8K17A > dist.spec
The program encodes every possible message with the specified length and accumulates a histogram of the encoded weights. The output file is two columns: column one is the code weight and column two is the number of messages with that weight. It is not reasonable to try to run this with more than about 5 characters.

For longer message lengths, the distance spectrum can be sampled using random messages. The -f7 option encodes random messages of the given length and outputs the resulting code weight. It continues until stopped and you have to build the histogram yourself. For example

   ebnaut -f7 -N12 -p 8K23A | awk '{
      if( NR == 1000000) exit
      for( i in cnt) print i, cnt[i]
   }' | sort -g > distance.spec

The above options -f7 and -f8 report the weights of the cascaded code. Options -f13 and -f14 perform the same functions respectively, but operate only on the inner convolutional code.

Extended decoding options

By default the decoder will only report a decode if its log likelihood exceeds that of any earlier decode in the phase search. Backward passes will end early once the log likelihood under consideration drops below that of the current best decode. This can be turned off with a -f9 option. The decoder will then report all valid messages up to the list length given by -L. Sometimes this will enable the operator to spot a correct decode further down the list which has been beaten by a false decode of higher likelihood.

An option -f10, which is only useful for diagnostics and code testing, instructs the decoder to output all entries in the Viterbi output list. The output with -f10 is tabular, five space separated columns:

column 1:   List position, ascending from zero;
column 2:   Weight of the corresponding codeword;
column 3:   Number of symbol errors (relative to list entry zero);
column 4:   Symbol error fraction;
column 5:   The log-likelihood value;

The option is very useful to examine the lower few entries of the weight spectrum, even for large blocks. Because the list decoder outputs codewords in descending order of likelihood, a noise-free input signal will produce a list in increasing weight order. For example, to examine the closest 19999 codewords to a 'TEST' message:

echo 'TEST' | ebnaut -ep8K19A -N4 -t | ebnaut -dp8K19A -N4 -t -L20000 -f10 > temp
You will see that temp lists 38 codes of hamming weight 92 relative to the correct codeword at the head of the list. There are 110 of weight 94, 145 of weight 145, and so on up to 3157 codewords of weight 118. The code is linear so the hamming distance spectrum is the same no matter what test message you use. If you use an 'all star' message '****', columns 2 and 3 will be the same, ie the hamming distance relative to codeword zero is the same as the weight.

-f10 can be used up to the max list length of 20,000,000 but will be very slow because of the degenerate tree in the list decoder (a large number of codewords of identical log-likelihood). You can speed it up considerably by introducing a small amount of noise.

echo 'TEST' | ebnaut -ep8K19A -N4 -r1 -S1 -n60 | ebnaut -dp8K19A -N4 -r1 -S1 -L20000000 -f10 > temp
Because of the noise, the outputs are no longer guaranteed to have increasing hamming distance, although of course the now noisy log-likelihoods still decrease monotonically.

Performance Testing

The output of the encoder can be piped directly into the decoder to test the performance. A -n option to the encoder turns on noise generation and the argument is the required Eb/N0 in dB.

   echo 'test message' |
      ebnaut -e -N12 -p 8K17A -S1 -r120 -n0.5 |
      ebnaut -d -N12 -p 8K17A -S1 -r120 -PS -L1000 -v
The noise is pseudorandom and repeats exactly each time it is run. To randomly seed the noise generator, append 's' to the noise amplitude:
   echo 'test message' |
      ebnaut -e -N12 -p 8K17A -S1 -r120 -n0.5s |
      ebnaut -d -N12 -p 8K17A -S1 -r120 -PS -L1000 -v
Then the noise, and the outcome, will be different each time it is run. The 'transmitted' reference phase is always zero so -PS can be replaced with -P0 to turn off reference phase search. A simple script can be arranged to build up performance statistics.

Signal Analysis

An option -f16 turns on a signal analysis mode. This is used to investigate failed decodes. It relies on the operator knowing what the message was. -f16 tells the decoder to pretend it got the decode and to analyse the signal for BER, Eb/N0 and carrier strength and phase, assuming the known message.

You must use a -M option to tell the decoder what the expected message is, and it is helpful to also give -f15 to disable use of the initial reference phase.

For example, generate a test message too weak to have a chance of decoding:

   echo 'test message' | ebnaut -ep8K19A -N12 -r1 -S1 -n -5.0 > temp
The Eb/N0 of -5dB is far too weak to decode but we can use -f16 to analyse the received signal:
   ebnaut -dvp8K19A -N12 -r1 -S1 -f15 -f16 -M 'test message' < temp