DSP tutorial: RTTY decoder using FFT & DPLL

This example is still a basic RTTY decoder, similar to the previously discussed one, the main difference is that it looks for the start bit and keeps bit sync using a simple but efficient DPLL algorithm. Like before, it uses the oversample & FFT & average method for bit decision making.

Problems with decoding using FFT

Note that this method would work well if we could increase the oversample count to more higher rates (decrease the size of the chunks).

The main problem is that if you use even such a low bitrate as 45.45 bauds, and 48kHz sampling rate, one bit is only 1056 samples long. If you are oversampling 4 times, one chunk will be only 1056/4 = 264 samples long. It means that you will have 264 bins after the FFT, representing the 0-24kHz range, so one bin will hold the spectral power of a 24000/264 = 91 Hz wide band.

If you are using the standard 170 Hz shift for RTTY decoding this means you have the two carriers in neighboring bins. So if you increase the oversampling, or decrease the sample rate the two carriers will be in the same bin and you won’t be able to separate them.

The DPLL algorithm

The algorithm is described in detail in the RTTY decoding using an IIR filter section of my tutorials. In this case it uses chunks instead of samples (the demodulator returns bit values of chunks).

Source code

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// reads a chunk from the sound device
    public int getChunk(double[] chunkBuffer) {
        byte[] abBuffer = new byte[oneChunkSampleCount*2];
        int samplesRead = 0;

        // waiting for the buffer to get filled
        try {
            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
            samplesRead = bytesRead / 2;
            for (int i = 0; i < samplesRead; i++) {
                chunkBuffer[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0;
            }
        } catch (InterruptedException e) {
        }
        return samplesRead;
    }

    // analyzes a chunk with FFT and returns the bit value based on the averaged spectral power at the mark and space freqs
    public int demodulator() {
        int samplesRead = getChunk(chunkBuffer);

        baos.write(getBytesFromDoubles(chunkBuffer, samplesRead), 0, samplesRead * 2);  // writing to the output wav for debugging purposes

        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] = chunkBuffer[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-PWRFreq1 < 0)
                return 1;
            else
                return 0;
        }
        return -1;
    }

    // this function returns at the half of a bit with the bit's value
    public int getBitDPLL() {
        boolean chunkPhaseChanged = false;
        int chunkVal = -1;
        int chunkPhase = 0;

        while (chunkPhase < CHUNKCOUNTPERBIT) {
            chunkVal = demodulator();
            if (chunkVal == -1)
                break;

            if (!chunkPhaseChanged && chunkVal != oldChunkVal) {
                if (chunkPhase < CHUNKCOUNTPERBIT/2)
                    chunkPhase++; // early
                else
                    chunkPhase--; // late
                chunkPhaseChanged = true;
            }
            oldChunkVal = chunkVal;
            chunkPhase++;
        }

        // putting a tick to the output wav signing the moment when the DPLL returned
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        return chunkVal;
    }
   
    // this function returns only when the start bit is successfully received
    public void waitForStartBit() {
        int bitResult;

        while (!Thread.interrupted()) {
            do {
                bitResult = demodulator();
            } while ((bitResult == 0 || bitResult == -1) && !Thread.interrupted());
           
            //System.out.println("sb0: 1");
           
            do {
                bitResult = demodulator();
            } while ((bitResult == 1 || bitResult == -1) && !Thread.interrupted());
           
            //System.out.println("sb1: 0");

            // waiting half bit time
            for (int i = 0; i < CHUNKCOUNTPERBIT/2; i++)
                bitResult = demodulator();

            //System.out.println("sb2: " + bitResult);

            if (bitResult == 0)
                break;
        }
        //System.out.println("start bit ok");
    }

    @Override
    public void run() {
        tdl.start();

        int byteResult = 0;
        int byteResultp = 0;
        int bitResult;
       
        while (!Thread.interrupted()) {
            waitForStartBit();
           
            System.out.print("0 "); // first bit is the start bit, it's zero
           
            // reading 7 more bits
            for (byteResultp = 1, byteResult = 0; byteResultp < 8; byteResultp++) {
                bitResult = getBitDPLL();
                if (bitResult == -1) {
                    byteResult = -1;
                    break;
                }

                switch (byteResultp) {
                    case 6: // stop bit 1
                        System.out.print(" " + bitResult);
                        break;
                    case 7: // stop bit 2
                        System.out.print(bitResult);
                        break;
                    default:
                        System.out.print(bitResult);
                        byteResult += bitResult << (byteResultp-1);
                }
            }

            if (byteResult == -1)
                continue;

            switch (byteResult) {
                case 31:
                    mode = RTTYMode.letters;
                    System.out.println(" ^L^");
                    break;
                case 27:
                    mode = RTTYMode.symbols;
                    System.out.println(" ^F^");
                    break;
                default:
                    switch (mode) {
                    case letters:
                        System.out.println(" *** " + RTTYLetters[byteResult] + "(" + byteResult + ")");
                        break;
                    case symbols:
                        System.out.println(" *** " + RTTYSymbols[byteResult] + "(" + byteResult + ")");
                        break;
                    }
            }
        }

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

download (853.3 kb)

Jacek 2019-12-11 12:08:50

Can your code be used to decode RTTY baud = 200
data bits = 7
stop bits = 2. I tried to modify your code but without success. Any advice?

 
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