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 /f0blog/soft-modem/ 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 SuperCollider busses at a very low baud rate. For good baud rate and min/max frequency settings see code.google.com/p/arms22/source/browse/trunk/SoftModem/SoftModem.h.
(
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 on RHYME 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.