supercollider

dragspel

this board is using an old raspberry pi 1 to control the speed of computer fans. the electronics are pretty simple (see attached schematics below): it takes 7-36V input power, has twelve mosfets for pwm control and finally a dc/dc converter to power the rpi.
it was built for controlling pc cooling fans but can also drive other types of dc motors, lightbulbs or solenoids.
the off button is there to safely power down the raspberry pi.

the trick with this though is that the system can be livecoded over wifi using supercollider, maxmsp or any other osc capable program. so when you start the board the rpi sets up a wireless access point and starts a python script that accepts incoming opensoundcontrol messages. at startup the rpi1 will also start supercollider and load a file (dragspelFans.scd) that is meant to contain whatever code you'd like to run as default. this file you later overwrite with your own sc code that you've developed/livecoded using your laptop.

dragspel

below are step-by-step instructions on how i set this up plus the relevant python and supercollider code. it should work on all rpi models but here the rpi1 or rpi0 is assumed.

//--preparation
* download and install raspbian-stretch-lite onto a 2gb sd card
* to enable ssh create an empty file on the sd card. call it ssh. (this terminal command touch /Volumes/boot/ssh will do it on osx or just create an empty textfile and save it without any file extension)
* connect your rpi to your home router via ethernet and type the following in terminal on your laptop:
* ssh-keygen -R raspberrypi.local
* ssh pi@raspberrypi.local #default password is raspberry
* sudo raspi-config #change password to _____, set memory split to 16 under advanced, change hostname to fans under network, update, finish and reboot (sudo reboot)
* ssh pi@fans.local #log in again from your laptop
* sudo apt-get update
* sudo apt-get upgrade
* sudo apt-get dist-upgrade

//--python
this section will install osc and gpio libraries for python and also set up the python script to automatically start at system boot.
* sudo apt-get install python-liblo pigpio python-pigpio
* sudo crontab -e #and add the following line at the end (use ctrl+o and ctrl+x to save and exit):

@reboot /usr/bin/pigpiod -s 5 && /usr/bin/python /home/pi/dragspelFans.py

* nano ~/dragspelFans.py #and copy&paste in the following:

#f.olofsson2016-2018
#pwm control for 12 fans/motors/leds

#NOTE: make sure to run this in terminal first...
# sudo pigpiod -s 5

import sys
from os import system
from time import sleep
import pigpio
from liblo import *

inport= 9999  #for osc commands to this python script
pinoff= 2  #bcm numbering
pins= [3, 4, 14, 15, 17, 18, 27, 22, 23, 24, 10, 9]  #bcm numbering - more can be added here
target= ('127.0.0.1', 57120)  #for osc to sclang
hz= 800  #pwm frequency in hz - note may need to adapt -s option in sudo pigpio -s 5 above
range= 100  #duty cycle range 0 to 100

pi= pigpio.pi()
pi.set_mode(pinoff, pigpio.INPUT)  #no internal pullup needed
for pin in pins:
  pi.set_mode(pin, pigpio.OUTPUT)
  pi.set_PWM_frequency(pin, hz)
  pi.set_PWM_range(pin, range)
  pi.set_PWM_dutycycle(pin, 0)

class MyServer(ServerThread):
        def __init__(self):
                ServerThread.__init__(self, inport)
        @make_method('/pwms', 'i'*len(pins))
        def pwms_callback(self, path, args):
                #print args  #debug
                i= 0
                for pin in pins:
                        pi.set_PWM_dutycycle(pin, min(max(0, args[i]), range))
                        i= i+1
        @make_method('/shutdown', '')
        def shutdown_callback(self, path, args):
                stop('sudo halt -p')  #turn off rpi
        @make_method('/reboot', '')
        def reboot_callback(self, path, args):
                stop('sudo reboot')  #reboot rpi
        @make_method('/start', '')
        def start_callback(self, path, args):
                send(target, '/start', 1)  #start default program in supercollider
        @make_method('/stop', '')
        def stop_callback(self, path, args):
                send(target, '/stop', 0)  #stop default program in supercollider
                for pin in pins:  #and also set all pwm to 0
                        pi.set_PWM_dutycycle(pin, 0)
        @make_method(None, None)
        def fallback(self, path, args):
                print 'received unknown message "%s"' % path

