Comunicazione seriale

Come abbiamo visto nel paragrafo dedicato possiamo utilizzare un cavo USB collegato alla porta seriale del computer per caricare su Arduino i programmi contenti le istruzioni per il suo funzionamento.

Possiamo utilizzare questo canale di comunicazione anche per:

Inviare

I dati sono trasmessi in byte e possono dunque assumere valori compresi tra 0 e 255 espressi in tre diversi formati: integer, floating point o ASCII.

Se inviamo un solo valore per volta possiamo utilizzare tutti e tre i formati mentre se vogliamo trasmettere più valori all'interno di un singolo pacchetto di dati possiamo farlo solo alltraverso il formato ASCII.

Singolo valore (int)

Colleghiamo Arduino e predisponiamo un sistema per testare la comunicazione tra i devices facendo accendere e spegnere un led (attuatore) collegato al pin 3 PWM con una resistenza da 220 Ohm.

image not found

Singolo valore (float)

Se vogliamo scrivere sulla porta seriale una cifra decimale (float) dobbiamo necessariamente convertirla in un numero intero (int) compreso tra 0 e 255 nel software che scrive sulla porta per poi trasformarla nuovamente in valore decimale nel software che legge dalla porta.

Singolo valore (ASCII)

Possiamo ottimizzare la trasmissione di dati attraveso la porta seriale convertendo i valori numerici in caratteri ASCII:

image not found

In Max possiamo realizzare direttamente questa conversione con l'oggetto atoi. Scarica il patch.

image not found

Mentre in SuperCollider dobbiamo effettuare una duplice conversione:

a = 23;         // int
a = a.asString; // int --> string
a = a.ascii;    // string --> ASCII					

In questa conversione ad ogni singolo numero intero compreso tra 0 e 9 ne corrisponde un altro come possiamo leggere nella tabella.

0 = 48

1 = 49

2 = 50

Fino a quando non si supera la cifra singola (da 0 a 9) non c'è alcun problema nella trasmissione dei dati, ma siccome i valori trasmissibili sono compresi tra 0 e 255 avremo numeri composti da due e tre cifre.

12 = 49 50 (1-2)

31 = 51 49 (3-1)

130 = 49 51 48 (1-3-0)

