Package com.talixa.specan.demod.fsk

Source Code of com.talixa.specan.demod.fsk.RttyDemod

package com.talixa.specan.demod.fsk;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayDeque;
import java.util.Queue;

import com.talixa.audio.wav.WaveFile;
import com.talixa.audio.wav.WaveReader;
import com.talixa.specan.SpectrumAnalyzer;
import com.talixa.specan.demod.fsk.BaudotDecoder.Mode;

/*
* This implementation of an RTTY demod determines ones and zeros based on
* the period of the waveform. This works very well for my test data but needs
* much more testing on a wide range of samples. Before entering the demod,
* it would be better if the sample was passed through a narrow band filter
* to block out unwanted frequencies.
*
* Input data is assumed to be sampled at 8K and have 7.5 bits per frame
*
* For my sample, with spikes at 930 and 1100, I made the following calculations
*
* 50 bits per second = each bit lasts 2 milliseconds (1/50 = .02)
* 2 milliseconds = 160 samples / bit (8000*.02)       
* 3 milliseconds = 1.5 stop bits (2ms*1.5)
* Our sample has spikes at aprox 930 and 1100 hz
* 930 hz * .02 = 18.6 waves for a bit
* 1100 hz * .02 = 22 waves for a bit
* 930 hz has a period of 0.001075268817204301
* 1100 hz has a period of 0.0009090909090909091
* 930 hz would take about 8.60215 samples to complete a single wave
* 1100 hz would take about 7.272727 samples to complete a single wave 
* Conclusions we can make:
* if transition > 8, low freq
* if transition < 8, high freq
* if 18 consecutive low freq, add 0 bit
* if 22 consecutive high freq, add 1 bit
*
* I have tried to generalize these calculations for use on other frequency pairs.
* This algorithm would work best on lower frequencies where the difference
* between low and high would be far more pronounced.  As such, a further
* enhancement would be to downshift the data so that the low frequency is at 100
* hz and the high freq - 170 hz up - would be at 270hz - nearly three times as
* high. These larger values would create a much better demodulation.   
*
* A band pass filter should be applied to the data before it goes into the demod
*/
public class RttyDemod {     
   
  private static final int SAMPLE_RATE = 8000;  
  private static final int BITS_PER_FRAME = 7;
  private static final double WAVE_COUNT_MOI = 0.9;
 
  private String inputFile;
  private String outputFile;
  private static boolean debug = false
  private static boolean gui = true;
  private Queue<Byte> baudotData = new ArrayDeque<Byte>();
 
  public RttyDemod(String inputFile, String outputFile) {
    this.inputFile = inputFile;
    this.outputFile = outputFile;
  }
 
  public RttyDemod(String inputFile) {
    this.inputFile = inputFile;
    this.outputFile = null;
  }
 