def stop(cmd):
        pi.stop()
        server.stop()
        system('killall pigpiod sclang')
        system(cmd)
        sleep(1)
        sys.exit()

try:
        server= MyServer()
except ServerError, err:
        print str(err)
        sys.exit()
server.start()

def main():
        while True:
                if pi.read(pinoff)==0:
                        print 'shutting down...'
                        stop('sudo halt -p')
                sleep(0.5)

if __name__ == '__main__':
        try:
                main()
        except KeyboardInterrupt:
                pi.stop()

again use ctrl+o and ctrl+x to save and exit. now sudo reboot and then try to send osc commands to the rpi. here's how to send some test osc messages from your laptop to the rpi using supercollider...

n= NetAddr("fans.local", 9999);
n.sendMsg(\pwms, *[50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])  //the number of integers should match the number of pins and range in your python code (here 12 pins, 0-100)
n.sendMsg(\pwms, *[25, 50, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0])  //first pin 25%, second %50 third 75%, rest 0
n.sendMsg(\pwms, *[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])  //all off

you can also try to connect pin bcm2 to ground. that should now act like an off button and turn off the rpi in a safe way.

//--supercollider
this section is optional. only install supercollider if you want to run your rpi as a standalone installation or something similar. so if you plan to always remote control the system you can skip over this step.
note: this is for rpi0&rpi1, for rpi2&rpi3 change all references to supercolliderStandaloneRPI1 below to supercolliderStandaloneRPI2
see https://github.com/redFrik/supercolliderStandaloneRPI1#stretch-lite for more details (this page also show how to install jackd if you need audio from your rpi).
* sudo apt-get install libqt5webkit5 libqt5sensors5 libqt5positioning5 libfftw3-bin libcwiid1 git libasound2-dev libsamplerate0-dev libsndfile1-dev libreadline-dev xvfb libjack-jackd2-0
* cd ~
* git clone https://github.com/redFrik/supercolliderStandaloneRPI1 --depth 1
* mkdir -p ~/.config/SuperCollider
* cp supercolliderStandaloneRPI1/sc_ide_conf_temp.yaml ~/.config/SuperCollider/sc_ide_conf.yaml
* cd supercolliderStandaloneRPI1
* nano autostart.sh #and change the script to look like this:

#!/bin/bash
./sclang -a -l sclang.yaml ../dragspelFans.scd

* nano share/user/startup.scd #and add the following two lines:

OSCFunc({"/home/pi/dragspelFans.scd".load}, \start).permanent= true;
OSCFunc({CmdPeriod.run}, \stop).permanent= true;

* mkdir share/user/Extensions
* nano share/user/Extensions/DragspelFans.sc #and copy&paste in the following:

//f.olofsson2016-2018 - for controlling 12ch computer fan switch board
DragspelFans {
        var <rpi, num, vals, lastv, <>debug;
        *new {|debug= false, rpi, num= 12|
                ^super.new.initDragspelFans(debug, rpi, num);
        }
        initDragspelFans {|d, r, n|
                num= n;
                if(r.notNil, {
                        rpi= r;
                }, {
                        try{
                                rpi= NetAddr("fans.local", 9999);
                        } {|err|
                                "could not connect to rpi.\n make sure you are connected to the wifi network 'dragspel'.".warn;
                                rpi= NetAddr("127.0.0.1", 9999);  //temp just for testing
                        };
                });
                debug= d;
                vals= 0!num;
        }

        setAll {|val= 100|  //val should be 0 to 100
                vals= val!num;
                this.prSend;
        }
        clearAll {
                vals= 0!num;
                this.prSend;
        }
        val {|index, val|  //index should be 0-11, val 0-100
                vals= vals.put(index, val);
                this.prSend;
        }
        arr {|arr|  //arr should be 12 numbers in an array
                vals= arr;
                this.prSend;
        }

        shutdown {
                rpi.sendMsg(\shutdown);
        }
        reboot {
                rpi.sendMsg(\reboot);
        }
        start {
                rpi.sendMsg(\start);
        }
        stop {
                rpi.sendMsg(\stop);
        }

        //--private
        prSend {|v|
                if(debug, {
                        vals.postln;
                });
                v= vals.clip(0, 100).round.asInteger;
                if(v!=lastv, {  //filter out repeats
                        lastv= v;
                        rpi.sendMsg(\pwms, *v);  //send to dragspelFans.py
                });
        }
}
*/

