On Linux, a single program does encoding and decoding.
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 -lpthreadYou 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 -vView the spectrogram with
vtrsgram -ox demo.vtCan 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.txtYou 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.txtThe 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 # DecodeThe 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.
Some functions are in ebnaut which may be helpful if you're experimenting with your own polynomials.
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'
Option -f6 outputs two columns of integers representing the interleaving map.
ebnaut -f6 -N6 -p 4K17A | lessThe first column is the position in the transmitted/received signal sequence. The second column is the offset into the encoded message bits.
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.specThe 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 '{ cnt[$1]++ if( NR == 1000000) exit }END{ 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.
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 > tempYou 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 > tempBecause 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.
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 -vThe 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 -vThen 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.
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 > tempThe 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