fsk

clean-up: #54

for a few projects in the past i had to communicate data bidirectionally via sound. it involved for example hooking up a microcontroller to an ipod touch (running supercollider) so that the device could read sensors and/or control leds. one successful method was to do frequency-shift keying. see here for another blog entry on that.
i've published fsk pure data code for that before, and also some supercollider code for generating/encoding fsk, but never the decoding part i think.
so below is some old code i've cleaned up a bit. it just demonstrates sending audio via internal sc busses at a very low baudrate. for good baudrate and min/max frequency settings see here

(
s.latency= 0.05;
s.waitForBoot{
        SynthDef(\redFSKdecode, {|in= 20, thresh= 0.1, baudrate= 126, lo= 882, hi= 1764|
                var sig= InFeedback.ar(in, 1);
                var sigActive= Amplitude.ar(sig, 0.01, 0.01)>thresh;
                var fre= 1/Timer.ar(sig*sigActive);
                var trg= Schmidt.ar(fre, lo+(baudrate*0.5), hi-(baudrate*0.5)); //0= lo, 1= hi
                var trgLo= trg<0.5;
                var trgHi= 1-trgLo;
                var trgCarr= Sweep.ar(trgHi, 1)>(1/baudrate*16); //when found more than 16bits high in a row
                var trgData= SetResetFF.ar(trgLo*trgCarr, trgCarr);
                var imp= Phasor.ar(trgData, baudrate/SampleRate.ir);
                var writeTrg= (imp-Delay1.ar(imp))<0+Impulse.ar(0);
                var writePos= PulseCount.ar(writeTrg, trgData);
                var writeVal= Median.ar(31, trg);
                var ok, done= 1-trgData;
                var buf= LocalBuf(13).clear;
                Demand.ar(writeTrg, 0, Dbufwr(writeVal, buf, writePos, 0));
                ok= done*(1-Demand.ar(done, 0, Dbufrd(buf, 1, 0)))*Demand.ar(done, 0, Dbufrd(buf, 11, 0));
                SendReply.ar(ok, '/data', Demand.ar(ok, 0, Dbufrd(buf, (2..9), 0)));
                DC.ar(0);
        }).add;
        SynthDef(\redFSKencode, {|out= 0, amp= 0.6, minFreq= 4900, maxFreq= 7350, invBaudrate= 0.008|
                var data= Control.names([\data]).ir(Array.fill(8, 0));
                var env= EnvGen.ar(Env(#[1, 1, 0], [invBaudrate*(16+11), 0]), doneAction:2);
                var parity= data.sum+1;
                var freq= Duty.ar(Dseq([invBaudrate*16]++invBaudrate.dup(11)), 0, Dseq(#[1, 0]++data++[parity%2, 1]));
                var src= SinOsc.ar(freq*(maxFreq-minFreq)+minFreq, 0, amp);
                OffsetOut.ar(out, src*env);
        }).add;
};
)

//test sending a simple counter
(
var baudrate= 126;
var lo= 882;
var hi= 1764;
OSCFunc({|msg|
        var byte;
        //("received:"+msg[3..]).postln;
        byte= msg[3..].sum{|x, i| 2**i*x};
        ("received:"+byte).postln;
}, '/data');
Routine.run({
        Synth(\redFSKdecode, [\in, 20]); //bus 20
        inf.do{|i|
                var byte= (i%256).asInteger;
                var data= byte.asBinaryDigits.reverse;
                s.bind{
                        ("sending :"+byte).postln;
                        Synth(\redFSKencode, [\minFreq, lo, \maxFreq, hi, \data, data, \out, 20, \invBaudrate, 1/baudrate]); //bus 20
                        Synth(\redFSKencode, [\minFreq, lo, \maxFreq, hi, \data, data, \out, 0, \invBaudrate, 1/baudrate]); //monitor
                };
                (1/baudrate*(11+16)).wait;
        };
})
)

//test a string of text
(
var baudrate= 126;
var lo= 882;
var hi= 1764;
OSCFunc({|msg|
        var byte;
        //("received:"+msg[3..]).postln;
        byte= msg[3..].sum{|x, i| 2**i*x};
        ("received:"+byte.asInteger.asAscii).postln;
}, '/data');
Routine.run({
        Synth(\redFSKdecode, [\in, 20]); //bus 20
        "hello supercollider!".do{|x|
                var byte= x.ascii.clip(0, 255);
                var data= byte.asBinaryDigits.reverse;
                s.bind{
                        //("sending :"+x+byte+data).postln;
                        Synth(\redFSKencode, [\minFreq, lo, \maxFreq, hi, \data, data, \out, 20, \invBaudrate, 1/baudrate]); //bus 20
                        Synth(\redFSKencode, [\minFreq, lo, \maxFreq, hi, \data, data, \out, 0, \invBaudrate, 1/baudrate]); //monitor
                };
                (1/baudrate*(11+16)).wait;
        };
})
)

this fsk technique was used in many projects to control leds. e.g. most of the 3rd gen projects listed here are running supercollider on an ipod touch with the 6 channels led brightness data being sent via fsk on one of the audio output channels.