DSP tutorial: RTTY decoder using FFT

This example is a very basic RTTY decoder, similar to the FSK decoder explained previously. It doesn’t look for a start byte, uses no bit synchronization. It oversamples 3 times (divides one bit length count of samples into 3 pieces called chunks) and decides between 0 and 1 by calculating the dominant frequency in these chunks. After the loop gathered 8 bits it reconstructs the sent ASCII byte using the Baudot code and displays it. Operates poorly even with a little noise on the channel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public void run() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); // this will store sound data

        int oneBitSampleCount = (int)Math.round(SAMPLERATE/BITSPERSEC);
        int oneChunkSampleCount = (int)Math.round(oneBitSampleCount/3);
        System.out.println("One bit length: " + 1/BITSPERSEC + " seconds, " + oneBitSampleCount + " samples");
        System.out.println("One chunk length: " + (1/BITSPERSEC)/3 + " seconds, " + oneChunkSampleCount + " samples");
        byte[] abBuffer = new byte[oneChunkSampleCount*2];
        double[] abBufferDouble = new double[oneChunkSampleCount];
        int[] chunkResults = new int[3];
        int chunkResultp = 0;
        int byteResult = 0;
        int byteResultp = 0;
        int numberOfInvalidResults = 0;

        fft = new DoubleFFT_1D(abBufferDouble.length);
        // we need to initialize a buffer where we store our samples as complex numbers. first value is the real part, second is the imaginary.
        double[] fftData = new double[abBuffer.length]; // size == samples * 2
        int binOfFreq0 = (int)Math.round((FREQ0/(double)SAMPLERATE)*fftData.length);
        int binOfFreq1 = (int)Math.round((FREQ1/(double)SAMPLERATE)*fftData.length);

        System.out.println("FFT bin of freq. 0 (" + FREQ0 + "Hz): " + binOfFreq0);
        System.out.println("FFT bin of freq. 1 (" + FREQ1 + "Hz): " + binOfFreq1);
       
        tdl.start();

        try {
            while (!Thread.interrupted()) {
                // waiting for the buffer to get filled
                while (tdl.available() < abBuffer.length)
                    Thread.sleep(0, 1); // without this, the audio will be choppy

                int bytesRead = tdl.read(abBuffer, 0, abBuffer.length);

                // converting frames stored as bytes to double values
                int samplesRead = bytesRead / 2;
                for (int i = 0; i < samplesRead; i++) {
                    abBufferDouble[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0;
                }

                for (int i = 0; i < fftData.length; i++)
                    fftData[i] = 0;
                for (int i = 0; i < samplesRead; i++) {
                    // copying audio data to the fft data buffer, imaginary part is 0
                    fftData[2 * i] = abBufferDouble[i];
                    fftData[2 * i + 1] = 0;
                }

                // calculating the fft of the data, so we will have spectral power of each frequency component
                // fft resolution (number of bins) is samplesNum, because we initialized with that value
                fft.complexForward(fftData);

                double PWRFreq0 = Math.sqrt(fftData[binOfFreq0] * fftData[binOfFreq0] + fftData[binOfFreq0+1] * fftData[binOfFreq0+1]);
                double PWRFreq1 = Math.sqrt(fftData[binOfFreq1] * fftData[binOfFreq1] + fftData[binOfFreq1+1] * fftData[binOfFreq1+1]);
               
                if (PWRFreq0 > PWRTHRESHOLD || PWRFreq1 > PWRTHRESHOLD) {
                    if (PWRFreq0*PWRFreq0-PWRFreq1*PWRFreq1 < 0)
                        chunkResults[chunkResultp] = 1;
                    else
                        chunkResults[chunkResultp] = 0;

                    //System.out.print("PWRFreq0: " + PWRFreq0 + " PWRFreq1: " + PWRFreq1 + " chunk[" + chunkResultp + "]=" + chunkResults[chunkResultp]);

                    chunkResultp++;
                    if (chunkResultp == 3) {
                        chunkResultp = 0;

                        int bitResult;
                        if (chunkResults[0] + chunkResults[1] + chunkResults[2] >= 2)
                            bitResult = 0;
                        else
                            bitResult = 1;
                        //System.out.print(" resulting bit: " + bitResult);

                        if ((byteResultp == 0 && bitResult != 1) || (byteResultp == 6 && bitResult != 0) || (byteResultp == 7 && bitResult != 0)) {
                            byteResultp = byteResult = chunkResultp = 0;
                            System.out.println("rst");
                        } else {
                            switch (byteResultp) {
                            case 0:
                            case 6:
                            case 7:
                                break;
                            default:
                                System.out.print(bitResult);
                                byteResult += (bitResult == 0 ? 1 : 0) << (byteResultp-1);
                            }
   
                            byteResultp++;
                            if (byteResultp == 8) {
                                switch (byteResult) {
                                case 31:
                                    mode = RTTYMode.letters;
                                    System.out.println();
                                    break;
                                case 27:
                                    mode = RTTYMode.symbols;
                                    System.out.println();
                                    break;
                                default:
                                    switch (mode) {
                                    case letters:
                                        System.out.println(" *** " + RTTYLetters[byteResult] + "(" + byteResult + ")");
                                        break;
                                    case symbols:
                                        System.out.println(" *** " + RTTYSymbols[byteResult] + "(" + byteResult + ")");
                                        break;
                                    }
                                }
                                byteResultp = byteResult = 0;
                            }
                        }
                    }
                    //System.out.println();
                    numberOfInvalidResults = 0;
                } else {
                    //System.out.println("invalid!");
                    numberOfInvalidResults++;
                }

                if (numberOfInvalidResults > 0) {
                    //System.out.println("reset");
                    numberOfInvalidResults = byteResultp = byteResult = chunkResultp = 0;
                }

                //System.out.println("Samples read: " + samplesRead + " sampleAfterSamples: " + bitChunkLengthInSamples);
                abBufferDouble[0] = 1;
                baos.write(getBytesFromDoubles(abBufferDouble, samplesRead), 0, samplesRead * 2);
            }
        } catch (InterruptedException e) {
        }

        tdl.stop();
        tdl.close();

        writeWavFile(baos.toByteArray(), baos.size() / 2, "output.wav");
    }

download (851.3 kb)

Name (required)
E-mail (required - never shown publicly)
Webpage URL
Comment:
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> in your comment.

Trackback responses to this post

About me

Nonoo
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with. ... »

Twitter

Listening now

My favorite artists