electronics

f0led

here is another project built around the esp8266. it's a wireless osc controlled 100w led. as the led should act as a stroboscope and not be kept on for long durations of time, i could save space and cost using a smaller sized heatsink. via wifi (opensound control) the led can be turned on/off, the level, attack and release times adjusted etc. there is also a push button trigger input as well as a microphone input. so the strobe can be either triggered manually by the musician, by the sound of the nearby instrument or remotely by a computer.

the strobe also send out osc data from the button and mic so it can in turn be used to trigger additional sounds in the computer.

supercollider example code...

OSCFunc.trace(true)
OSCFunc.trace(false)

n= NetAddr("192.168.1.10", 15555);
n.sendMsg(\led, 0.5, 0.1)   //val, fade
n.sendMsg(\led, 0.0, 0.01)  //val, fade
n.sendMsg(\micMode, 1);  //mic on/off
n.sendMsg(\micFade, 1.0, 0.1);  //mic atk rel
n.sendMsg(\butFade, 1.0, 0.1);  //but atk rel

OSCdef(\oscin, {|msg| msg.postln}, \f0led, NetAddr("192.168.1.10", 15555));

f0led 1

the battery is a 12v sealed lead-acid and i measured up toward 8 amps current draw. it weights about 0.5kg.

f0led schematics

bill of material...

1       ESP8266-01
1       4x2 socket
1       heatsink
2       100uF cap
1       100 resistor
1       10k resistor
1       10k log pot     (reichelt ACP 6-L 10K)
1       regulator       (reichelt LF 33 CV)
1       mosfet          (reichelt IRLZ 34N)
1       mic             (reichelt MCE 101)
4       screwterminals  (reichelt AKL 101-02)
1       12v lead-acid   (pollin 94‑271194)
1       heatsink        (ebay 2.4x2.4inch Aluminum Alloy Heat Sink for 1W/3W/5W/10W LED Silver White)
1       dcdc            (ebay DC DC boost converter Constant Current Mobile Power supply 250W)
1       100w led        (ebay 100W Cool White High Power LED LIGHT SMD chip Panel 9000-10000LM)

thick wires
heat paste
screws and nuts

arduino code...

// * install OSC from https://github.com/CNMAT/OSC
// * edit where it says EDIT below
// * choose board: "Generic ESP8266 Module" 160 MHz

//TODO: gamma correction

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCData.h>

//pin3 (urxd) can do pwm out
//pin2 and pin0 can not do pwm
//pin2 and pin0 have to be 3v3 at powerup

#define ID 10       //EDIT device id (also static ip)
#define PINMIC 0
#define PINBUT 2
#define PINPWM 3
#define PORT 15555
#define UPDATERATE 16
#define TRIES 100  //how many times try check for wifi connection at startup
#define PINGRATE 600
#define STATICIP 1  //optional - 0 or 1

const char *ssid = "mywlan"; //EDIT your accessPoint network name
const char *password = "mypass";  //EDIT your password
const char *espname = "f0led";
const unsigned int outPort = 57120;
#if STATICIP==1
IPAddress ip(192, 168, 1, ID); //EDIT optional static ip
IPAddress gateway(192, 168, 1, 1);  //EDIT optional router
IPAddress subnet(255, 255, 255, 0);
#endif
float micFadeAtk = 1.0, micFadeRel = 0.1; //default fade times
float butFadeAtk = 1.0, butFadeRel = 0.1; //default fade times
float val = 0.0, valTarget = 0.0, fade = 1.0;
unsigned long nextTime;
byte micMode = 0;  //allow mic trigger led on/off
byte micState = 1;
byte butState = 1;
int cnt;
IPAddress outIp;
WiFiUDP Udp;
OSCMessage msgPing("/f0led");
OSCMessage msgMic("/f0led");

void setup() {
  delay(10);
  WiFi.mode(WIFI_STA);
  WiFi.hostname(espname);
  WiFi.begin(ssid, password);
#if STATICIP==1
  WiFi.config(ip, gateway, subnet);
#endif
  cnt = 0;
  while ((WiFi.status() != WL_CONNECTED) && (cnt < TRIES)) {
    delay(100);
    cnt++;
  }
  outIp = WiFi.localIP();
  outIp[3] = 99;  //send to ip x.x.x.99 on same network (e.g. 192.168.1.99)
  Udp.begin(PORT);
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
  pinMode(PINMIC, INPUT);
  pinMode(PINBUT, INPUT_PULLUP);
  pinMode(PINPWM, OUTPUT);
  msgMic.add(ID);
  msgMic.add("mic");
  msgPing.add(ID);
  msgPing.add("ping");
}