In questi casi per ogni singolo valore numerico ne saranno trasmessi due (per quelli di due cifre) o tre (per quelli di tre cifre) in modo sequenziale (uno dopo l'altro) generando una stringa ininterrotta di numeri:

.. 48 51 57 55 53 54 49 52 51 48 50 53 49..

rendendone di fatto impossibile la riconversione. Verifichiamolo.

Per ovviare a questo inconveniente possiamo aggiungere il valore 13 alla fine di ogni lista che in formato ASCII significa \cr (a capo) separando in questo modo i singoli valori composti da 2 o tre cifre.

12          31         130
1    2 \cr   3   1 \cr   1   3   0  \cr
49  50  13  51  49  13  49  51  48   13

Inoltre dobbiamo sostituire nel codice di Arduino Serial.read() con Serial.parseInt() che legge in maniera corretta questo formato e converte automaticamente il valore in int.

int val;     
void setup()
{
 Serial.begin(9600); 
 pinMode(3,OUTPUT);
}
void loop() {
  if (Serial.available()) {      
             val = Serial.parseInt(); // cerca il primo int 
                                      // separato da 13 
             Serial.print("convertito: ");
             Serial.println(val); 
             analogWrite(3,val);
  };   
}

// DOPO 1000ms CHE NON RICEVE DATA RIPORTA '0' 
// UNA PRIMA SOLUZIONE (C) E' INVIARE CONTINUAMENTE UN DATO
// METTENDO UN METRO IN MAX, SPEGNENDO IL METRO SI RESETTA
// IL SISTEMA

Per ragioni tecniche Serial.parseInt() scrive automaticamente il valore 0 (Timeout) dopo un secondo dall’ultimo dato ricevuto. Se non vogliamo che questo avvenga nello scorrere del flusso di valori abbiamo due possibilità:

Per verificare:

Se invece volessimo utilizzare SuperCollider il codice è il seguente.

s.boot;
SerialPort.devices;
SerialPort.closeAll;
p = SerialPort.new("/dev/cu.usbmodem14201", 9600, crtscts:true);


a = rand(255).postln; // int
a = a.asString;       // int --> string
a = a.ascii;          // string --> ASCII


// Routine che invia continuamente bit alla porta seriale

(
a = 150.asString.ascii ++ 13; // converte, inserisce \cr (13) e crea un Int8Array

r = Routine({
            inf.do({
                    p.putAll(a);  // Scrive sulla porta
                    0.05.wait     // è necessario un downsampling
                    })
             }).reset.play;
)

a = 0.asString.ascii ++ 13;
a = 20.asString.ascii ++ 13;
a = 100.asString.ascii ++ 13;
a = 255.asString.ascii ++ 13;

(
a = rand(255).asString.ascii ++ 13;
a.postln;
)

r.stop;
p.close;

Se apriamo il monitor seriale in Arduino si scollega automaticamente la porta in SuperCollider quindi possiamo monitonarne il funzionamento solo attraverso la prototipazione.

Più valori (ASCII)

image not found

Se volessimo inviare più valori contemporaneamente a uno o più dispositivi attuatori come i led nella configurazione illustrata nell'immagine possiamo impacchettarli in una lista o in un Array nel software che utilizziamo per controllarli (in questo caso Max o SuperCollider).

Se invece vogliamo inviare valori da SuperCollider:

s.boot;
SerialPort.devices;
SerialPort.closeAll;
p = SerialPort.new("/dev/cu.usbmodem14201", 9600, crtscts:true);

// Routine che invia continuamente bit alla porta seriale
(
a = [0,0,0,0,0,0].asString.ascii ++ 13; // Converte, inserisce \cr (13) e crea un Int8Array

r = Routine({
             inf.do({
                     p.putAll(a);  // Scrive sulla porta
                     0.05.wait     // è necessario un downsampling
	                 })
             }).reset.play;
)

r.stop;d.stop;a = [0,0,0,0,0,0].asString.ascii ++ 13; // Stop e reset

p.close;

a = [  0,  0,  0,  0,  0,  0].asString.ascii ++ 13; // Crea un Array con \cr
a = [  0, 20, 40, 60, 80,100].asString.ascii ++ 13;
a = [255,  0,255,  0,255,  0].asString.ascii ++ 13;
a = [  0,255,  0,255,  0,255].asString.ascii ++ 13;
a = [255,180,120, 80, 40,  0].asString.ascii ++ 13;
a = Array.rand(6,0,255).asString.ascii ++ 13;	

Se apriamo il monitor seriale in Arduino si scollega automaticamente la porta in SuperCollider quindi possiamo monitonarne il funzionamento solo attraverso la prototipazione.

Ricevere

Se vogliamo ricevere dati da uno o più sensori analogici possiamo collegarlo a uno dei sei pins dedicati di Arduino.

Questo tipo di sensori in genere ha tre piedini o tre pins.

Dovremo collegre il filo positivo e negativo (rosso e nero) a due di essi per fornire al sensore la corrente necessaria al funzionamento, mentre sul terzo ci sarà una tensione che cambierà dinamicamente al variare del parametro fisico monitorato fornendoci i valori da mappare (debitamente riscalati) su uno o più parametri di un algoritmo di sintesi o di elaborazione del suono in Max o SuperCollider.

Singolo sensore analogico

Nei prossimi esempi utilizzeremo un sensore di prossimità (4-30cm) a raggi infrarossi Sharp che necessita di una tensione di alimentazione compresa tra 4.5V e 5.5V (non dovremo mettere resistenze nel circuito), fornisce un segnale di uscita compreso tra 3.1V (4cm) e 0.3V (30cm) e consuma circa 33mA di corrente.

image not found

Verifichiamo dal datasheet del sensore l'ordine in cui sono posizionati i collegamenti.

Arduino IDE

Scarichiamo e carichiamo su Arduino lo sketch Sharp_1. Il codice è semplice in quanto dobbiamo solo definire su quale pin analogico abbiamo collegato il filo della tensione in uscita e aggiornare continuamente una variabile con i valori che arrivano.

const int pinAn = A0;   // Dichiara il pin analogico dal quale
                        // leggere i valori di tensione
int val;                // variabile che conterrà i valori
void setup() {
  Serial.begin(9600);
  pinMode(pinAn, INPUT); // può essere omesso per i pin analogici
}

void loop() {
  val = analogRead(pinAn); // legge i valori dal pin
  
  Serial.flush();          // "pulisce" la memoria della 
                           // porta seriale
  Serial.println(val);     // scrive sulla porta seriale
                           // in valori ASCII separando i singoli
                           // numeri dal carattere 10 (newline)
  delay(50);
}	

Median Filter

Se apriamo il monitor seriale possiamo leggere i valori in ingresso e capire entro quale range lavora il sensore.

image not found

Osservando attentamente i valori possiamo notare come si verifichino frequenti "sfarfallii" ovvero all'intero della sequenza numerica (numeric stream) ci siano numeri molto distanti dal valore che li precede e quello che li segue (rumore digitale):

...87 88 89 90 456 95 96 97 23 75 74 73 72 567...

rendendo di fatto problematico il mappaggio di questi valori su un parametro di controllo di un segnale audio.

Per eliminare questi valori e rendere più continuo il segnale possiamo applicare un filtro mediano (median filter) direttamente in Arduino (potremmo applicarlo anche in Max o SuperCollider) ma essendo una problematica esterna a questi software è preferibile risolverla all'origine).

A questo link possiamo scaricare una libreria di Arduino che fornisce diversi tipi di filtri dati tra i quali un filtro mediano. Per utilizzare una libreria in uno sketch dobbiamo:

Riscalaggio e clipping

A questo punto possiamo verificare entro quale range sono compresi i valori per poi poterli eventualmente riscalare in modo corretto.

Il costruttore dichiara che nell'ambito entro il quale il sensore risponde linearmente vanno da 80 (30cm) a 530 (4cm) e che possiamo effettuare una conversione di questi in centimetri attraverso la formula:

Distanza (cm) = 2076 / (Valore_sensore - 11)

Possiamo semplicemente sostituire la seguente riga di codice:

Serial.println(2076 / (valFilt-11));

Notiamo che però anche i valori che oltrepassano i due limiti continuano a comparire. Possiamo effettuare un clipping del range con la funzione constrain(val,min,max);

Serial.println(constrain(valFilt,80,530));

Per riscalare i valori possiamo anche utilizzare la funzione map(val,minIn,maxIn,minOut,maxOut); che ci permette di ottenere facilmente valori compresi tra due valori limite qualsiasi. Nel nostro caso un ambito che può consentirci ulteriori riscalaggi in Max o SuperCollider potrebbe essere quello tra zero e mille (possiamo utilizzare solo numeri interi).

Serial.println(map(constrain(valFilt,80,530),80,530,1000,0));

Max

Se vogliamo utilizzare in Max i valori scritti sulla porta seriale da Arduino dobbiamo convertirli in int (per consentire la trasmissione a 8 bit sono stati scritti come valori ASCII).

Scarichiamo e lanciamo il patch 6_analog_1.maxpat.

image not found

SuperCollider

Se vogliamo utilizzare in SuperCollider i valori scritti sulla porta seriale da Arduino dobbiamo utilizzare il metodo p.read che legge i valori dalla porta seriale specificata tra quelle presenti posto all'interno di una Routine infinita. I dettagli sono commentati nel codice.

Ricodiamoci di uscire eventualmente da Max o dal monitor seriale di Arduino altrimenti saranno postati errori.

SerialPort.devices;
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);

