supercollider

example class

here is a very basic supercollider class i wrote as an example of why and how to write classes.

//save this as MySequencerTrack.sc in your extensions folder and recompile
MySequencerTrack {
        var <steps;
        var <>array;
        *new {|steps= 16|
                ^super.newCopyArgs(steps).init;
        }
        init {
                array= Array.fill(4, {Array.fill(steps, 0)});  //4 here because of the four params: amp, freq, mod, pan
        }

        //array get/set
        amps {^array[0]}
        amps_ {|arr| array[0]= arr}
        freqs {^array[1]}
        freqs_ {|arr| array[1]= arr}
        mods {^array[2]}
        mods_ {|arr| array[2]= arr}
        pans {^array[3]}
        pans_ {|arr| array[3]= arr}

        //single value get/set
        amp {|index| ^array[0][index]}
        amp_ {|index, val| array[0].put(index, val)}
        freq {|index| ^array[1][index]}
        freq_ {|index, val| array[1].put(index, val)}
        mod {|index| ^array[2][index]}
        mod_ {|index, val| array[2].put(index, val)}
        pan {|index| ^array[3][index]}
        pan_ {|index, val| array[3].put(index, val)}
}

and here is some test code for it...

a= MySequencerTrack.new;
a.freqs
a.steps
a.amps= {1.0.rand}!a.steps
a.amps
a.amp(10)  //first one
//and then the same for a.freqs etc

(
s.waitForBoot{
        a.amps= {[0.5, 0.25, 0, 0, 0].choose}!a.steps;
        a.freqs= {[60, 66, 70].choose.midicps}!a.steps;
        a.mods= {1.0.linrand}!a.steps;
        a.pans= {1.0.rand2}!a.steps;
        b= {|freq= 400, amp= 0, mod= 0, pan= 0| Pan2.ar(SinOsc.ar(freq, SinOsc.ar*mod, amp), pan)}.play;
        s.sync;
        r= Routine.run({
                inf.do{
                        a.steps.do{|i|
                                b.set(\freq, a.freq(i), \amp, a.amp(i), \mod, a.mod(i), \pan, a.pan(i));
                                0.125.wait;
                        };
                };
        });
};
)

//and while it is running...  replace freqs
a.freqs= {[52, 66, 70, 80].choose.midicps}!a.steps;
a.amp_(0, 1)  //set first amp to 1.0
a.amps
a.amps= a.amps.rotate(-1)  //rotate amps left
a.freqs= a.freqs.rotate(2) //rotate freqs right
a.freqs= a.freqs+10  //transpose up
a.freqs= a.freqs.scramble //reorder

r.stop
b.free

//now the important thing and why classes are good...
//here we make 10 tracks all 32 values in length...
~mysequencer= {MySequencerTrack(32)}!10;
~mysequencer[0].amps  //amplitudes for first track
~mysequencer[0].amps= {1.0.rand}!a.steps
~mysequencer[0].amps
~mysequencer[0].amp(0)  //first one

(
s.waitForBoot{
10.do{|i|
        var steps= ~mysequencer[i].steps;
        ~mysequencer[i].amps= {[0.5, 0.25, 0, 0, 0, 0, 0, 0].choose}!steps;
        ~mysequencer[i].freqs= {[60, 66, 70, 90].choose.midicps}!steps;
        ~mysequencer[i].mods= {1.0.linrand}!steps;
        ~mysequencer[i].pans= {1.0.rand2}!steps;
};
~synths= {
        {|freq= 400, amp= 0, mod= 0, pan= 0| Pan2.ar(SinOsc.ar(freq, SinOsc.ar*mod, amp/2), pan)}.play;
}!10;
        s.sync;
        r= ~mysequencer.collect{|trk, i|
                var syn= ~synths[i];
                Routine.run({
                        inf.do{|j|
                                var x= j%trk.steps;
                                syn.set(\freq, trk.freq(x), \amp, trk.amp(x), \mod, trk.mod(x), \pan, trk.pan(x));
                                0.125.wait;
                        };
                });
        };
};
)

//while the above is running
~mysequencer[0..7].do{|trk| trk.amps= 0!32}  //mute all tracks except 8&9
~mysequencer[8..9].do{|trk| trk.mods= {4.0.linrand}!32}  //more fmod on tracks 8&9
~mysequencer[0..1].do{|trk, i| trk.freqs= {i+1*150}!32; trk.mods= {0}!32; trk.amps= {|j| [0, 1].wrapAt(i+j)}!32} //renew and add tracks 0&1
~mysequencer[8..9].do{|trk| trk.amps= {0.75.linrand*[1, 0].choose}!32}  //new amps for track 8&9
~mysequencer[8..9].do{|trk| trk.freqs= {8.linrand+1*150}!32}  //new freqs for track 8&9
~mysequencer[6].amps= {0.4}!9++({0}!23); ~mysequencer[6].freqs= {|i| 2**i*50+50}!32; //add arpeggio on track 6

