«first 1 2 3 4 5 6 7 last»

udssrKontroll

2017-07-24 17:27 electronics, supercollider

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 MaxMSP or SuperCollider and runs on 5V (USB power bank).

KiCad schematics, Arduino firmware, SuperCollider classes and MaxMSP abstractions attached below.

udssrKontroll photo 1

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.

udssrKontroll photo 2

Updates:

  • 190124: v1.1 fixed a small but breaking bug in the ATmega168 code.
Attachments:
udssrKontroll_1.1.zip

f08ch

2017-07-03 01:14 electronics

This box can drive eight speakers (3 Watt each) and play sound files independently from eight micro SD cards (MP3 or Wav). An Arduino Nano is programmed to do sequencing, randomise playback, control volume and set equaliser settings. It all runs on a single 9-12V power supply.

f08ch photo

Very cheap to build. The biggest cost is all the SD cards.

To send commands out to all eight MP3 players, I'm bit banging 9600 baud serial data out on eight digital pins.


f0led

2017-05-14 12:43 electronics

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 and OSC (Open Sound 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 sends 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 photo 1

The battery is a 12V sealed lead-acid and I measured up toward 8A 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 = "mypass123";  //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 photo 2

f0led photo 3

Updates:

  • 171124: Added two more photos, a MaxMSP patch and updated the Arduino code with static IP option
Attachments:
f0ledtest.maxpat.zip

f0neo

2017-05-13 15:01 electronics

Here is how I build super cheap wireless OSC controlled RGB led strips. The main components for these are an ESP8266, a 5V power bank, 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 phot 1

f0neo photo 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 = "mypass123";  //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 MaxMSPJitter and SuperCollider examples and KiCad schematics.

Attachments:
f0neo.zip

f0dmx

2017-05-09 21:04 electronics

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). Open Sound 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 photo 1

f0dmx photo 2

Below is Arduino code for the ESP8266, Bill-Of-Material, 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 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);

Updates:

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

Optoforce

2017-02-10 18:38 other

Here's some Python code for reading serial input from Optoforce's 3D sensor and sending it over OSC to MaxMSPJitter or SuperCollider.

The slightly odd baud rate of 1000000 isn't supported in SuperCollider nor MaxMSPJitter under macOS, 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()

SuperCollider Firmata 3

2017-01-03 21:45 supercollider

Reading digital inputs from an Arduino with the SCFirmata is a little bit more complicated than needed.

Here an example that reads 6 analogue 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 pull-ups.)

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

/f0blog/supercollider-firmata-2

/f0blog/supercollider-firmata


SuperCollider Firmata 2

2017-01-02 18:12 supercollider

+2 years ago I put up a simple example of how to use firmata with Arduino and SuperCollider. See /f0blog/supercollider-firmata/. That code still works but it only shows how to read a single analogue 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 analogue 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 analogue 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;
)

«first 1 2 3 4 5 6 7 last»