(
r = Routine({
             var byte, str, val;
             inf.do{|i|
                    if(p.read == 10, // se il valore in ingresso è 10 (newline)

                        {str = "";                          // crea una stringa vuota e,
                         while({byte = p.read; byte !=13 }, // fino a che il valore in
                                                            // ingresso è diverso da
                                                            // 13 (return)
                                {str = str++byte.asAscii}); // riempila con i valori in
                                                            // ingresso convertiti in
                                                            // ASCII

             val = str.asInteger;                          // assegna i valori convertiti
                                                           // in int alla variabile
                                                           // locale 'val'
            ("valore in ingresso:"+val).postln;            // stampa
                       });
                   };
             }).play;
)

r.stop;
p.close;	

Esempio wa wa (Max)

In questo esempio il sensore controlla dinamicamente la frequenza di taglio di un filtro passa basso.

Ricordiamo che l'audio file e il patch devono essere nella stessa cartella.

image not found

Esempio scratch (SuperCollider)

In questo esempio il sensore controlla dinamicamente un puntatore che legge un audio file.

SerialPort.devices;
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);       // Connette la
                                                                    // porta Seriale
s.boot;                                                             // Boot Server Audio
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // Crea un Buffer e
                                                                    // carica un File
// ------------------------------------ Synthdef e Synth
(                                    
SynthDef(\scrat,{arg pos,smooTh;
                 Out.ar(0,
                        BufRd.ar(b.numChannels,
                                 b.bufnum,
                                 VarLag.ar(K2A.ar(pos),smooTh,warp:\lin)
                                 );
                       )
}).add;

{a = Synth(\scrat,[\pos,0,\smooTh,0.2])}.defer(0.3);
)