~mysequencer.do{|x| x.freqs= x.freqs*1.1} //transpose all frequencies
~mysequencer.do{|x| x.amps= x.amps.rotate(-3)} //rotate all amps

r.do{|x| x.stop}
~synths.do{|x| x.free}

greenpeace

here's a handy class for supercollider. it's an audio clipping detector loosely based on Batuhan Bozkurt's StageLimiter.

to install it download and extract the zip file into your supercollider extensions folder. then recompile and type...

GreenPeace.activate

now as soon as you play sound that's clipping (i.e. exceeds -1.0 or 1.0), the class will warn you.

to turn it off type...

GreenPeace.deactivate
AttachmentSize
Package icon GreenPeace.zip3.67 KB

spiral

just some hypnotic graphics...

the javascript code above is this...

<div style="background-color:black;">
<canvas id="can" width="800" height="600"></canvas>
<script>
var width, height;
var ctx, frameCount= 0;
(function() {
    var can= document.getElementById('can');
    ctx= can.getContext('2d');
    width= can.width;
    height= can.height;
    ctx.fillStyle= '#FFF';
    window.requestAnimationFrame(draw);
})();
function draw() {
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.translate(width*0.5, height*0.5);
    ctx.beginPath();
    var theta= Math.sin(frameCount*0.001)*Math.PI*2*4;
    for(var y= 0; y<height; y++) {
        for(var i= 0; i<10; i++) {
            ctx.rotate(theta*0.001);
            ctx.fillRect((Math.sin(y*0.1+theta+(i*2))*100), y, 2, 2);
        }
    }
    ctx.restore();
    frameCount= frameCount+1;
    window.requestAnimationFrame(draw);
}
</script>
</div>

originally this was a quick sketch made in processing...

//spiral.pde - processing
void setup() {
  size(800, 600);
  noStroke();
}
void draw() {
  background(0);
  translate(width*0.5, height*0.5);
  float theta= sin(frameCount*0.001)*TWO_PI*4;
  for(int y= 0; y<height; y++) {
    for(int i= 0; i<10; i++) {
      rotate(theta*0.001);
      rect((sin(y*0.1+theta+(i*2))*100), y, 2, 2);
    }
  }
}

and then ported to supercollider...

//spiral.scd - supercollider
(
var width= 800, height= 600;
var win= Window("spiral", Rect(100, 100, width, height), false);
var usr= UserView(win, Rect(0, 0, width, height));
usr.background= Color.black;
usr.animate= true;
usr.drawFunc= {
        var theta= sin(usr.frame*0.001)*2pi*4;
        Pen.fillColor= Color.white;
        Pen.translate(width*0.5, height*0.5);
        height.do{|y|
                10.do{|i|
                        Pen.rotate(theta*0.001);
                        Pen.fillRect(Rect(sin(y*0.1+theta+(i*2))*100, y, 2, 2));
                };
        };
};
CmdPeriod.doOnce({win.close});
win.front;
)

esp8266 opensound control

here some example arduino code for sending and receiving osc via the cheap esp8266 serial wifi module.

note that the opensound control messages here are very basic - only 4 bytes packed into a single 32bit integer.

* upload the code below to an arduino.

* connect esp8266 TX pin to arduino pin0.

* connect esp8299 RX to arduino pin1. it is safest to use a 3v3 lever converter for this line (or at least a voltage divider).

* power the esp8266 (VCC and GND) from an external 3v source. do not use the arduino 3v3pin as it cannot provide the required current. i used a LF33CV voltage regulator to get 3.3v from the 5v supply that also powers the arduino.

* connect esp8288 RESET pin to arduino pin4.

* and last connect esp8266 CH_PD to 3v3

optional: connect a separate usb-serial (ftdi) chip to arduino pins 2 and 3 to use software serial debugging. start debugging in terminal with something like screen /dev/tty.usbserial-A4015TKA 115200

the arduino code sits and waits for an incoming osc message (/tap). it then replies by sending out a counter message (/sti).

//f0 150705
//sending and receiving udp osc with an esp8266
//for an arduino + esp8266 with firmare 0.9.5.2

#include <SoftwareSerial.h>

#define WLAN_SSID  "SSID"
#define WLAN_PASS  "PASS"
#define WLAN_ADDR  "192.168.1.51" //laptop running sc
#define PORT  1112 //incoming osc port
String tag = "/tap"; //incoming osc addy

SoftwareSerial mySerial(2, 3);

uint8_t buf[16];
byte cnt;
byte id, on, hi, lo;
boolean resp;

