package us.achromaticmetaphor.imcktg;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Locale;
public class MorsePCM extends ToneGenerator {
private static final int secondsPerMinute = 60;
private static final int defaultFrequency = 800;
private static final int defaultWordsPerMinute = 20;
private static final int defaultRepeatCount = 0;
public static void main(String [] argVector) throws IOException {
(new MorsePCM()).writeWithWavHeader(System.out, argVector[0]);
}
private final int sampleRate;
private byte [] [] samplevec;
private byte [] silent;
private final int samplesPerPulse;
private final int freq;
private final int wpm;
private final int repeatCount;
public MorsePCM() {
this(defaultRepeatCount);
}
public MorsePCM(int repeatCount) {
this(defaultWordsPerMinute, repeatCount);
}
public MorsePCM(int wpm, int repeatCount) {
this(defaultFrequency, wpm, repeatCount);
}
public MorsePCM(int freq, int wpm, int repeatCount) {
this(freq, freq * 10, wpm, repeatCount);
}
public MorsePCM(int freq, int sampleRate, int wpm, int repeatCount) {
this.sampleRate = sampleRate;
this.freq = freq;
this.wpm = wpm;
this.repeatCount = repeatCount;
samplesPerPulse = secondsPerMinute * sampleRate / wpm / Morse.unitsPerWord;
silent = new byte [samplesPerPulse];
Arrays.fill(silent, Byte.MAX_VALUE);
samplevec = new byte [sampleRate / freq] [samplesPerPulse];
final double scale = Math.PI * 2 * freq / sampleRate;
for (int i = 0; i < samplevec.length; i++)
for (int j = 0; j < samplevec[i].length; j++)
samplevec[i][j] = (byte) (Byte.MAX_VALUE + (Byte.MAX_VALUE * Math.sin((i * samplevec[i].length + j) * scale)));
}
public void writeWithWavHeader(OutputStream out, String s) throws IOException {
MorseWriter writer = new MorseWriter(out);
Iterable<String> mcs = Morse.morse(s);
writer.writeWavHeader(Morse.numPulses(mcs) * samplesPerPulse * (repeatCount + 1));
writer.writeMorse(mcs);
writer.flush();
}
public void writeWithHeaders(OutputStream out, String s) throws IOException {
MorseWriter writer = new MorseWriter(out);
Iterable<String> mcs = Morse.morse(s);
final int nsamples = Morse.numPulses(mcs) * samplesPerPulse * (repeatCount + 1);
writer.writeHttpHeaders(nsamples, to83(s));
writer.writeWavHeader(nsamples);
writer.writeMorse(mcs);
writer.flush();
}
private static String to83(String s) {
s = s.toLowerCase(Locale.getDefault()).replaceAll("[^abcdefghijklmnopqrstuvwxyz0123456789]", "");
return s.length() > 8 ? s.substring(0, 8) : s;
}
private static void mSHORTle(byte [] b, int offset, short s) {
b[offset] = (byte) s;
b[offset+1] = (byte) (s >> 8);
}
private static void mSHORTle(byte [] b, int offset, int s) {
mSHORTle(b, offset, (short) s);
}
private static void mINTle(byte [] b, int offset, int i) {
mSHORTle(b, offset, i);
mSHORTle(b, offset + 2, i >> 16);
}
private class MorseWriter {
private OutputStream out;
private int pulsesWritten;
public MorseWriter(OutputStream out) {
this.out = new BufferedOutputStream(out);
pulsesWritten = 0;
}
public void writeWavHeader(int samples) throws IOException {
byte [] wav = new byte [44];
mINTle(wav, 0, 0x46464952); // RIFF
mINTle(wav, 4, samples + wav.length - 8); // length of rest of stream
mINTle(wav, 8, 0x45564157); // WAVE
mINTle(wav, 12, 0x20746d66); // fmt<sp>
mINTle(wav, 16, 16); // size of "fmt " subchunk
mSHORTle(wav, 20, 1); // audio format
mSHORTle(wav, 22, 1); // number of channels
mINTle(wav, 24, sampleRate); // sample rate
mINTle(wav, 28, sampleRate); // byte rate
mSHORTle(wav, 32, 1); // bytes per sample * number of channels
mSHORTle(wav, 34, 8); // bits per sample
mINTle(wav, 36, 0x61746164); // data
mINTle(wav, 40, samples); // size of "data" subchunk
out.write(wav);
}
public void writeHttpHeaders(int nsamples, String fnhint) throws IOException {
final int filelen = nsamples + 44;
OutputStreamWriter writer = new OutputStreamWriter(out);
writer.write("Content-Type: audio/wav\n");
writer.write("Content-Length: " + filelen + "\n");
writer.write("Content-Range: bytes 0-" + (filelen - 1) + "/" + filelen + "\n");
writer.write("Content-Disposition: filename=\"" + fnhint + ".wav\"\n");
writer.write("\n");
writer.flush();
}
private void writeSamplePulse() throws IOException {
out.write(samplevec[pulsesWritten]);
pulsesWritten++;
pulsesWritten %= samplevec.length;
}
private void writeSilentPulse() throws IOException {
out.write(silent);
}
private void writePulse(int pulse) throws IOException {
if (pulse == 0)
writeSilentPulse();
else
writeSamplePulse();
}
private void writePulses(int n, int pulses) throws IOException {
while (n-- > 0) {
writePulse(pulses & 1);
pulses >>= 1;
}
}
private void writeMorseChar(char c) throws IOException {
writePulses(c == '-' ? 4 : 2, c == '.' ? 1 : c == '-' ? 7 : 0);
}
private void writeMorseString(String mcs) throws IOException {
for (int i = 0; i < mcs.length(); i++)
writeMorseChar(mcs.charAt(i));
writePulses(2, 0);
}
public void writeMorse(Iterable<String> morse) throws IOException {
for (int i = 0; i <= repeatCount; i++)
for (String s : morse)
writeMorseString(s);
}
public void flush() throws IOException {
out.flush();
}
}
@Override
public void writeTone(OutputStream out, String s) throws IOException {
writeWithWavHeader(out, s);
}
@Override
public String filenameExt() {
return ".wav";
}
@Override
public String filenameTypePrefix() {
return "RIFF.WAV:" + freq + ":" + wpm + ":" + repeatCount + ":";
}
}