// ------------------------------------ Legge, converte, riscala e invia i valori

(
c = b.numFrames;    // lunghezza in frames del Buffer
r = Routine({
             var byte, str, val;
             inf.do{|i|
                    if(p.read == 10,
                       {str = "";
                        while({byte = p.read; byte !=13 },
                              {str = str++byte.asAscii}
							  );
                        val = str.asInteger;
                        val = val.linlin(0,1000,0,c); // riscala lineare
                        val.postln;                   // stampa
                        a.set(\pos,val);              // invia al Synth
		              });
                   };
             }).reset.play;
)

r.stop;     // ferma la Routine
s.freeAll;  // distrugge Synth e Buffer dal Server
p.close;    // chiude la porta seriale	

Singolo sensore digitale

Nei prossimi esempi utilizzeremo un sensore di inclinazione (tilt sensor) che come si evince dal suo spreadsheet necessita di una tensione di alimentazione al di sotto dei 12V, consuma circa 20mA di corrente e necessita di una resistenza di 10 KOhm .

image not found

Questo sensore funziona come un pulsante (switch): al suo interno c'è una pallina che a seconda dell'inclinazione chiude uno tra due contatti riportando hight (1) oppure low (0).

Arduino IDE

Scarichiamo e carichiamo su Arduino lo sketch Inclinazione_1.zip. Il codice è semplice in quanto dobbiamo solo definire a quale pin digitale è collegato il filo della tensione in uscita dal sensore e aggiornare continuamente una variabile con i valori che arrivano.

const int Tilt = 7;    // costante pin ingresso
int val;               // variabile val

void  setup()
{
  pinMode(Tilt, INPUT); 
  Serial.begin(9600);
}
void  loop()
{
  val = digitalRead(Tilt); // legge il valore
       Serial.print(val);  // lo stampa
	   delay(50); // downsampling
}

La pallina all'interno del sensore è molto sensibile e i dati in ingresso possono sfarfallare esattamente come avviene con i sensori analogici. In questo caso per risolvere il problema e stabilizzare il data stream al posto di un median filter dobbiamo adottare tecniche di debouncing ovvero effettuare una sorta di sample and hold dei valori in ingresso.

Debouncing

Scarichiamo e carichiamo su Arduino lo sketch Inclinazione_2.zip.

Il codice seguente illustra come realizzare un debouncing specificando una zona morta temporale all'interno della quale i cambiamenti non sono presi in considerazione. Per verificare apriamo il monitor seriale.


const int Tilt = 7;    // costante pin ingresso
int val;               // variabile valore attuale
int prev = HIGH;       // valore precedente

unsigned long delta = 0;  // tempo delta tra cambi stato
unsigned long zonaM = 50; // zona morta in ms; 

void setup() {
  pinMode(Tilt, INPUT);
  Serial.begin(9600);
}

void loop() 
{ val = digitalRead(Tilt); // legge il valore

 // se è diverso dal precedente aggiorna il tempo delta
  if (val != prev) {delta = millis();}
  
 // se è > della zona morta cambia stato
  if ((millis() - delta) > zonaM) {Serial.print(val);}
 // resetta la variabile ad ogni giro
   prev = val;
   delay(50); // downsampling
  }

Cercare nel Reference di Arduino IDE gli oggetti che non conosciamo.

Max

Scarichiamo e lanciamo il patch 8_digital_1.maxpat.

Il patch di Max illustra i due possibili modi di utilizzo dei dati provenienti da un sensore digitale:

image not found

SuperCollider

Anche in SuperCollider possiamo utilizzare due modalità.

SerialPort.devices;
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);

// ----------------------- Segnale continuo
(
r = Routine({var str, val;
             inf.do{
                    str = "";
                    str = str++p.read.asAscii;
                    val = str.asInteger; 
                    val.postln; 
                  }
             }).play;
)

r.stop;
p.close;