  private void doDemodulation(int lowFreq, int highFreq, double baudRate) {
    double baudLength = 1f / baudRate;
    double samplesPerBit = SAMPLE_RATE * baudLength;
    double samplesPerStop = samplesPerBit * 1.5;
    double wavesFor0 = lowFreq * baudLength;
    double wavesFor1 = highFreq * baudLength;
    double periodLow = 1f / lowFreq;
    double periodHigh = 1f / highFreq;
    double samplesPerWaveLow = SAMPLE_RATE * periodLow;
    double samplesPerWaveHigh = SAMPLE_RATE * periodHigh;
   
    if (debug) {
      System.out.println("Sample rate: " + SAMPLE_RATE);
      System.out.println("Baud Rate: " + baudRate + " bps");
      System.out.println("Baud Length: " + baudLength + " s");
      System.out.println("Samples per bit: " + samplesPerBit);
      System.out.println("Samples per stop: " + samplesPerStop);
      System.out.println("Waves for 0 bit: " + wavesFor0);
      System.out.println("Waves for 1 bit: " + wavesFor1);
      System.out.println("Low freq period: " + periodLow);
      System.out.println("High freq period: " + periodHigh);
      System.out.println("Samples per low wave: " + samplesPerWaveLow);
      System.out.println("Samples per high wave: " + samplesPerWaveHigh);
    }
   
    double spaceThresholdLow = samplesPerWaveLow  - .6;
    double spaceThresholdHigh = samplesPerWaveLow + .6;
    double markThresholdLow = samplesPerWaveHigh  - .65;
    double markThresholdHigh = spaceThresholdLow;
     
    try
      FileOutputStream fos = null;
      if (outputFile != null) {
        File output = new File(outputFile);
        fos = new FileOutputStream(output);
      }
     
      Queue<Integer> bitQueue = new ArrayDeque<Integer>();
     
      WaveFile waveFile;
      if (gui) {
        waveFile = SpectrumAnalyzer.readWaveFile(inputFile);
      } else {
        waveFile = WaveReader.readFromFile(inputFile);
      }
     
      if (waveFile == null) {
        return;
      }
     
      short signedSample = (short)waveFile.getSampleAt(0)// two-byte samples             
      double previousSample = ((double) signedSample)/ ((1 << waveFile.getFormatChunk().getBitsPerSample()) / 2);
      double currentSample;
     
      int consecutiveLows = 0;    // number of waves of low frequency
      int consecutiveHighs = 0;    // number of waves of high frequency
      double currentWaveLength = 0// length of current wave
      boolean startOutput = false// true after first stop bit encountered
      int bitsSinceLastStop = 0;    // keep track of bits/frame so we don't loose sync
               
      for(int i = 1; i < waveFile.getNumberOfSamples(); ++i) {
        signedSample = (short)waveFile.getSampleAt(i*2)// two-byte samples         
        currentSample = ((double) signedSample)/ ((1 << waveFile.getFormatChunk().getBitsPerSample()) / 2);       
       
        // if we crossed from negative to positive, the magic starts!
        if ((currentSample >= 0 && previousSample < 0)) {
         
          // deviation = abs(low) + high
          double deviation = Math.abs(previousSample) + currentSample;
          double previousFrameToZeroLength = 1.0 / deviation * previousSample;
          double currentFrameFromZeroLength = 1.0 / deviation * currentSample;
         
          // calculate total length of current wave including fractional portion from this iteration
          currentWaveLength += Math.abs(previousFrameToZeroLength);                             
                 
          // is the wavelength within the margin of error for a mark?
          if (currentWaveLength >= markThresholdLow && currentWaveLength <= markThresholdHigh) {           
            consecutiveLows = 0;
            ++consecutiveHighs;
           
            // enough highs to be a 1?
            if (consecutiveHighs >= Math.floor(wavesFor1 - WAVE_COUNT_MOI)) {
              bitsSinceLastStop++;
              bitQueue.add(1);
              consecutiveHighs = 0;
              if (debug) System.out.print(1);
            }
           
          // is the wavelength within the margin of error for a space?
          } else if (currentWaveLength >= spaceThresholdLow && currentWaveLength <= spaceThresholdHigh) {
           
            // was the previous iteration a half-bit? (IE stop?)
            if (consecutiveHighs >= (wavesFor1/2.0)) { 
              // Try to prevent slip by inserting a 1 so we stay in sync
              if(bitsSinceLastStop != BITS_PER_FRAME) {               
                if (debugSystem.out.print("S");               
                bitQueue.add(1)
              }
              if (debugSystem.out.println("X");
              // if we didn't start outputting yet, now's the time!
              if (!startOutput) {
                startOutput = true;
                bitQueue.clear();
             
              bitsSinceLastStop = 0;
            }   
                   
            consecutiveHighs = 0;
            ++consecutiveLows;
           
            // enough lows to be a 0?
            if (consecutiveLows >= Math.floor(wavesFor0 - WAVE_COUNT_MOI)) {
              bitsSinceLastStop++;
              bitQueue.add(0);
              consecutiveLows = 0;
              if (debug) System.out.print(0);
            }           
          } else {
            // Decode error
            if (debug) System.out.print("*");
          }
           
          // add the fractional portion to this wave
          currentWaveLength = currentFrameFromZeroLength;
        } else {
          // Did not cross 0, increment wavelength counter
          currentWaveLength += 1;
        }
       
        previousSample = currentSample;
           
        // if we have a byte, output it
        if (fos != null) {
          // writing to output
          while (bitQueue.size() >= 8 && startOutput) {
            int newByte =
                bitQueue.poll() << 7 |
                bitQueue.poll() << 6 |
                bitQueue.poll() << 5 |
                bitQueue.poll() << 4 |
                bitQueue.poll() << 3 |
                bitQueue.poll() << 2 |
                bitQueue.poll() << 1 |
                bitQueue.poll() << 0;
           
           
            fos.write((byte)newByte);           
          }
        } else {
          // direct decode
          while(bitQueue.size() >= 7 && startOutput) {
            // remove start bit
            bitQueue.poll();
           
            // reverse bits
            int baudotByte =
                bitQueue.poll() << 0 |
                bitQueue.poll() << 1 |
                bitQueue.poll() << 2 |
                bitQueue.poll() << 3 |
                bitQueue.poll() << 4;
           
            // remote stop bit
            bitQueue.poll();
            baudotData.add((byte)baudotByte);
          }
        }
      }         
     
      if (fos != null) {
        fos.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }         
  }
 
  public void demodulate(int lowFreq, int highFreq, double baudRate) {
    doDemodulation(lowFreq, highFreq, baudRate);
  }
 
  public String demodulateBaudot(int lowFreq, int highFreq, double baudRate) {
    doDemodulation(lowFreq, highFreq, baudRate);
    byte[] data = new byte[baudotData.size()];
    int lastIndex = 0;
    while(!baudotData.isEmpty()) {
      data[lastIndex++] = baudotData.poll();
    }   
    BaudotDecoder decoder = new BaudotDecoder(data, Mode.US);
    return decoder.decode();
  }
 
  private static final String PATH_WIN = "C:\\Users\\tgerlach\\git\\specan\\SpecAn\\res\\";
  private static final String PATH_LINUX = "/home/thomas/git/specan/SpecAn/res/";
  private static final String[] TEST_FILES = {"rtty-170-45.wav", "rtty-170-50.wav", "rtty-425-50.wav", "rtty-850-50.wav"};
  private static final int[]    TEST_LOW  = { 920930800585};
  private static final int[]    TEST_HIGH = {1090, 1100, 1230, 1430};
  private static final double[] TEST_BAUD = {45.45, 50, 50, 50};
 
  public static void main(String[] args) {
    //debug = true;   
    testDemod();           
 
 
  public static void testDemod() {
    boolean usingWindows = System.getProperty("os.name").toLowerCase().contains("windows");
    String path;
    if (usingWindows) {
      path = PATH_WIN;
    } else {
      path = PATH_LINUX;
    }
   
    gui = false;
    for(int i = 0; i < TEST_FILES.length; ++i) {
      System.out.print("***********************");
      System.out.print(TEST_FILES[i]);
      System.out.println("***********************");
      String inputFile = path + TEST_FILES[i];         
      RttyDemod demod = new RttyDemod(inputFile);
      String data = demod.demodulateBaudot(TEST_LOW[i], TEST_HIGH[i], TEST_BAUD[i]);       
      System.out.println(data);           
   
  }
}
TOP

Related Classes of com.talixa.specan.demod.fsk.RttyDemod

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.