supercollider

rpi audio codec

here's how to set up the proto WM8731 based audio codec module from MikroElektronika and use it with supercollider on a raspberry pi 3.

power off the rpi and connect the proto board to the rpi with jump wires like this...

proto               raspberry
-----                -----
sck           ->    rpi 12
miso         ->    rpi 38
mosi         ->    rpi 40
adcl+dacl  ->    rpi 35  //both proto pins go to the same rpi pin
sda           ->    rpi 3
scl            ->    rpi 5
3.3v         ->    rpi 1
gnd          ->    rpi 6

see pinout diagram for help with the rpi gpio numbering.

power on the rpi and open a terminal and type...

sudo nano /boot/config.txt

find and uncomment the first line and add the second...

#dtparam=audio=on
dtoverlay=audioinjector-wm8731-audio

press ctrl+o to save and ctrl+x to exit.

sudo reboot

again open a terminal and type...

alsamixer

first press F5 to show all controls, then...
* enable item 'Mic' (space)
* set item 'Mic Boost' to 100 (up arrow key)
* enable item 'Playback Deemphasis' (m key)
* disable item 'ADC High Pass Filter' (m key)
* set item 'Input Mux' to Mic (arrow keys)
* enable item 'Output Mixer HiFi' (m key)

alsamixer-proto

now you should be able to start jackd with for example...

jackd -P75 -dalsa -dhw:0 -r48000 -p256 -n2

and get decent in/out sound at 5.3ms jack latency.

AttachmentSize
Image icon alsamixer-proto.png126.01 KB

solar powered supercollider

here's how to run supercollider on power coming from the sun...

the main component is a raspberry pi zero with wifi that at startup creates a wireless access point, starts jackd+supercollider and launches a default sound patch.
to play around with the system and change the default sound log on to the access point with a laptop and start livecoding supercollider via the terminal or use the standard scide via vnc. one can for example also set up a couple of osc responders and let friends log on with their phones to control sounds.

front...
solarpoweredsupercollider02

back...
solarpoweredsupercollider01

the connections are pretty straightforward...

solarpanel -> dcdc converter -> battery -> rpi0 -> soundcard -> amplifier -> speaker(s)

the dcdc converter is taking the higher voltage coming out of the solar panel (~6v) and turns it into a stable 5v. this is then either charging the battery, or directly powering the raspberry pi. note that the amplifier also needs 5v and here i have taken that from pins 4 and 6 on the pi.

the powerbank battery is optional and can be omitted but then the solar panel will have to stay in the sun at all times - else the system will turn off or reboot when the power from the panel drops. the battery acts like a reservoir for when clouds are passing by but not only that - it also lets the system be used for a couple of hours in the evening.

material/modules needed:

* rpi zero w
* 8gb micro sd card
* 5v usb powerbank (best if it can charge and output power at the same time)
* 6v 6watt solar panel ( www.adafruit.com/product/1525 )
* dc-dc converter ( www.olimex.com/Products/Power/DCDC6-16-TO5 )
* usb sound adapter
* pam8403 stereo amplifier module
* two full range speakers
* wooden board, double adhesive tape + various cables and screws

download raspbian jessie (here jessie desktop 2017-07-05-raspbian-jessie.zip) and burn it onto the sd card with etcher.

do the usual setup (change default password, activate ssh), optionally activate vnc and then install supercolliderStandaloneRPI1.

to set up a wifi access point do the following (basically the same as this)...

* sudo apt-get install dnsmasq hostapd
* sudo systemctl stop dnsmasq
* sudo systemctl stop hostapd
* sudo nano /etc/dhcpcd.conf  #and add...
        denyinterfaces wlan0
* sudo nano /etc/network/interfaces  #and make sure wlan0 looks like...
        allow-hotplug wlan0
        iface wlan0 inet static
            address 192.168.4.1
            netmask 255.255.255.0
            network 192.168.4.0
* sudo service dhcpcd restart
* sudo ifdown wlan0
* sudo ifup wlan0
* sudo nano /etc/dnsmasq.conf  #and add the following...
        interface=wlan0
        dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
* sudo nano /etc/hostapd/hostapd.conf  #and add the following...
        interface=wlan0
        driver=nl80211
        ssid=solarsc
        hw_mode=g
        channel=7
        wmm_enabled=0
        macaddr_acl=0
        auth_algs=1
        ignore_broadcast_ssid=0
        wpa=2
        wpa_passphrase=mypass12345
        wpa_key_mgmt=WPA-PSK
        wpa_pairwise=TKIP
        rsn_pairwise=CCMP
* sudo nano /etc/default/hostapd  #and change to the following...
        DAEMON_CONF="/etc/hostapd/hostapd.conf"
* sudo service hostapd start
* sudo service dnsmasq start

last change the file mycode.scd and add this default sound (tweet0340)...

s.waitForBoot{
        play{a=SinOscFB;Mix(AllpassN ar:a.ar(midicps(Duty.ar(c=a.ar(1/[12,8])+3/24,0,Dseq([0,8,5,1,5,4,5]*round(c*18),inf))+60),c*2)/4)}// #SuperCollider
};

if it is distorting try lowering the volume in alsamixer.

udssrKontroll

after many years i finally got around to rebuild one of these boxes.

so this old soviet made device is now a wireless controller that send out osc. there are in total 34 buttons, 16 knobs and an additional rgb status led. it automatically connects via wifi to max or supercollider and run on 5v (usb powerbank).

kicad schematics, arduino firmware, supercollider classes and maxmsp abstractions attached below.

udssrKontroll01

the inside is quite a mess. i use an atmega168 together with six 4051 multiplexers to read all the inputs. the wifi module is an esp8266-01.

udssrKontroll02

AttachmentSize
Package icon udssrKontroll_1.0.zip116.03 KB

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

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

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

Pages

Subscribe to RSS - supercollider