void setup() {

  //--osc message
  buf[0] = 47;   // /
  buf[1] = 115;  // s
  buf[2] = 116;  // t
  buf[3] = 105;  // i
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;
  buf[8] = 44;   // ,
  buf[9] = 105;  // i
  buf[10] = 0;
  buf[11] = 0;
  buf[12] = 4;   // a high   (id)
  buf[13] = 3;   // a low    (on)
  buf[14] = 2;   // b high   (hi)
  buf[15] = 0;   // b low    (lo)

  mySerial.begin(115200);
  Serial.begin(115200);
  Serial.setTimeout(10000);
  mySerial.println("");
  mySerial.println("starting");

  mySerial.print("hard reset...");
  digitalWrite(4, 0);
  pinMode(4, OUTPUT);
  delay(10);
  pinMode(4, INPUT);
  resp = Serial.find("ready\r\n");
  mySerial.println(resp);

  mySerial.print("mode1...");
  Serial.println("AT+CWMODE=1");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  mySerial.print("connecting...");
  do {
    Serial.print("AT+CWJAP=\"");
    Serial.print(WLAN_SSID);
    Serial.print("\",\"");
    Serial.print(WLAN_PASS);
    Serial.println("\"");
    resp = Serial.find("OK\r\n");
    mySerial.println(resp);
  } while (!resp);

  mySerial.print("mux1...");
  Serial.println("AT+CIPMUX=1");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  mySerial.print("udp...");
  Serial.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial.print(WLAN_ADDR);
  Serial.print("\",57120,");
  Serial.print(PORT);
  Serial.println(",0");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  Serial.setTimeout(1000);
}
void loop() {
  while (Serial.available()) {
    String abc = Serial.readStringUntil('\n');
    if (abc.startsWith("+IPD,4,16:" + tag)) {
      id = abc[22];
      on = abc[23];
      hi = abc[24];
      lo = abc[25];
      mySerial.print("id:");
      mySerial.println(id);
      mySerial.print("on:");
      mySerial.println(on);
      mySerial.print("hi:");
      mySerial.println(hi);
      mySerial.print("lo:");
      mySerial.println(lo);

      buf[15] = cnt++;
      Serial.println("AT+CIPSEND=4,16");
      Serial.find(">");
      Serial.write(buf, sizeof(buf));
      resp = Serial.find("OK\r\n");
      mySerial.print("send...");
      mySerial.println(resp);
    }
  }
}

supercollider test code:

(
//--call&response
var last= Main.elapsedTime;
OSCFunc({|msg, time, addr|
        [\id, msg[1]>>24, \on, (msg[1]>>16)&255, \hi, (msg[1]>>8)&255, \lo, msg[1]&255, time-last, addr].postln;
        last= time;
}, \sti);
n= NetAddr("192.168.1.50", 1112); //esp8266 ip address
f= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
r= Routine.run({
        inf.do{|i|
                n.sendMsg(\tap, f.value(4, 1, i.asInteger>>8&255, i.asInteger%256));
                0.5.wait;
        };
});
)

supercollider firmata

clean-up: #57

just cleaned up an example for supercollider and arduino that i found on my computer. it is demonstrating the SCFirmata class by Eirik Arthur Blekesaune.

//how to read pin A0 with SCFirmata...

//for Arduino1.0.6 and SC3.6.6
//first in Arduino IDE:
//  * select File / Examples / Firmata / StandardFirmata
//  * upload this example to an arduino
//then in SC install the SCFirmata classes
//  * download zip file https://github.com/blacksound/SCFirmata
//  * extract files and put them in your sc application support directory
//  * recompile sc

SerialPort.devices
d= SerialPort.devices[0]; // or d= "/dev/tty.usbserial-A1001NeZ" - edit number (or string) to match your arduino
f= FirmataDevice(d);//if it works it should post 'Protocol version: 2.3' after a few seconds

s.boot
Ndef(\snd, {|freq= 400, amp= 0.5| SinOsc.ar([freq, freq+4].lag(0.08), 0, amp.lag(0.08)).tanh}).play;
f.reportAnalogPin(0, true)      //start reading A0
f.analogPinAction= ({|num, val| [num, val].postln; Ndef(\snd).set(\freq, val.linexp(0, 1023, 400, 800))})//control freq
f.analogPinAction= ({|num, val| [num, val].postln; Ndef(\snd).set(\amp, val.linexp(0, 1023, 0.001, 1))})//control amp instead

Ndef(\snd).stop
f.reportAnalogPin(0, false)     //stop reading A0
f.end
f.close

audioSerial

clean-up: #56

compared to generating a serial bitstream in audio, analyzing and extract serial data from audio is much harder. the supercollider code below does it, but the program has limitations and is quite sensitive for noise.

