optoforce

here's some python code for reading serial input from optoforce's 3d sensor and sending it over osc to maxmsp or supercollider.

the slightly odd baudrate of 1000000 isn't supported in sc nor max under osx, so i had to use python for this.

#for the 3d sensor OMD-30-SE-100N
#f.olofsson 2017

#first argument is serial port, second ip and third port.  e.g.
#python optoforceOsc.py '/dev/tty.usbmodem1451' '127.0.0.1' 9999

import sys
from struct import *
from threading import Thread
import serial
from OSC import OSCServer, OSCClient, OSCMessage, OSCClientError

osc= OSCClient()
if len(sys.argv)>3:
  osc.connect((sys.argv[2], int(sys.argv[3])))  #send to address and port
else:
        osc.connect(('127.0.0.1', 57120))  #default send to sc on same computer

serport= '/dev/cu.usbmodem1411'
if len(sys.argv)>1:
  serport= sys.argv[1]

ser= serial.Serial(
  port= serport,
  baudrate= 1000000,
  parity= serial.PARITY_NONE,
  stopbits= serial.STOPBITS_ONE,
  bytesize= serial.EIGHTBITS,
  timeout= 1
)
print('connected to serial port: '+ser.portstr)

def oscInput(addr, tags, stuff, source):
  print stuff  #for now do nothing

server= OSCServer(('0.0.0.0', 9998))  #receive from everywhere
server.addDefaultHandlers()
server.addMsgHandler('/optoforceConfig', oscInput)
server_thread= Thread(target= server.serve_forever)
server_thread.start()

print('sending osc to: '+str(osc.address()))
print('listening for osc on port: '+str(server.address()[1]))

###configure sensor (optional)
conf= bytearray(9)
speed= 10  #0, 1, 3, 10, 33, 100 (default 10)
filter= 3   #0 - 6 (default 4)
zero= 255   #0, 255
checksum= 170+0+50+3+speed+filter+zero
conf[0]= 170
conf[1]= 0
conf[2]= 50
conf[3]= 3
conf[4]= speed
conf[5]= filter
conf[6]= zero
conf[7]= checksum>>8
conf[8]= checksum&255
ser.write(conf)

def main():
  while True:
    b= ser.read(4)
    header= unpack('BBBB', b)
    if header==(170, 7, 8, 10): #data
      b= ser.read(12)
      counter= unpack('>H', b[0:2])[0]
      status= unpack('>H', b[2:4])[0]
      xyz= unpack('>hhh', b[4:10])
      checksum= unpack('>H', b[10:12])[0]
      sum= (170+7+8+10)
      for i in range(10):
        sum= sum+ord(b[i])
      if checksum==sum:
        #print(counter, status, xyz)
        msg= OSCMessage()
        msg.setAddress('/optoforce')
        msg.append(xyz)
        try:
          osc.send(msg)
        except OSCClientError:
          print 'osc: could not send to address'
      else:
        print 'data: checksum error'
        print checksum
    else:
      if header==(170, 0, 80, 1): #status
        b= ser.read(3)
        status= unpack('B', b[0])[0]
        checksum= unpack('>H', b[1:3])[0]
        if checksum!=(170+0+80+1+status):
          print 'status: checksum error'
          print checksum
      else:
        print 'header: serial read error'
        print header

if __name__ == '__main__':
  try:
    main()
  except KeyboardInterrupt:
    server.close()
    server_thread.join()
    ser.close()

optoforce 3d sensor with maxmsp from redFrik on Vimeo.

optoforce 3d sensor with supercollider from redFrik on Vimeo.

supercollider firmata 3

reading digital inputs from an arduino with the scfirmata is a little bit more complicated than needed.

here an example that reads 6 analog and 6 digital at the same time.

