|
|
|
For Week 2, I give you...: "Morse Listener!!" (code, examples, readme) |
// HOWDY!!! This is a Morse Code Listener/Decoder
// by Perry R. Cook, for Month of Code, May 2016
//
// Uses quadrature heterodyne (WOAH!) filter to detect tones.
// There are lots of ways to do this, like a narrow-
// bandpass filter. Turns out that multiplying the
// signal by sine and cosine at one frequency, running
// those outputs through a low-pass filter, then taking
// the magnitude of those (sum of squares), actually IS
// a very narrow bandpass filter, tuned to that exact freq.
// We need both sine and cosine to cover all possible
// phases (time delay between mic and speaker) we might see.
// Those of you who know about Fourier Transforms will note
// that we're computing 1 bin (the one we want) of a DFT.
// Run this using > chuck MorseListen.ck
// while running MorseSlower.ck as well.
// Adjust volume(s) (make sure mic can "hear" speaker)
// for lowest error rate. Try typing "5" and "0" to
// make sure you get "....." and "-----". Then have fun!!
// You can also adjust the THRESHOLD level below.
// Note that reverb/echoes in the room will mess the timings
// up, so adjust things just until they work right. Or
// go into a less reverberant room. Note also that you can
// confuse (or talk to) the MorseListener by just whistling
// the correct tone. So you shouldn't have noise or music or
// cats meowing or other stuff going on in the background.
// Not very robust, but still pretty cool, right?
// What we've created is actually a MOdulatorDEModulator (MODEM),
// which codes ASCII into acoustic beeps, while the receiver
// receives/detects those beeps and turns them back into ASCII.
// In fact, we could use 2 tones, one for dots, one for dashes,
// and make a MODEM that's possibly more robust and faster. We
// could even use three tones (silence, dot, dash).
// We might do that next week!!
0 => int VERBOSE; // print more diagnostics
<<< "Morse Code Listener Ready!!!", "" >>>;
adc => BPF band => Gain in => Gain smult => OnePole sp => Gain smag => Gain mag => blackhole;
in => Gain cmult => OnePole cp => Gain cmag => mag;
880 => int BASE_FREQ => band.freq; 2 => band.Q; // First band pass filter a little bit
sp => smag; cp => cmag; // squarers
SinOsc s => smult; 0.0 => s.phase; // sine squared +
SinOsc c => cmult; pi/2 => c.phase; // cosine squared = full phasor
3 => smult.op => cmult.op => smag.op => cmag.op;
BASE_FREQ => s.freq => c.freq;
0.999 => sp.pole => cp.pole;
4*ms => dur PERIOD; // this is our listening period (1/10 DOTLENGTH)
0.04 => float DOTLENGTH; // fundamental (smallest) time period
// NOTE: DOT and DASH Aren't Slower, just Char Silences
3*DOTLENGTH => float DASHLENGTH; // dash is 3x dot
DOTLENGTH => float DOTSILENCE; // silence between symbols within char
2*DASHLENGTH => float CHARSILENCE; // 2x3dot silence between characters
7*DOTLENGTH => float WORDSILENCE; // 7dot silence between words
0.001 => float THRESHOLD; // You'll have to fiddle with this (or volumes)
0 => int WATCHING; // state variable for watching tones
0.0 => float onTime; // how long tone is on
0.0 => float offTime; // how long silence is around
0 => int STATE; // 0=Between Chars, 1=Within Word -1=Between Words
"" @=> string myChar; // We use this to assemble our characters
0 => int charPoint;
1 => int notDone;
spork ~ watchSilence(); // one shred to keep track of silence
while (notDone) { // main loop just looks for tone
PERIOD => now;
if (mag.last() > THRESHOLD) { // then launches toneWatcher if there
spork ~ watchTone();
}
}
fun void watchSilence() {
while (notDone) {
PERIOD => now;
PERIOD/second +=> offTime;
// NOTE HERE YOU COULD ASSEMBLE CHARACTERS INTO WORDS IF YOU LIKE
if (offTime > WORDSILENCE*2) { // END OF WORD
if (STATE == 0) { // if just finished up character
if (VERBOSE) <<< "WordEnd", "" >>>; // then print out word end
chout <= "\n"; chout.flush();
-1 => STATE;
}
}
else if (offTime > CHARSILENCE) { // END OF CHARACTER
if (STATE) {
whatsThis(myChar) => string theChar; // look it up
if (VERBOSE) <<< "END OF CHAR:", myChar, theChar >>>; // print it out
chout <= myChar+" "+theChar+" "; chout.flush();
"" => myChar; // reset for next char
0 => charPoint => STATE;
}
}
}
}
fun void watchTone() {
if (!WATCHING) {
0.0 => onTime;
1 => STATE;
1 => WATCHING;
0.0 => offTime;
while (WATCHING) {
if (mag.last() > THRESHOLD) {
PERIOD => now;
PERIOD/second +=> onTime;
}
else {
0 => WATCHING;
if (onTime > 1.33*DOTLENGTH) { // Pretty Arbitrary, Hackola!!
myChar+"-" => myChar; // <<< "DASH!!" >>>;
}
else {
myChar+"." => myChar; // <<< "DOT!" >>>;
}
charPoint++;
0.0 => onTime;
}
}
}
}
fun string whatsThis(string acode) {
// Morse code patterns to match characters in MorseChars array
[".-","-...","-.-.","-..",".","..-.","--.","....","..",
".---","-.-",".-..","--","-.","---",".--.","--.-",".-.",
"...","-","..-","...-",".--","-..-","-.--","--..",
"-----",".----","..---","...--","....-",
".....","-....","--...","---..","----.",
".-.-.-","--..--","..--..","-....-","---...",".----.",
"-.--.","-.--.-","-...-",".--.-.",".-.-.","-..-."] @=> string MorseCode[];
// Characters in standard Morse Code Alphabet
["A" ,"B" ,"C" ,"D" ,"E","F" ,"G" ,"H" ,"I" ,
"J" , "K" ,"L" ,"M" ,"N" ,"O" ,"P" ,"Q" ,"R",
"S" ,"T","U" ,"V" ,"W" ,"X" ,"Y" ,"Z" ,
"0" ,"1" ,"2" ,"3" ,"4",
"5" ,"6" ,"7" ,"8" ,"9" ,
"." ,"," ,"?", "-", ":", "'",
"(", ")", "=", "@", "+", "/"] @=> string MorseChars[];
0 => int found => int point;
while (point < MorseCode.cap() & ! found) {
if (MorseCode[point] == acode) {
1 => found;
return MorseChars[point];
}
point++;
}
if (!found) return "DUNNO?!?";
}