the code takes a string, chops it up into groups of six 8bit bytes and generates a serial audio bitstream from that. another part listens to this sound and tries to decode it. if it finds six full bytes it sends the result back to sclang via osc where it is printed.
to test the example connect an audio cable directly from your computer's output to its input (preferably via a small mixer), or change the audioSerial synthdef to use an internal audio bus. i can also imagine it could function with a mic next to the speakers - but i didn't test this.
if it only prints gibberish try with a different threshold setting, different volume on you computer or use a lower baud rate.

(
s.waitForBoot{
        var baudrate= 9600;
        SynthDef(\serialAudio, {|out= 0, amp= -0.5|                     //for sending out serial via audio
                var data= Control.names([\data]).kr(Array.fill(60, 0)); //max 6 bytes
                var src= Duty.ar(1/baudrate, 0, Dseq(data), 2);
                OffsetOut.ar(out, src*amp);
        }).add;
        SynthDef(\audioSerial, {|in= 0, thresh= 0.05|           //for receiving serial via audio
                var raw= 0-SoundIn.ar(in); //here change to In.ar if trying internal audio bus
                var src= raw>thresh;
                var reading= DelayC.ar(Trig1.ar(src, 1/baudrate*9), 1/baudrate/2, 1/baudrate/2);
                var osc= Phasor.ar(reading, baudrate/SampleRate.ir);
                var clock= (osc-Delay1.ar(osc))<0+Impulse.ar(0);
                var index= PulseCount.ar(clock, reading);
                var stopTrig= index>7;
                var data= Latch.ar(src, index>=#[7, 6, 5, 4, 3, 2, 1]);
                var byte= (1-data).sum{|x, i| 2**(6-i)*x};
                SendReply.ar(stopTrig, '/data', byte);
                DC.ar(0);
        }).add;
        OSCFunc({|msg| msg[3].asInteger.asAscii.post}, '/data');
        s.sync;
        Synth(\audioSerial);
};
)

(
var str= "hello supercollider!";
var baudrate= 9600;
fork{
        1.wait;
        str.ascii.clump(6).do{|bytes|
                var data= bytes.collect{|x| [1]++(1-x.asBinaryDigits.reverse)++[0]}.flat;
                s.bind{
                        Synth(\serialAudio, [\data, data]);
                };
                (1/baudrate*60+0.005).wait;
        };
};
)

one can use this technique to communicate with another computer via audio. to communicate with a microcontroller (e.g. an arduino), one needs additional electronics (amplification, rectification). here's schematics for a bi-directional circuit for talking to a 5v arduino.

this audio-to-serial technique was used to get input from rfid, touch and bend sensors in our reflect installation. i.e. sc is running on an ipod touch and receives all sensor data via audio from an atmega168 microcontroller.

serialAudio

clean-up: #55

another way (compared to fsk in my previous blog entry) of sending data via audio is to directly generate the serial bit stream using supercollider.

to test and learn about these things i first wrote and uploaded a very simple program to an arduino board. the program just transmitted the bytes 128, 10, 20, 30, 40 and 50.

//arduino testcode
void setup() {
  Serial.begin(9600);
}
void loop() {
  delay(1000);
  Serial.write(128);
  Serial.write(10);
  Serial.write(20);
  Serial.write(30);
  Serial.write(40);
  Serial.write(50);
}

then i connected the arduino serial tx pin (pin1) to the audio line-in of my laptop (via a 1k + 10k voltage divider) and recorded the sound of the serial transmission.

i then analyzed the sound by hand and wrote a little program in sc that could generate similar waveforms.

s.boot;
o= {|chr| [1]++(1-chr.asBinaryDigits.reverse)++[0]};
(
SynthDef(\serialAudio, {|amp= -0.5|     //for sending out serial via audio
        var data= Control.names([\data]).kr(Array.fill(60, 0));//max 6 bytes
        var src= Duty.ar(1/9600, 0, Dseq(data), 2);     //baudrate
        OffsetOut.ar(1, src*amp);
}).add;
)
Synth(\serialAudio, [\data, [128, 10, 20, 30, 40, 50].collect{|c| o.value(c)}.flat, \amp, -0.5]);

this screenshot show the signal recorded from the arduino in the first channel, and the supercollider generated one in the second.

after all this i could reverse the process, generate any serial data and send it back to the arduino rx pin (pin0). a small amplifier circuit in between helped to get a more stable communication going.

this serial-to-audio technique was used to control the 24 leds (6 pwm channels) in our reflect installation. i.e. sc is running on an ipod touch and sends out serial audio to an atmega168 microcontroller.

here is another example that can fade a single led by sending serial commands over audio. includes schematics for an amplifier circuit plus sc and pd example code.

and for a more advanced (actually using a much better technique) example see here

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.

Pages

Subscribe to RSS - supercollider