NOTE: use resistors (10K) to pull up or pull down the digital inputs. (i couldn't figure out how to activate the built in pullups.)

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.5' after a few seconds

(
~analog= [0, 1, 2, 3, 4, 5];  //A0-A5
~digital= [2, 3, 4, 5, 6, 12];  //some digital input pins
s.latency= 0.05;
s.waitForBoot{
        var freqsArr= 0!~analog.size;
        var ampsArr= 0!~digital.size;
        Ndef(\snd3, {Splay.ar(SinOsc.ar(\freqs.kr(freqsArr, 0.05), 0, \amps.kr(ampsArr.lag(0.01))).tanh)}).play;
        ~analog.do{|x|
                f.reportAnalogPin(x, true);      //start reading analog pins
        };
        f.analogPinAction= {|num, val|
                //[num, val].postln;
                freqsArr.put(~analog.indexOf(num), val);
                Ndef(\snd3).setn(\freqs, freqsArr);
        };
        ~digital.do{|x|
                f.setPinMode(x, \INPUT);
        };
        f.reportDigitalPort(0, true);
        f.reportDigitalPort(1, true);
        f.digitalPortAction= {|port, mask|
                var dig;
                //[port, mask, mask.asBinaryString].postln;
                dig= ~digital.collect{|x| ((mask<<(port*8))&(1<<x)==(1<<x)).binaryValue};
                Ndef(\snd3).set(\amps, dig.postln);
        };
};
)

(
Ndef(\snd3).stop;
~analog.do{|i|
        f.reportAnalogPin(i, false);     //stop reading A0-Anum
};
f.reportDigitalPort(0, false);
f.reportDigitalPort(1, false);
f.end;
f.close;
)

previous articles...

http://www.fredrikolofsson.com/f0blog/?q=node/647

http://www.fredrikolofsson.com/f0blog/?q=node/629

supercollider firmata 2

+2 years ago i put up a simple example of how to use firmata with arduino and supercollider here. that code still work but it only show how to read a single analog input on the arduino.

here is how one can read both A0 and A1 and map those to synth parameters in supercollider...

//how to read pins A0 and A1 with SCFirmata...
//tested with Arduino1.8.0 and SC3.8.0
//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.5' after a few seconds

s.boot

(
Ndef(\snd, {|freq1= 400, freq2= 500, amp= 0.5| SinOsc.ar([freq1, freq2].lag(0.08), 0, amp.lag(0.08)).tanh}).play;
f.reportAnalogPin(0, true);      //start reading A0
f.reportAnalogPin(1, true);      //start reading A1
f.analogPinAction= {|num, val|
        [num, val].postln;
        switch(num,
                0, {
                        Ndef(\snd).set(\freq1, val.linexp(0, 1023, 400, 800)); //A0 mapped to freq1
                },
                1, {
                        Ndef(\snd).set(\freq2, val.linexp(0, 1023, 400, 800)); //A1 mapped to freq2
                }
        );
};
)

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

and to read all six analog inputs (A0-A5) one can do...

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.5' after a few seconds

s.boot
~numberOfAna= 6;  //number of analog inputs (here A0-A5)

(
var freqsArr= 0!~numberOfAna;
Ndef(\snd2, {|amp= 0.5| Splay.ar(SinOsc.ar(\freqs.kr(freqsArr, 0.05), 0, amp.lag(0.08)).tanh)}).play;
~numberOfAna.do{|i|
        f.reportAnalogPin(i, true);      //start reading A0-Anum
};
f.analogPinAction= {|num, val|
        [num, val].postln;
        freqsArr.put(num, val);
        Ndef(\snd2).setn(\freqs, freqsArr);
};
)

(
Ndef(\snd2).stop;
~numberOfAna.do{|i|
        f.reportAnalogPin(i, false);     //stop reading A0-Anum
};
f.end;
f.close;
)

wireless mqtt circuits

i've stared using mqtt for talking to microcontrollers over wifi and here's some code and instructions on how to set up such a system.

there are two programs that have to run in the background. they handle all the communication between the wireless hardware and the client software (maxmsp, supercollider etc). one is mosquitto. mosquitto is a mqtt broker and the central pub/sub hub of the system. the second program is a python mqtt-osc bridge script using the paho client. this python script lets programs like maxmsp or supercollider talk to mosquitto via osc. see the readme.txt included below on how to install and configure these programs.

on the hardware side i build send/receive circuit nodes consisting of a esp8266 module and an arduino pro-mini. these circuits run on 3v, are small and configurable and the parts cost almost nothing. the esp8266 module provides wifi communication and runs a mqtt client (i'm using the adafruit mqtt library), while the pro-mini does the physical inputs and outputs (sensors, leds etc). the two modules talk to each other via serial.
some circuits i've build do 12 digital + 8 analog inputs, while others have 12 leds in combination with 8 analog inputs. but any combination is possible and the number of ins/outs depends on how the pro-mini is programmed. (see portable_promini_ana and portable_promini_led in the zip archive below.)

portable00

portable01

so far i'm really pleased with this new technique. it seems to scale well and work more reliable than what i used before (sending raw osc via cc3000 or esp8266).

AttachmentSize
Package icon portable.zip16.11 KB

syntjuntan

i got to design and build version 2 of syntjuntan's sewable synthesizer circuit. for this version they wanted to add an on-board amplifier that could drive a passive speaker element.

the circuit now has three schmitt triggers and can run on 3-12V. the amplifier is the classic lm386 and the connector pads around the board are made to fit needle and conductive thread as well as being crocodile friendly.

there are some options as standard through-hole soldering pads (a fourth schmitt trigger and x10 extra gain). the circuit can also be used as a standalone audio amplifier - just ignore the schmitt triggers and connect your own signal to the in pad.

anyway, lots of fun mass producing this and in the process i learned how to do hot-air smd soldering with stencil and solder paste plus got to know kicad a bit better.

i also built a test rig with an arduino and some pogo pins. it both scans for short-cuts and tests the sound.

syntjuntakrets 2 2

syntjuntakrets 2 4

syntjuntakrets 2 5

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.

//--preparation
* download and install raspbian-jessie-lite onto a 2gb sd card
* connect your rpi to your home router (use a rpi2 with ethernet) and type the following in terminal on your laptop:
* ssh pi@raspberrypi.local #default passwork is raspberry
* sudo raspi-config #change password to _____, set gpu memory to 16, change hostname to fans and reboot
* sudo apt-get update
* sudo apt-get upgrade
* sudo apt-get dist-upgrade
* sudo apt-get clean

//--wifi ap
* sudo apt-get install dnsmasq hostapd firmware-atheros firmware-ralink firmware-realtek
* sudo nano /etc/hostapd/hostapd.conf #and add the following:

interface=wlan0
driver=nl80211
country_code=DE
ssid=dragspel
channel=6
hw_mode=g
wpa=2
wpa_passphrase=______
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
#rsn_pairwise=CCMP
auth_algs=1
macaddr_acl=0
ignore_broadcast_ssid=0
eapol_key_index_workaround=0

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

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

* 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,4h

* sudo nano /etc/network/interfaces #and edit eth0 to look like:

allow-hotplug eth0
#auto eth0
iface eth0 inet dhcp

* and also change/add wlan0 to look like:

allow-hotplug wlan0
auto wlan0
iface wlan0 inet static
    address 192.168.4.1
    netmask 255.255.255.0
    network 192.168.4.0
    gateway 192.168.4.1
    wireless-power off

//--python
* sudo apt-get install liblo-dev python-dev python-pip
* sudo pip install cython #this takes a long time
* sudo pip install pyliblo
* sudo crontab -e #and add the following line at the end

@reboot /usr/bin/python /home/pi/dragspelFans.py

//--supercollider
(this step is basically the same as installing sc for rpi1 from here)
* sudo apt-get install libqt5webkit5 libqt5sensors5 libqt5positioning5 libcwiid-dev libfftw3-dev
* sudo apt-get install git dbus-x11 xvfb jackd2 #enable realtime when asked
* git clone git://github.com/redFrik/supercolliderStandaloneRPI1 --depth 1
* mkdir -p ~/.config/SuperCollider
* cp supercolliderStandaloneRPI1/sc_ide_conf_temp.yaml ~/.config/SuperCollider/sc_ide_conf.yaml
* sudo reboot #and log in again with ssh
* cd supercolliderStandaloneRPI1
* nano autostart.sh #and change the two lines to look like this

/usr/bin/jackd -P95 -dalsa -dhw:0 -p1024 -n3 -s &
./sclang -a -l sclang.yaml ../dragspelFans.scd

* crontab -e #and add the following to the end

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

* mkdir share/user
* 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

* from your laptop copy over some files using these commands...

scp ~/arbeten/dragspel/dragspelFans.py pi@fans.local:
scp ~/Library/Application\ Support/SuperCollider/Extensions/DragspelFans.sc pi@fans.local:supercolliderStandaloneRPI1/share/user/Extensions
scp ~/arbeten/dragspel/dragspelFans.scd pi@fans.local:

now log on to dragspel wifi network and try to send some osc commands using sc on your laptop.

//--debug
(for logging on to the rpi and start supercollider from terminal)
* pkill jackd
* pkill scsynth
* pkill sclang
* export DISPLAY=:0.0
* export `dbus-launch | grep ADDRESS`
* export `dbus-launch | grep PID`
* jackd -P95 -dalsa -dhw:0 -p1024 -n3 -s -r44100 &
* cd supercolliderStandaloneRPI1
* xvfb-run --auto-servernum ./sclang -a -l sclang.yaml

//--debug2
(for starting sc and run the default program)
* cd supercolliderStandaloneRPI1
* xvfb-run ./autostart.sh

save this as dragspelFans.py...

#f.olofsson2016
#pwm control for 12 fans

import sys
from os import system
from time import sleep
import RPi.GPIO as GPIO
from liblo import *

inport= 9999
pinoff= 3
pins= [5, 7, 8, 10, 11, 12, 13, 15, 16, 18, 19, 21]
target= ('127.0.0.1', 57120)  #to sclang
hz= 50  #pwm frequency

GPIO.setmode(GPIO.BOARD)
GPIO.setup(pinoff, GPIO.IN)  #no internal pullup needed
GPIO.setup(pins, GPIO.OUT)  #set all outputs
pwms= []
for pin in pins:
        pwms.append(GPIO.PWM(pin, hz))
for pwm in pwms:
        pwm.start(0)

class MyServer(ServerThread):
        def __init__(self):
                ServerThread.__init__(self, inport)
        @make_method('/pwms', 'iiiiiiiiiiii')
        def pwms_callback(self, path, args):
                #print args  #debug
                i= 0
                for pwm in pwms:
                        pwm.ChangeDutyCycle(args[i])
                        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 pwm in pwms:
                        pwm.ChangeDutyCycle(0)
        @make_method(None, None)
        def fallback(self, path, args):
                print 'received unknown message "%s"' % path

def stop(cmd):
        for pwm in pwms:
                pwm.stop()
        server.stop()
        system(cmd)
        sleep(10)
        sys.exit()

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

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

if __name__ == '__main__':
        try:
                main()
        except KeyboardInterrupt:
                GPIO.cleanup()

and here's the default dragspelFans.scd demo file that will be run by the rpi at startup. it just uses a pbind to set random pwm duty cycles (0-100) on all twelve pins. overwrite this file with your own code. save this as dragspelFans.scd

s.waitForBoot{
        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;
}

here's a screenshot of a simple max patch to manually control the fans...

fansStandaloneController

AttachmentSize
PDF icon dragspel_schematics.pdf50.67 KB
Binary Data DragspelFans.sc2.01 KB

paper speakers

here's some technical information on a collaboration with visual artist Jenny Michel. it's an sounding art installation that has previously been shown at galerie feldbuschwiesner and städtische galerie wolfsburg, and is now (sept2016) up and running at smac project space in berlin.

'traps', as the piece is called, consists of speakers made out of paper and enameled copper wire. the wire is embedded into the paper in the shape of a coil and two strong magnets are situated on the back. the paper is manually layered and filled with prints and later perforated with cutouts. behind the paper is also a small circuit with a microcontroller, an amplifier and an antenna - all hand soldered dead-bug style. power comes from a shared 5v wall adapter.

traps consists of around 30 individual speakers and they all react on each other via their antennas. they also pick up different electromagnetic fields - but we can't say in exactly which frequency range they are sensitive as the antennas are made with arbitrary lengths and shapes (they're also copper wire).

the sound is synthesized with the microcontroller (attiny45). loudness and quality varies depending on the shape of the speakers - they're all individual - but in general the resulting sounds are quite noisy and piercing. the code was written to create a lot of variation and not just sonify the raw antenna input. so it's more of an interpretation than a direct translation of trapped radio frequency signals to sound as we first had planned. we also wanted harsher electronic sounds as it both fits better with the visual impression and that that type of square wave audio work very well in these non-linear lo-fi speakers.

there are also two modes: if one quickly turn the power on-off-on, the installation will start in 'vernissage mode' and play more sound than if one just turn the power on as normal (look for the eeprom part in the code below to see how it works more in detail).

in total we built 50 circuits and many more paper speakers. though ~30 are enough to fill medium sized gallery.





here's the code...

//f.olofsson 2015-2016
//select ATtiny, 1MHz (internal)
//decreased durMax in two places from 80000 to 8000

#include <EEPROM.h>

int durPause;
unsigned long durMax;
byte pausePer;
void setup() {
  randomSeed(analogRead(3));
  pinMode(4, OUTPUT);
  byte magic = EEPROM.read(0);
  if (magic == 123) { //lots of sound - chaotic opening mode
    durPause = 15000;
    durMax = 8000;
    pausePer = 67;  //percentage
    for (byte i = 0; i < 3; i++) {  //beep three times at startup
      prgStatic(100);
      prgPause(100);
    }
  } else {  //default - soft gallery mode
    durPause = 30000;
    durMax = 8000;
    pausePer = 75;  //percentage
    EEPROM.write(0, 123);
    delay(3000);  //power on for <3sec & then next time mode 1
    prgStatic(1500);  //make a tone at startup to know all working
  }
  EEPROM.write(0, 255);
}
void loop() {
  prgNoise(analogRead(3));
  if (random(100) < pausePer) {
    prgPause(durPause);
  } else {
    unsigned long dur = random(durMax);
    switch (analogRead(3) % 7) {
      case 0:
        prgPause(dur);
        break;
      case 1:
        prgNoise(dur);
        break;
      case 2:
        prgNoise(dur);
        break;
      case 3:
        prgChunks(dur);
        break;
      case 4:
        prgChunks(dur);
        break;
      case 5:
        prgBitbang(dur);
        break;
      case 6:
        prgImpulses(dur);
        break;
    }
  }
}
void prgPause(unsigned long dur) {
  analogWrite(4, 0);
  pinMode(0, OUTPUT);
  digitalWrite(0, 0);
  delay(dur);
  pinMode(0, INPUT);
}
void prgStatic(int dur) {
  analogWrite(4, 127);
  delay(dur);
}
void prgNoise(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
  }
}
void prgNoise2(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
    delay(1);
  }
}
void prgChunks(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val);
  }
}
void prgChunks2(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val / 2);
    analogWrite(4, base);
    delay(val / 2);
  }
}
void prgBitbang(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3 >> 2);
    for (byte i = 0; i < 8; i++) {
      digitalWrite(4, val >> i & 1);
    }
  }
}
void prgImpulses(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    digitalWrite(4, 1);
    digitalWrite(4, 0);
    delay(val);
  }
}

note the use of the lm386 n-4. it is more powerful than the n1, n3 variants.

Pages

Subscribe to f0blog RSS