void oscLed(OSCMessage &msg) {
  valTarget = msg.getFloat(0);
  fade = msg.getFloat(1);
}
void oscMicMode(OSCMessage &msg) {
  micMode = msg.getInt(0);
}
void oscMicFade(OSCMessage &msg) {
  micFadeAtk = msg.getFloat(0);
  micFadeRel = msg.getFloat(1);
}
void oscButFade(OSCMessage &msg) {
  butFadeAtk = msg.getFloat(0);
  butFadeRel = msg.getFloat(1);
}
void sendOscBut(byte val) {
  OSCMessage msg("/f0led");
  msg.add(ID);
  msg.add("but");
  msg.add(val);
  Udp.beginPacket(outIp, outPort);
  msg.send(Udp);
  Udp.endPacket();
  msg.empty();
}
void sendOscMic() {
  Udp.beginPacket(outIp, outPort);
  msgMic.send(Udp);
  Udp.endPacket();
}
void sendOscPing() {
  Udp.beginPacket(outIp, outPort);
  msgPing.send(Udp);
  Udp.endPacket();
}

void loop() {

  //--osc input
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    OSCMessage oscMsg;
    while (packetSize--) {
      oscMsg.fill(Udp.read());
    }
    if (!oscMsg.hasError()) {
      oscMsg.dispatch("/led", oscLed);
      oscMsg.dispatch("/micMode", oscMicMode);
      oscMsg.dispatch("/micFade", oscMicFade);
      oscMsg.dispatch("/butFade", oscButFade);
    }
  }

  //--mic input
  if (digitalRead(PINMIC) == 0) {
    if (micState == 0) {
      micState = 1;
    }
  }

  if (millis() >= nextTime) {
    nextTime = millis() + UPDATERATE;
    if (cnt % PINGRATE == 0) {
      sendOscPing();
    }
    cnt++;

    //--mic input2
    if (micState == 1) {
      micState = 2;
      sendOscMic();
      if (micMode == 1) {
        valTarget = 1.0;
        fade = micFadeAtk;
      }
    } else if (micState == 2) {
      if (digitalRead(PINMIC) == 1) {
        valTarget = 0.0;
        fade = micFadeRel;
        micState = 0;
      }
    }

    //--button input
    if (digitalRead(PINBUT) == 0) {
      if (butState == 0) {
        butState = 1;
        sendOscBut(1);
        valTarget = 1.0;
        fade = butFadeAtk;
      }
    } else {
      if (butState == 1) {
        butState = 0;
        sendOscBut(0);
        valTarget = 0.0;
        fade = butFadeRel;
      }
    }

    //--fade in/out
    if (val < valTarget) {
      val = val + fade;
      if (val > valTarget) {
        val = valTarget;
      }
    } else if (val > valTarget) {
      val = val - fade;
      if (val < valTarget) {
        val = valTarget;
      }
    }

    analogWrite(PINPWM, int(val * 1023));
  }
}

f0led 2

f0led 3

update 171124: added two more photos, a maxpatch and updated the arduino code with static ip option

AttachmentSize
Package icon f0ledtest.maxpat.zip2.07 KB

f0neo

here is how i build super cheap wireless osc controlled rgb ledstrips. the main components for these are an esp8266, a 5v powerbank, a voltage regulator and some leds. the leds i've used so far are the SK6812 RGBW, but it is easy to adapt the arduino code to work with other models like the WS2812B.

f0neo 1

f0neo 2

f0neo schematics

a basic version of the arduino code shown here below. when it starts it creates a soft access point. connect to it with a computer or phone, figure out the ip address of the esp8266 and start sending osc commands to it.

// * install OSC from https://github.com/CNMAT/OSC
// * install Adafruit_NeoPixel from library manager
// * edit where it says EDIT below
// * choose board: "Generic ESP8266 Module"
// * upload and connect to softap with laptop
// * try to send osc messages to ip 192.168.4.1 port 19999
//protocol: [\rgbw, index, red, green, blue, white] example red: [\rgbw, 0, 255, 0, 0, 0]

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCData.h>
#include <Adafruit_NeoPixel.h>

#define PORT 19999
#define NUMNEO 12  //EDIT number of neo pixels in use
#define PINNEO 2

const char *ssid = "f0neo"; //EDIT softAccessPoint network name
const char *password = "mypass";  //EDIT password

WiFiUDP Udp;

//EDIT to match type of leds (see example/Adafruit_NeoPixel/strandtest)
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMNEO, PINNEO, NEO_RGBW + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.show();
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  Udp.begin(PORT);
}

void rgbw(OSCMessage &msg) {
  pixels.setPixelColor(msg.getInt(0), msg.getInt(2), msg.getInt(1), msg.getInt(3), msg.getInt(4));
  pixels.show();
}

void loop() {
  OSCMessage oscMsg;
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    while (packetSize--) {
      oscMsg.fill(Udp.read());
    }
    if (!oscMsg.hasError()) {
      oscMsg.dispatch("/rgbw", rgbw);
    }
  }
}

attached (zip file) are more elaborate versions of this code - also including maxmsp and supercollider examples and kicad schematics.

AttachmentSize
Package icon f0neo.zip26.95 KB

f0dmx

here is how i built a wireless isolated dmx controller that takes osc input. the box uses an esp8266 to create a wifi access point that one can connect to with a laptop (or phone or whatever). opensound control messages sent to the box are converted into standard dmx commands. multiple clients can be connected and send dmx commands at the same time.

