ChucK Code Blog, Perry Cook, May 14, 2016

May is the Month of Code!
For Week 2, I give you...:

"Morse Listener!!" (code, examples, readme)

Code only:


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