Package com.talixa.specan.demod.psk

Source Code of com.talixa.specan.demod.psk.Psk31Demod

package com.talixa.specan.demod.psk;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;

import com.talixa.audio.riff.exceptions.RiffFormatException;
import com.talixa.audio.wav.WaveFile;
import com.talixa.audio.wav.WaveReader;
import com.talixa.specan.SpectrumAnalyzer;
import com.talixa.specan.dsp.SharedDSPFunctions;

/*
* see www.arrl.org/psk31-spec
* 0 = phase reversal
* 1 = steady carrier
* To demodulate, calculate length of baud in ms, and check for phase reversal.
* If present, output 0 else output 1
* Preamble is continuous phase reversals (IE 0's)
* Postamble is continuous carrier (IE 1's)
* A minimum of two zeros exists between ALL characters
*
* Baud length = 1/31.25 = 0.032 ms
* Samples / baud = 8000/31.25 = 256
*
* To begin demod, sync on the phase reversals
* At these reversals, the amplitude of the signal should near zero.
* In fact, we don't really need to look at the phase, but rather the amplitude.
* If we observe a low point after 256 samples, we have a zero. If it's a high
* point, we have a 1. In order to stay synced, and since we know signals may
* not shift at exactly the 256 sample mark, we need to recenter on every 0.
*
* For my sample at 1900hz
* 1900 hz has a period of 0.0005263157894736842
* 1900 hz would take about 4.2105 samples to complete a single wave 
*
* A band pass filter should be applied to the data before it goes into the demod
*/
public class Psk31Demod {

  private static final int SAMPLE_RATE = 8000;    // 8K sample rate
  private static final int FREQ_DEV = 50;        // 50 hz either side of center freq
  private static final int MIN_FREQ_HIT_COUNT = 10// min hits for frequency detection
  private static final int MIN_STABLE_COUNT = 20;    // time for signal to come to full power
  private static final int REVERSAL_WINDOW = 20;    // number of bauds to check for a reversal
  private static final int ZERO_AMPLITUDE = 10;    // maximum value to be considered a zero
 
  private static boolean debug = false;        // true for logging
  private static boolean gui = true
 
  private short[] data;                // pcm samples
 
  public Psk31Demod(String inputFile) throws IOException, RiffFormatException
    WaveFile waveFile;
    if (gui) {
      waveFile = SpectrumAnalyzer.readWaveFile(inputFile);
    } else {
      waveFile = WaveReader.readFromFile(inputFile);
   
   
    data = SharedDSPFunctions.extractWaveFileData(waveFile);
  }
 
  public Psk31Demod(short[] data) {
    this.data = data;
  }
 