* nano ~/dragspelFans.scd #and copy&paste in the following:

//demo autostart script - put your own standalone code in here
d= DragspelFans.new;
Event.addEventType(\fans, {d.val(~index, ~val)});
Pbind(\type, \fans, \dur, 0.5, \index, Pseq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], inf), \val, Pwhite(0, 100, inf)).play;

* crontab -e #and add the following to the end (note no sudo here this time)

@reboot cd /home/pi/supercolliderStandaloneRPI1 && xvfb-run ./autostart.sh

now sudo reboot and supercollider should automatically start the code in dragspelFans.scd. it'll take a while so give it a minute or two.

to test it more run the following supercollider code on your laptop...

n= NetAddr("fans.local", 9999);
n.sendMsg(\stop);  //first stop the dragspelFans.scd script
n.sendMsg(\pwms, *[25, 50, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0]);  //set pwm manually

//install the DragspelFans.sc class on your laptop sc and also try the following example code

a= DragspelFans(true);  //might take a moment or two
CmdPeriod.doOnce({a.clearAll});  //optional

//version0 - all on or off
a.setAll
a.clearAll
a.setAll(50)  //set all to some value 0-100
a.clearAll

//version1 - using an array
a.arr([0, 0, 100, 0, 0, 100, 0, 0, 100, 0, 0, 100])  //turn on some
a.arr([0, 100, 0, 0, 100, 0, 0, 100, 0, 0, 100, 0])  //turn some other fans
a.arr([30, 0, 0, 40, 100, 0, 40, 0, 0, 80, 0, 0])  //a few slower
a.clearAll

//version2 - set index to value
a.val(9, 100);
a.val(9, 0);
a.val(11, 100);
a.val(11, 0);
a.val(11, 60);
a.val(11, 0);

//fade in each fan in order
(
r= Routine.run({
        12.do{|j|
                100.do{|i|
                        a.val(j, i);
                        0.05.wait;
                };
                100.do{|i|
                        a.val(j, 99-i);
                        0.05.wait;
                };
        };
});
)
r.stop;

//using patterns
a= DragspelFans.new;
Event.addEventType(\fans, {a.val(~index, ~val)});
Pdef(\test, Pbind(\type, \fans, \dur, 0.125, \index, Pseq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble, inf), \val, Pwhite(0, 100, inf))).play;
Pdef(\test).stop;

a.start;  //start the dragspelFans.scd script on the rpi again
a.stop;
a.reboot;
a.shutdown;

or test it using the attached maxmsp patch.

fansStandaloneController

//--wifi softap
this section is optional. it will set up a wifi access point served from the rpi. basically taken from https://www.raspberrypi.org/documentation/configuration/wireless/access-point.md
* sudo apt-get install dnsmasq hostapd
* sudo systemctl stop dnsmasq
* sudo systemctl stop hostapd
* sudo nano /etc/dhcpcd.conf #and add the following to the end:

interface wlan0
    static ip_address=192.168.4.1/24

* sudo service dhcpcd restart
* sudo nano /etc/dnsmasq.conf #and add the following two lines to the bottom:

interface=wlan0
dhcp-range=192.168.4.2,192.168.4.50,255.255.255.0,4h

* sudo nano /etc/hostapd/hostapd.conf #and add the following (remember to set passphrase 8-64 characters):

interface=wlan0
driver=nl80211
ssid=dragspel
hw_mode=g
channel=6
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=________
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

* sudo nano /etc/default/hostapd #and change one line to the following:

DAEMON_CONF="/etc/hostapd/hostapd.conf"

* sudo systemctl start hostapd
* sudo systemctl start dnsmasq

now sudo reboot on the rpi, log on to dragspel wifi network from your laptop and try to send some osc commands.

update 180225: major rewrite to use pigpio instead of RPi.GPIO and also raspbian stretch instead of jessie. pwm works much better.

stine

next week in Bucharest we'll be setting up the subjective frequency transducer for the third time. i described the sound/vibration generating part of this system before but didn't write much about how the controllers work.

