DSP tutorial: RTTY decoder using FFT
Last modified: March 4th, 2012This 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)
About me
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with.
... »
Trackback URL
No comments yet.
Trackback responses to this post