package com.talixa.specan.demod.ook;
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;
public class CwDemod {
/*
* How to decode CW?
* count on/off periods
* convert to morse
*/
private static boolean debug = false; // true for logging
private static boolean gui = true;
private short[] data; // pcm samples
private int wpm;
public CwDemod(String inputFile) throws IOException, RiffFormatException {
WaveFile waveFile;
if (gui) {
waveFile = SpectrumAnalyzer.readWaveFile(inputFile);
} else {
waveFile = WaveReader.readFromFile(inputFile);
}
data = SharedDSPFunctions.extractWaveFileData(waveFile);
}
public CwDemod(short[] data) {
this.data = data;
}
public String demodulate() {
int sampleSize = 11;
int sampleLowHigh = (sampleSize - 1) / 2;
// count of high/low waves
int onCount = 0;
int offCount = 0;
int shortestTransition = 9999;
Queue<Integer> countLengths = new ArrayDeque<Integer>();
for(int i = sampleLowHigh; i < data.length - sampleLowHigh; ++i) {
double avg = getAverage(data, i, sampleLowHigh);
if (avg > 10000) {
if (offCount > 10) {
//System.out.println("OFFCOUNT: " + offCount);
if (countLengths.size() != 0) {
// always want array to start with mark condition
countLengths.add(offCount);
}
offCount = 0;
}
++onCount;
} else {
if (onCount > 10) {
//System.out.println("ONCOUNT: " + onCount);
countLengths.add(onCount);
if (onCount < shortestTransition) {
shortestTransition = onCount;
}
onCount = 0;
}
++offCount;
}
}
double shortTransitionLength = shortestTransition / 8000f;
wpm = (int)Math.pow((shortTransitionLength * 1000) / 1200, -1);
debug("WPM: " + wpm);
// now we have the transition states in countLengths and the short transition length
// go through and figure out dots/dashes
boolean isMark = true;
StringBuilder morseString = new StringBuilder();
while (!countLengths.isEmpty()) {
int currentLength = countLengths.poll();
double duration = currentLength / shortestTransition;
if (isMark) {
if (duration > 2.5 && duration < 3.5) {
morseString.append("-");
} else if (duration > .8 && duration < 1.2) {
morseString.append(".");
}
} else {
if (duration > 2.5 && duration < 3.5) {
morseString.append(" ");
} else if (duration > .8 && duration < 1.2){
morseString.append("");
} else if (duration > 9.5 && duration < 10.5){
morseString.append("\n");
}
}
isMark = !isMark;
}
return MorseCodeDecoder.decodeMorse(morseString.toString());
}
public int getWpm() {
return wpm;
}
// instead of ever using a single point, average 5 points together to determine high/low
private double getAverage(short[] samples, int averagePoint, int sampleLowHigh) {
double sum = 0;
for(int sampleId = sampleLowHigh * -1; sampleId < sampleLowHigh; ++sampleId) {
sum += Math.abs(samples[averagePoint + sampleId]);
}
return sum/ (sampleLowHigh*2+1);
}
private void debug(String msg) {
if (debug) {
System.out.println(msg);
}
}
private static final String PATH_LINUX = "/home/thomas/git/specan/SpecAn/res/";
private static final String PATH_WIN = "C:\\users\\tgerlach\\git\\specan\\SpecAn\\res\\";
private static final String[] TEST_FILES = {"cw-5wpm.wav"};
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 {
CwDemod demod = new CwDemod(inputFile);
String data = demod.demodulate();
System.out.println(data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}