so for each sound channel (i.e. each bass transducer) there's a wireless controller that enables the audience to set their preferred frequency. technically it's done with a rotary encoder, a esp8266 wifi module, a mega168 and a big 7-segment lcd. the circuit runs off two AAA batteries.

when someone touches the rotary encoder, the circuit wakes up and starts sending osc messages to a laptop running supercollider. supercollider receives the values, starts playing an oscillator and sends the sound to the corresponding audio channel. when done, sc fades out the oscillator and sends an off message to the circuit and the controller goes back to sleep mode.

i spent quite some time optimising the microcontroller (atmega168) code. it was hard to both reduce power consumption and still being able to quickly wake up and react on user input as well as on incoming osc messages. it's a common problem with battery powered radio devices.

also getting the esp8266 to handle osc messages was a pain. here and here are some more info and simplified versions of that.

in the end, the code for talking to these circuits in supercollider looked like this:

//sc example: sending. turn off circuit 3 and set it back to initial frequency
~encode= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
~encode.value(3, 0, 0, ~initFreq);
//sc example: receiving. decoding data from the four esp8266
OSCdef(\sti, {|msg, time, addr|
        var id= msg[1]>>24;
        var onoff= (msg[1]>>16)&255;
        var freq= (msg[1]&65280)+(msg[1]&255);
        [\id, id, \onoff, onoff, \freq, freq].post;
}, \sti);

the microcontroller code could still be improved. i'd like it to wake up on both wdt and uart. at the moment the circuit is drawing 22mA average in off+idle state, and 33mA average with display set to '20' which is okey but not optimal. and when sending osc you get current spikes of a few hundred milliamps but there's no way around that.

//f.olofsson 2015-2016

#define ID 3
#define FREQ 0 //start frequency
#define FREQ_MIN 0
#define FREQ_MAX 9999
#define WLAN_SSID "MYNETWORK"
#define WLAN_PASS "MYPASSWORD"
#define WLAN_ADDR "192.168.43.99" //laptop static ip
#define WLAN_PORT 1112
String tag = "/tap"; //incomming osc addy

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

#include <Encoder.h>

Encoder myEnc(3, 2);
float freq = FREQ;  //starting frequency
int freqLast = -999;

byte state = 0;
int enc = 0;
byte dig = 0;
byte cnt = 0;
boolean resp;

uint8_t buf[16];  //osc message

void setup() {
  pinMode(2, INPUT_PULLUP);  //encoder a
  pinMode(3, INPUT_PULLUP);  //encoder b
  pinMode(4, INPUT_PULLUP);  //encoder button
  DDRB = B11111111;  //segments
  DDRC = B00001111;  //digits selector

  //--set up wifi
  Serial.begin(115200);
  Serial.setTimeout(10000);
  resp = Serial.find("ready\r\n");
  progressDot(1);
  Serial.println("AT+CWMODE=1");
  resp = Serial.find("OK\r\n");
  progressDot(2);
  do {
    Serial.print("AT+CWJAP=\"");
    Serial.print(WLAN_SSID);
    Serial.print("\",\"");
    Serial.print(WLAN_PASS);
    Serial.println("\"");
    resp = Serial.find("OK\r\n");
  } while (!resp);
  progressDot(3);
  Serial.println("AT+CIPMUX=1");
  resp = Serial.find("OK\r\n");
  progressDot(4);
  Serial.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial.print(WLAN_ADDR);
  Serial.print("\",57120,");  //supercollider default port
  Serial.print(WLAN_PORT);
  Serial.println(",0");
  resp = Serial.find("OK\r\n");
  Serial.setTimeout(1000);
  displayClear();

  //--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] = ID;  // a high   (id)
  buf[13] = state; // a low  (onoff)
  buf[14] = 0;   // b high   (freq hi)
  buf[15] = 0;   // b low    (freq lo)

  //--timer
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 32768;  //62.5Hz display updaterate
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS10);  //prescaler divide by 1
  TIMSK1 |= (1 << OCIE1A);
  interrupts();

  //--sleep
  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = 1 << WDP0 | 1 << WDP1;
  WDTCSR |= _BV(WDIE);
}

volatile int f_wdt = 1; //watchdog wakeup
ISR(WDT_vect) {
  if (f_wdt == 0) {
    f_wdt = 1;
  }
}
void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_IDLE);
  sleep_enable();
  sleep_mode();
  sleep_disable();
  power_all_enable();
}