// ----------------------- Trigger
(
r = Routine({var str, val, prec=0;
             inf.do{
                    str = "";
                    str = str++p.read.asAscii;
                    val = str.asInteger; 
                    if((val != prec),  // se diverso dal precedente
                       {val.postln;    // esegue
                        prec = val }); // riassegna 'prec' all'ultimo
                   }
             }).play;
)

r.stop;
p.close;

Esempio trigger (Max)

Scarichiamo e lanciamo il patch 8_digital_1.maxpat.

image not found

Esempio gate (SuperCollider)

s.boot;
SerialPort.devices;

(
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

SynthDef(\grain,
                {arg gate=0;
                 var trate, dur, sig;
                     trate = LFNoise1.kr(0.8).range(2,120);
                     dur = 1.2 / trate;
                     sig = TGrains.ar(2, Impulse.ar(trate), b, (1.2 ** WhiteNoise.kr(3).round(1)), LFNoise1.kr(3).range(0,BufDur.kr(b)), dur, WhiteNoise.kr(0.6), 0.1);
                 Out.ar(0,sig * gate.lag(0.2))
}).add;
)

(
a = Synth(\grain);
r = Routine({var str, val, prec=0;
             inf.do{
                    str = "";
                    str = str++p.read.asAscii;
                    val = str.asInteger; 
                    if((val != prec),  
                        {a.set(\gate,abs(1-val));
                         abs(1-val).postln;
                         prec = val }); 
                   }
             }).play;
)

r.stop;
p.close;					

Singolo sensore PWM

Nei prossimi esempi utilizzeremo un sensore di prossimità a ultrasuoni (HC-SR04) (2-400cm) che come si evince dal suo spreadsheet necessita di una tensione di alimentazione di 5V, consuma circa 15mA di corrente e il suo funzionamento è ottimale all'interno di un angolo di 30°.

image not found

image not found

Questo sensore utilizza due trasduttori ad ultrasuoni. Un impulso a 5 volt di almeno 10 μS (microsecondi) di durata viene applicato al pin Trigger generando nel primo trasduttore (Trig) un treno di 8 impulsi ultrasonici a 40 KHz che si allontanano dal sensore viaggiando nell’aria circostante. Il segnale sul Pin Echo intanto diventa alto ed inizia la registrazione del tempo di ritorno in attesa dell’onda riflessa. Se l’impulso non viene riflesso il segnale su Echo torna basso dopo 38 ms (millisecondi) e va interpretato come assenza di ostacolo, altrimenti si calcola il tempo che ha impiegato nel suo tragitto che corrisponde alla distanza (tempi maggiori corrispondono a distanze maggiori).

image not found

image not found

Arduino IDE

Scarichiamo e carichiamo su Arduino lo sketch Pwm_1.zip. In rete possiamo trovare numerosi tutorials sulla programmazione di Arduino per questo sensore, in questo caso ho deciso di utilizzare una libreria dedicata che si chiama NewPing. Possiamo analizzare direttamente il codice.

#include       // Libreria per il sensore
#include       // libreria per il median filter

medianFilter Filter;

#define TRIGGER_PIN 5    // Pin PWM del trigger
#define ECHO_PIN  6      // Pin PWM dell'echo
#define MAX_DISTANCE 100 // Distanza massima in cm.
 
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
 
int SetDistance = 0;
int ValueDist   = 0;
int valFilt;
 
void setup() {
 Serial.begin(9600);
 Filter.begin();
}
 
void loop() {
 delay(100);                       // Aspetta 100ms tra i pings. 
                                   // (29ms minimo) 
 unsigned int uS = sonar.ping();   // Manda il pings, rileva il 
                                   // ritorno in microsecondi (uS).
 ValueDist = uS / US_ROUNDTRIP_CM; // Calcola la distanza (converte 
                                   // il tempo di ping in distanza 
                                   // in cm)
 valFilt = Filter.run(ValueDist);  // Valori filtrati
 Serial.println(valFilt);          // Scrive sulla porta seriale
}

Aggiungiamo anche in questo caso un median filter per ottenere un flusso di valori più preciso e stabile. Per verificare il funzionamento del sensore possiamo aprire il monitor seriale ricordando che il range ottimale è compreso tra 2 e 100 centimetri.

Sample and Hold