  public String demodulate(int centerFreq) {
    StringBuilder demodData = new StringBuilder();
   
    try {                                   
      debug("Samples in file: " + data.length);
     
      // lets start by populating an array with all the sample values
      // not very memory efficient, but it will make this more simple
      double[] samples = new double[data.length];
      for(int i = 0; i < data.length; ++i) {
        short signedSample = data[i]// two-byte samples             
        double doubleSample = ((double) signedSample)/ (Short.MAX_VALUE) * 100;
        samples[i] = doubleSample;
      }             
       
      // Step 1 - frequency detect
      // Find point in samples where frequency carrier present
      int frequencyDetectedAt = frequencyDetect(centerFreq, samples, MIN_FREQ_HIT_COUNT);
      debug("Frequency detected at " + frequencyDetectedAt);   
     
      // Step 2 - signal coming up, wait for max amplitude
      int stabilizedAt = waitToStabilize(samples, frequencyDetectedAt, MIN_STABLE_COUNT);
      debug("System stabilized at " + stabilizedAt);
     
      // Step 3 - find reversal
      int reversalAt = findReversal(samples, stabilizedAt);
      debug("Reversal at " + reversalAt + " - " + getFivePointAverage(samples, reversalAt));
                       
      // step 4 - demod 
      Queue<Integer> bitQueue = new ArrayDeque<Integer>();
      for(int i = reversalAt ; i < samples.length; i += 256) {       
        double avgEnergy = getFivePointAverage(samples, i);               
        if (avgEnergy < ZERO_AMPLITUDE) {
          i = recenter(samples, i)// make sure we are at the center of the 0
          bitQueue.add(0);
          debugBit(0);
        } else {
          bitQueue.add(1);
          debugBit(1);
        }       
      }     
     
      // Step 5 - run through varicode decoder
      int currentCode = 0// build code to send to varicode decoder
      int previousBit = -1// keep track of last and current bit since no
      int currentBit = -1// psk31 character contains two 0 beside each other
      while (!bitQueue.isEmpty()) {
        currentBit = bitQueue.poll();
        if (currentBit == 0 && previousBit == 0 && currentCode != 0) {
          currentCode = currentCode >> 1// remove last zero         
          demodData.append((char)VaricodeDecoder.psk_varicode_decode(currentCode));
          currentCode = 0;
        } else {
          currentCode = (currentCode << 1) | currentBit;
          previousBit = currentBit;         
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
   
    return demodData.toString();
 

  // When we get a 0, this function is called to verify that we are at the low point of the zero.
  // This will help to keep us on track if the bit rate changes slightly
  private int recenter(double[] samples, int start) {
    int lowPointIndex = 0;
    double lowPoint = 999;
    for(int i = -15; i < 15; ++i) {
      double avg = getFivePointAverage(samples, start + i);
      if (avg < lowPoint) {
        lowPoint = avg;
        lowPointIndex = i;
      }
    }
    return start + lowPointIndex;
  }
 
  // iterate through samples until the required frequency is found
  private int frequencyDetect(int centerFreq, double[] samples, int minHits) {
    int frequencyDetectedAt = 0;
   
    double currentWaveLength = 0;    // length of current wave
    int frequencyHits = 0;        // when we have 10 consecutive hits, we have detected frequency   
       
    double currentSample;
    double previousSample;
    for(int i = 1; i < samples.length && frequencyDetectedAt == 0 ; ++i) {
      currentSample = samples[i];
      previousSample = samples[i-1];
     
      // transition - lets measure the period of the wave
      if (currentSample >= 0 && previousSample < 0) {
        // calculation time portion to zero for previous and current wave
        double deviation = Math.abs(previousSample) + currentSample;
        double lengthFromNegativeToZero = 1.0 / deviation * previousSample;
        double lengthFromZeroToCurrent = 1.0 / deviation * currentSample;
        currentWaveLength += Math.abs(lengthFromNegativeToZero);
                 
        double currentFrequency = SAMPLE_RATE / currentWaveLength;
         
        if (currentFrequency > (centerFreq - FREQ_DEV) && currentFrequency < (centerFreq + FREQ_DEV)) {
          ++frequencyHits;           
        } else {
          if (frequencyHits > 0) {             
            frequencyHits = 0;                           
          }
        }
       
        if (frequencyHits == minHits) {
          frequencyDetectedAt = i;                 
        }                   
        currentWaveLength = lengthFromZeroToCurrent;
      } else {     
        // Did not cross 0, increment wavelength counter
        currentWaveLength += 1;
      }   
    }
    return frequencyDetectedAt;
  }
 
  // when a signal starts, it will be a tone increasing in amplitude
  // this looks for the end of that brief period of signal life
  private int waitToStabilize(double samples[], int sampleStart, int stableCount) {
    int stableCycles = 0;    // how many stable cycles in a row
    double maxAmplitude = 0// max amplitude during cycle
    int stabilizedAt = 0;    // point where stable
   
    for(int i = sampleStart ; i < samples.length && stabilizedAt == 0; ++i) {
      // check for amplitude change                     
      double amp = Math.abs(samples[i]);       
      if (amp > maxAmplitude) {
        maxAmplitude = amp;         
        stableCycles = 0;
      } else {
        ++stableCycles;
      }
      if (stableCycles == stableCount) {
        stabilizedAt = i;       
      }
    }
    debug("Max amplitude is " + maxAmplitude);
    return stabilizedAt;
  }
 
  // find a reversal within the reversal window
  // this point will serve as the beginning of the decode process
  private int findReversal(double[] samples, int startAt) {
    // a reversal should exist at the point of lowest average amplitude
    // we should find another low point at 256 bytes past the first
    int checkLength = 256 * REVERSAL_WINDOW;
    double[] averageAmplitude = new double[checkLength];
   
    // 256 samples per baud, generate all 5-point averages then find smallest
    for(int i = 0; i < checkLength; ++i) {     
      averageAmplitude[i] = getFivePointAverage(samples, startAt + i);       
    }
   
    double smallestValue = 999;
    int smallestIndex = 0;
   
    for(int i = 0; i < checkLength; ++i) {
      if (averageAmplitude[i] < smallestValue) {
        smallestValue = averageAmplitude[i];
        smallestIndex = i;
      }
    }
             
    return smallestIndex + startAt;   
  }
 
  // instead of ever using a single point, average 5 points together to determine high/low
  private double getFivePointAverage(double[] samples, int averagePoint) {
    double sum = 0;
    for(int sampleId = -2; sampleId < 2; ++sampleId) {
      sum += Math.abs(samples[averagePoint + sampleId]);       
    }
    return sum/5
  }
 
  private void debug(String msg) {
    if (debug) {     
      System.out.println(msg);
    }
  }
 
  private void debugBit(int bit) {
    if (debug) {
      System.out.print(bit);
    }
  }
 
  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 = {"psk31-1900hz.wav", "SK01-061228-BPSK.wav"};
  private static final int[] CENTER_FREQS = {1900, 1023}
 
  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]
      try {
        WaveFile wave = WaveReader.readFromFile(inputFile);
        short[] data = SharedDSPFunctions.extractWaveFileData(wave);
        Psk31Demod demod = new Psk31Demod(data);
        String demodData = demod.demodulate(CENTER_FREQS[i])
        System.out.println(demodData);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
TOP

Related Classes of com.talixa.specan.demod.psk.Psk31Demod

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.