f0dmx 1

f0dmx 2

below is arduino code for the esp8266, bom, the kicad schematics and some supercollider test code.

// * install OSC from https://github.com/CNMAT/OSC
// * install WiFiManager from https://github.com/tzapu/WiFiManager
// * install LXESP8266UARTDMX from https://github.com/claudeheintz/LXESP8266DMX
// * select board: "Generic ESP8266 Module" 160 MHz

#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Ticker.h>
#include <WiFiManager.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <OSCData.h>
#include <LXESP8266UARTDMX.h>

#define CONFIG_PIN 0   //gpio0 to gnd to reset wifi
#define PORT 19998  //EDIT osc input port
#define OUTPORT 57120 //EDIT osc output port (only used at startup for announcement)
char *espname = "f0dmx";

Ticker ticker;
IPAddress outIp;
WiFiUDP Udp;

void setup() {
  delay(10);
  pinMode(CONFIG_PIN, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  ticker.attach(0.6, tick);

  WiFi.hostname(espname);
  wifi_station_set_hostname(espname);
  WiFiManager wifiManager;
  wifiManager.setAPCallback(configModeCallback);
  if (!wifiManager.autoConnect(espname)) {
    ESP.reset();
    delay(1000);
  }
  MDNS.begin(espname);  //make .local work
  outIp = WiFi.localIP();
  Udp.begin(PORT);
  OSCMessage msg("/ready"); //announcement
  msg.add(espname);
  msg.add(int(outIp[0]));
  msg.add(int(outIp[1]));
  msg.add(int(outIp[2]));
  msg.add(int(outIp[3]));
  msg.add(PORT);
  outIp[3] = 255;  //use broadcast ip x.x.x.255
  Udp.beginPacket(outIp, OUTPORT);
  msg.send(Udp);
  Udp.endPacket();
  yield();
  msg.empty();

  ESP8266DMX.startOutput();

  ticker.detach();
  digitalWrite(LED_BUILTIN, LOW);   //debug
}

void tick() {
  int state = digitalRead(LED_BUILTIN);
  digitalWrite(LED_BUILTIN, !state);  //debug
}
void configModeCallback(WiFiManager *myWiFiManager) {
  ticker.attach(0.15, tick);
}

void dmx(OSCMessage &msg) {
  int chan, value;
  for (byte i = 0; i < msg.size(); i = i + 2) {
    chan = getIntCast(msg, i);
    value = getIntCast(msg, i + 1);
    ESP8266DMX.setSlot(chan, value);
  }
}

int getIntCast(OSCMessage &msg, int index) {  //support for both integers and floats
  if (msg.isInt(index)) {
    return msg.getInt(index);
  }
  return int(msg.getFloat(index));
}

void start(OSCMessage &msg) {
  ESP8266DMX.startOutput();
}

void stop(OSCMessage &msg) {
  ESP8266DMX.stop();
}

void loop() {
  OSCMessage oscMsg;
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    while (packetSize--) {
      oscMsg.fill(Udp.read());
    }
    if (!oscMsg.hasError()) {
      oscMsg.dispatch("/dmx", dmx);
      oscMsg.dispatch("/start", start);
      oscMsg.dispatch("/stop", stop);

      tick();
    }
  }

  //--wifi
  if (digitalRead(CONFIG_PIN) == LOW) {  //reset pin
    WiFiManager wifiManager;
    wifiManager.resetSettings();
    wifiManager.setAPCallback(configModeCallback);
    wifiManager.autoConnect(espname);
    ticker.detach();
    digitalWrite(LED_BUILTIN, LOW); //debug
  }
}

bill of material...

1       dcdc            ROE-0505S       reichelt
1       xlr female      XLR 3KU         reichelt
1       optocoupler     6N 137          reichelt
1       ic              SN 75176BP      reichelt
1       box             BOPLA KS 420    reichelt
1       resistor        10K
1       resistor        470
3       resistor        10
1       resistor        120
2       cap             10uF
1       cap             220uF
1       regulator       LF 33 CV
1       micro           ESP8266-01
1       socket          4x2
1       push button
1       usb cable

f0dmx kicad schematics

example of how to send osc from supercollider to the f0dmx box.

//make sure you are connected to the same wifi network as f0dmx
n= NetAddr("f0dmx.local", 19998);  //the ip and port of the f0dmx box
n.sendMsg(\dmx, 9, 255)  //dmx channel 9, value 255
n.sendMsg(\dmx, 9, 0)
n.sendMsg(\dmx, 7, 100)  //dmx channel 7, value 100
n.sendMsg(\dmx, 7, 0)

n.sendMsg(\stop)  //usually not needed
n.sendMsg(\start)

update 180620: cast floats to integers (getIntCast), increased electrolytic cap value from 100 to 220uF
update 181212: simplified wifi setup with the wifimanager library

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. 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.

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. also exhibited in wiesbaden and at kunstverein-tiergarten in berlin.

'traps', as the piece is called, consists of speakers made out of paper and enamelled 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 synthesised 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 RSS - electronics