Come possiamo notare dalla lettura del monitor seriale nello scorrere del flusso di dati si verificano delle imprecisioni quando l'onda ultrasonica riflessa non viene rilevata magari anche solo per un istante. Questo inconveniente genera uno o più zeri consecutivi. Per ovviare a questo inconveniente dobbiamo programmare un sample and hold che entra in azione ogni qualvolta il valore è 0.

Il concetto di base è: se il valore è uguale a zero riporta il valore precedente.

La realizzazione di questa tecnica in Max o in SuperCollider è illustrata all'interno dei codici seguenti.

Max

Scarichiamo e lanciamo il patch 10_pwm_1.maxpat.

Il patch è simile ai precedenti. Ci sono però tre accorgimenti che aiutano a stabilizzare ulteriormente il flusso di valori:

image not found

SuperCollider

Anche in SuperCollider dobbiamo realizzare gli stessi accorgimenti adottati in Max per stabilizzare il flusso di dati. I passaggi sno gli stessi ma come possiamo osservare da un'analisi del codice per effettuare uno smoothing lineare dobbiamo trasformare i valori in segnale di controllo

SerialPort.devices;

(
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);

d = {arg val=0; VarLag.kr(val,0.8)}.scope;              // Smoothing

r = Routine({
             var byte, str, val, prec=0;
             inf.do{|i|
                    if(p.read == 10, 
                        {str = "";                         
                         while({byte = p.read; byte !=13 }, 
                                {str = str++byte.asAscii}); 

                         val = str.asInteger;                         
                                                           
			 if((val == 0), {val = prec}, {prec = val });  // Filtra gli 0	
			 val.postln;                                   // Stampa
			 d.set(\val,val.linlin(0,100,0,1))	       // Invia allo smoothing
                       });
		     0.1.wait;                                         // Downsampling
                   };
             }).play;

)

(
r.stop;
d.free;
p.close;
)

Crossfades

Scarichiamo e lanciamo il patch 11_pwm_2.maxpat.

In questo caso utilizziamo il flusso di dati in entrata e un tempo di interpolazione lungo per realizzare un crossfade dinamico tra due segnali differenti.

image not found

Soglie e triggers

In questo esempio vediamo come splittare un segnale continuo come quello che proviene da un sensore di prossimità in diversi ambiti numerici per generare trigger ad ogni passaggio da un ambito all'altro.

s.boot;
s.plotTree;
SerialPort.devices;

(
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);

Buffer.freeAll;                            // Elimina eventuali Buffers allocati in precedenza
b = 3.collect{Buffer.read(s,               // Genera 3 Buffers diversi
	            Platform.resourceDir +/+ "sounds/a11wlk01.wav",
	            rand(44100*4),         // Inizio
	            rrand(4410,88200))};   // Durata

SynthDef(\player, {arg buf=0,trig=0;
	               var sig, env;
	                   sig = PlayBuf.ar(1,buf,1,trig,0);
	                   env = EnvGen.kr(Env.asr(Rand(0.1,0.3),1,0.1),trig);
                   Out.ar(0,sig*env)
}).add;
)

(
a = Synth(\player, [\buf,b[0]]); // Player Buffer 0
c = Synth(\player, [\buf,b[1]]); // Player Buffer 1
d = Synth(\player, [\buf,b[2]]); // Player Buffer 2

r = Routine({
             var byte, str, val=0, prec=0;
             inf.do{|i|
                    if(p.read == 10, 
                        {str = "";                         
                         while({byte = p.read; byte !=13 }, 
                               {str = str++byte.asAscii}); 

                         val = str.asInteger;                                
                         if((val == 0), {val = prec}, {prec = val });  // Filtra gli 0	
                         val.postln});
                    case
                        {(val > 0)  && (val  < 10)} {a.set(\trig,1); // Tra 0 e  10
                                                     c.set(\trig,0);
                                                     d.set(\trig,0)} 
                        {(val > 10) && (val  < 20)} {a.set(\trig,0); // Tra 10 e  20
                                                     c.set(\trig,1);
                                                     d.set(\trig,0)} 
                        {(val > 20) && (val  < 30)} {a.set(\trig,0); // Tra 20 e  30
                                                     c.set(\trig,0);
                                                     d.set(\trig,1)}; 
                     0.1.wait;                                       // Downsampling
                      };
             }).play;
)

(
r.stop;
b.free;
a.free;c.free;d.free;
p.close;
)		

Più sensori