|
|
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?!?"; }