ISR(TIMER1_COMPA_vect) {  //update display periodically
  if (state == 2) {
    displayFreq();
  }
}

void sendOsc() {
  buf[13] = state;
  buf[14] = int(freq) >> 8;
  buf[15] = int(freq) & 255;
  Serial.println("AT+CIPSEND=4,16");
  Serial.find(">");
  Serial.write(buf, sizeof(buf));
  resp = Serial.find("OK\r\n");
}

void loop() {
  dig = 1 - ((PIND >> 4) & 1);  //encoder momentary button
  switch (state) {
    case 2:  //running (display on)
      enc = myEnc.read();
      if (enc != 0) {
        float incStep = enc / 2.0;
        myEnc.write(0);
        freq = max(FREQ_MIN, min(FREQ_MAX, freq + incStep));
        if (int(freq) != freqLast) {
          sendOsc();
          freqLast = int(freq);
        }
      }
      if (dig == 1) {  //TODO: or timeout here?
        state = 3;
      }
      break;
    case 0:  //sleeping (display off)
      f_wdt = 0;
      enterSleep();
      enc = myEnc.read();
      if ((dig == 1) || (enc != 0)) {
        state = 1;
        freq = FREQ; //reset
        sendOsc();
      }
      break;
    case 3:  //turning off when button released
      displayClear();
      if (dig == 0) {
        state = 0;
        sendOsc();
      }
      break;
    case 1:  //turning on when button released
      if ((dig == 0) || (enc != 0)) {
        state = 2;
        myEnc.write(0);
      }
  }

  //--receive osc
  while (Serial.available()) {
    String abc = Serial.readStringUntil('\n');
    if (abc.startsWith("+IPD,4,16:" + tag)) {
      //if(abc[22]==ID) { //optional filter by device ID
      if (abc[23] == 0) {
        displayClear();
        state = 0;
      } else {
        state = 2;
        myEnc.write(0);
      }
      freq = (abc[24] << 8) + abc[25];
    }
  }
}
void displayClear() {
  PORTC = B00001111;
  PORTB = B00000000;
}
void progressDot(byte index) {
  setChr(255, true);
  selDig(index);
}
void displayFreq() {
  int val = freq; //cuts off fraction
  switch (cnt) {
    case 0:
      if (val > 999) {
        setChr((val % 10000) / 1000, false);
      } else {
        setChr(255, false);
      }
      selDig(1);
      cnt = 1;
      break;
    case 1:
      if (val > 99) {
        setChr((val % 1000) / 100, false);
      } else {
        setChr(255, false);
      }
      selDig(2);
      cnt = 2;
      break;
    case 2:
      if (val > 9) {
        setChr((val % 100) / 10, false);
      } else {
        setChr(255, false);
      }
      selDig(3);
      cnt = 3;
      break;
    case 3:
      setChr(val % 10, false);
      selDig(4);
      cnt = 0;
  }
}

void selDig(byte index) {
  switch (index) {
    case 1:
      PORTC = B00001110;
      break;
    case 2:
      PORTC = B00001101;
      break;
    case 3:
      PORTC = B00001011;
      break;
    case 4:
      PORTC = B00000111;
  }
}

void setChr(byte chr, bool dot) {
  switch (chr) {
    case 255:  //clear
      PORTB = B00000000;
      break;
    case 0:
      PORTB = B11111100;
      break;
    case 1:
      PORTB = B01100000;
      break;
    case 2:
      PORTB = B11011010;
      break;
    case 3:
      PORTB = B11110010;
      break;
    case 4:
      PORTB = B01100110;
      break;
    case 5:
      PORTB = B10110110;
      break;
    case 6:
      PORTB = B10111110;
      break;
    case 7:
      PORTB = B11100000;
      break;
    case 8:
      PORTB = B11111110;
      break;
    case 9:
      PORTB = B11100110;
      break;
      /*
        case 10:  //A
        case 11:  //B
        case 12:  //C
        case 13:  //D
        case 14:  //E
        case 15:  //F
        case 16:  //G
        case 17:  //H
      */

  }
  if (dot) {
    PORTB |= B00000001;
  }
}

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;
        };
});
)

note: my new and better way to do this is described here

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

Pages

Subscribe to RSS - supercollider