Creazione di un controller MIDI con Arduino
Cos’è MIDI?
MIDI sta per Musical Instrument Digital Interface, è uno standard creato per lo scambio di dati digitali fra strumenti musicali elettronici a prescindere dalla marca e dal modello. Grazie al MIDI gli strumenti musicali elettronici possono colloquiare fra loro e con i computer a un livello inimmaginabile una ventina di anni fa. Senza il Midi, la registrazione degli eventi musicali, la programmazione dei mixer, e l’uso estensivo dei computer in sala di registrazione e anche dal vivo non sarebbe possibile.
Se volete approfondire il modo del MIDI vi consiglio di buttare un occhio qui.
Oggi come oggi i controller MIDI vengono utilizzati, non solo per la partitura musicale, ma anche come dei controller attraverso i quali si interagisce direttamente con una macchina o un software presente su un computer. I campi di applicazione si sono quindi estesi anche a settori che prima non ne avevamo bisogno (ad esempio oggi vengono creati controller MIDI per l’ambito medico, o anche per chi deve gestire un impianto Luci di un palco o per chi fa VideoMapping o come nel mio caso, nell’ambito del Djing).
Arduino … quale scegliere?
Com’è possibile notare direttamente dal sito Arduino, esistono tanti modelli di schede, da quelle più grandi (con tanti ingressi e uscite) a quelle più piccole o anche da chi dispone di connettività wireless, bluethoot, etc,etc… in realtà la scelta della scheda da utilizzare è molto importante per cui per prima cosa cerchiamo di capire cosa vogliamo costruire e poi sceglieremo la scheda più adatta alle nostre esigenze.
Personalmente, dato che nel mio progetto con Arduino volevo creare un controller MIDI USB, ho focalizzato la mia ricerca sul chip ATMEGA32u4 (chip montato sulle Arduino Leonardo e anche sulla MEGA). Questo chip è direttamente collegato alla porta USB, senza passare per convertitori seriali, il che consente di programmare l’interfaccia USB, nel senso che volendo gli si può impostare che quando viene collegata ad un computer, la sua interfaccia USB verrà visualizzata come tastiera o mouse o controller MIDI a seconda di come venga programmata.
Di quali controlli abbiamo bisogno?
Vi sono una marea di controlli che possiamo collegare alla nostra Arduino ma, in sostanza sono solo di due tipi: Analogici o Digitali.
Un segnale analogico può assumere qualsiasi valore (all’interno di un range noto). Con notevole semplificazione, si può pensare che siano “analogiche” tutte le grandezze fisiche misurabili nell’ambiente.
Un segnale digitale può assumere due soli stati (High/Low, 1/0), corrispondenti a due livelli di tensione convenzionali (ad esempio, 5-0V). Con simile semplificazione, si può pensare che siano “digitali” tutte le informazione scambiate tra componenti logici (microprocessori, memorie, interfacce di rete, display…).
Come viene interpretato un segnale Analogico o Digitale?
Affinchè possa essere letto ed elaborato da Arduino, il segnale analogico deve essere campionato, ovvero convertito in una sequenza di bit che ne esprime l’ampiezza.
Un segnale digitale è immediatamente “leggibile” non appena ne è stato discriminato il livello (Hight/Low).
Del segnale analogico interessa leggere il valore istantaneo, opportunamente campionato.
Del segnale digitale occorre sapere solo lo stato alto o basso.
Arduino come riconosce se un segnale è di ingresso o di uscita?
Affichè Arduino riconosca se sul PIN a cui facciamo riferimento sia presente un segnale di Ingresso o Uscita, i PIN digitali devono essere preventivamente impostati in modalità input o output. Questo avviene tramite pinMode.
Esempio: pinMode(13, OUTPUT);
imposta il PIN 13 in modalità OUTPUT. Questo significa che dal PIN 13 può “uscire” un segnale digitale e quindi potrà risultare Alto o Basso. (In pratica a livello elettronico potrà essere emessa una tensione che assumerà due soli valori, 5V o 0V).
Esempio: pinMode(2, INPUT);
Questo esempio, invece, imposta il PIN 2 in modalità INPUT. Questo significa che dal PIN 2 può “entrare” un segnale digitale che come nel caso precedente dovrà risultare Alto o Basso.
Da notare bene che solo i PIN Digitali vanno preventivamente impostati, quelli analogici no.
Come leggo o scrivo sui PIN?
La lettura e Scrittura avvengono attraverso funzioni dedicate.
Per i PIN Digitali abbiamo:
digitalWrite() e digitalRead()
Per i PIN Analogici invece:
analogWrite() e analogRead()
Esempio di “scrittura” Digitale :
pinMode(13, OUTPUT); //Imposto il PIN 13 in modalità OUTPUT digitalWrite(13, HIGH); //Scrivo sul PIN 13 il valore Alto
Questo codice imposta prima il PIN 13 in modalità OUTPUT (come visto in precedenza). Successivamente “Scrive” sullo stesso PIN un valore Alto.
Esempio di “lettura” Analogica:
int valore; //Dichiaro una variabile intera di nome "valore" valore = analogRead(1); //Assegno alla variabile la lettura del PIN 1
Questo codice dichiara una variabile intera di nome “valore”. Succesivamente assegna a quest’ultima il valore analogico “letto” sul PIN Analogico 1.
Dove sono i PIN di INPUT/OUTPUT su Arduino Leonardo?
Differenza tra Input Digitale e Input Analogico
- A differenza di un Ingresso Digitale che può assumere i due valori LOW (basso) e HIGH (Alto) che corrispondono rispettivamente ad una tensione nulla (GND) o positiva (+5V), con un Ingresso Analogico è possibile leggere tensioni comprese tra 0 e +5V.
- La funzione analogRead(), come abbiamo visto nell’esempio precedente, riceve come parametro il numero del PIN Analogico da leggere e restituisce un numero intero assegnandolo ad una variabile.
Il numero intero restituito è compreso tra 0 e 1023. Questo significa che il segnale analogico in ingresso viene campionato con una risoluzione di 10 Bit (2^10=1024), ovvero divedendo la massima tensione (+5V) applicabile all’ingresso per 1024 otteniamo una unità di 4,9 mV (la risoluzione massima espressa in Volts).
Qualsiasi grandezza opportunamente trasformata in una tensione può essere letta da Arduino:
- Temperatura: sensore di temperatura
- Rotazione: potenziometro
- Intensità luminosa: fotoresistenza
- Distanza: sensore ad infrarossi
- Inclinazione: accelerometro
- …
Fatta questa premessa sui pin di Arduino, torniamo a parlare del nostro controller MIDI.
I controlli che ho voluto integrare sono dei bottoni di tipo “soft” button normalmente aperti e dei potenziometri logaritmi.
Nell’esempio riportato qui di seguito ho utilizzato:
- 1 Arduino Leonardo
- 4 potenziometri logaritmi
- 4 bottoni (soft button) normalmente aperti
- 4 resistenze da 1Kohm
I Collegamenti
I 4 potenziometri sono direttamente collegati alle rispettive porte analogiche A0, A1 A2 e A3.
Qui di seguito riporto un dettaglio dei collegamenti di un potenziometro
Come potete vedere nell’immagine sopra riportata, il potenziometro per funzionare ha bisogno di tre collegamenti: il primo partendo dal basso, va collegato ai 5v di Arduino, il secondo al pin Analogico ed il terzo alla massa (GND).
Invece i 4 bottoni sono rispettivamente collegati alle porte digitali D2, D3, D4 e D5, ma per un corretto funzionamento hanno la necessità di una resistenza (questo per evitare problemi di “flickering“).
Qui di seguito riporto un dettaglio di collegamento di un “soft” button o anche “momentary” button.
In questo esempio potete notare che un pin del nostro bottone va direttamente ai 5v di Arduino, mentre il secondo pin dovrà essere connesso sia ad una resistenza da 1Kohm che a sua volta andrà a massa, che al pin digitale che riceverà l’input.
Ora che abbiamo appreso come effettuare i collegamenti dei nostri potenziometri e dei nostri bottoni possiamo cominciare a vedere il codice di Arduino passo passo.
Le Librerie
#include <Bounce2.h> #include "MIDIUSB.h"
La prima libreria <Bounce2.h> ci servirà per gestire i bottoni mentre la seconda ci servirà per attivare la modalità MIDI (e tutte le funzioni inerenti) sulla nostra scheda.
Entrambe le librerie vanno installate tramite il gestore librerie del nostro Arduino IDE.
#define …
subito dopo aver dichiarato le librerie andiamo a definire i nostri pin attribuendogli un nome più semplice, in modo tale che mentre scriveremo o rileggeremo il codice ci semplificherà la traduzione
#define button1Pin 2 #define button2Pin 3 #define button3Pin 4 #define button4Pin 5 #define pot1Pin A0 #define pot2Pin A1 #define pot3Pin A2 #define pot4Pin A3
Inoltre andremo a definire il nostro canale MIDI (il canale 0 sulla nostra Arduino corrisponderà al canale 1 MIDI)
#define CHANNEL 0
Dichiarazione delle Variabili
In questa sezione andrò ad utilizzare dei vettori per contenere le varie note che i nostri bottoni e potenziometri andranno a richiamare quando vengono utilizzati
int ButtonNote [4] = {60, 61, 62, 63}; int PotNote [4] = {17, 18, 19, 20};
In più , utilizzando sempre i vettori, andrò a dichiarare i pin dei bottoni e dei potenziometri.
int ButtonPin [4] = {2, 3, 4, 5}; int PotPin [4] = {A0, A1, A2, A3};
ora dobbiamo definire il Bounce per i nostri bottoni
Bounce Btn1 = Bounce(ButtonPin[0],5); Bounce Btn2 = Bounce(ButtonPin[1],5); Bounce Btn3 = Bounce(ButtonPin[2],5); Bounce Btn4 = Bounce(ButtonPin[3],5);
e i nostri potenziometri più delle variabili dove registrare il valore letto in precedenza
uint8_t Pot1, Pot2, Pot3, Pot4, prevPot1, prevPot2, prevPot3, prevPot4;
void noteOn(),void noteOff() e void ControlChange()
qui di seguito invece le tre funzioni che si occuperanno di inviare le note tramite il protocollo MIDI attraverso la porta USB della nostra scheda Arduino
void noteOn(byte channel, byte pitch, byte velocity) { midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); } void noteOff(byte channel, byte pitch, byte velocity) { midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity}; MidiUSB.sendMIDI(noteOff); MidiUSB.flush(); } void controlChange(byte channel, byte control, byte value) { // First parameter is the event type (0x0B = control change). // Second parameter is the event type, combined with the channel. // Third parameter is the control number number (0-119). // Fourth parameter is the control value (0-127). midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value}; MidiUSB.sendMIDI(event); MidiUSB.flush(); }
void loop()…
Questa che segue, invece sarà la porzione di codice che verrà ripetuta all’infinito, fino a quando la scheda Arduino non verrà spenta.
In pratica non farà altro che controllare i 4 bottoni, poi controllerà i 4 potenziometri ed infine richiamerà le funzioni per l’invio delle note
void loop() { Btn1.update(); Btn2.update(); Btn3.update(); Btn4.update(); Pot1 = map(analogRead(PotPin[0]), 0, 1023, 0, 127); if (prevPot1 != Pot1) { controlChange(CHANNEL, PotNote[0], Pot1); prevPot1 = Pot1; } Pot2 = map(analogRead(PotPin[1]), 0, 1023, 0, 127); if (prevPot2 != Pot2) { controlChange(CHANNEL, PotNote[1], Pot2); prevPot2 = Pot2; } Pot3 = map(analogRead(PotPin[2]), 0, 1023, 0, 127); if (prevPot3 != Pot3) { controlChange(CHANNEL, PotNote[2], Pot3); prevPot3 = Pot3; } Pot4 = map(analogRead(PotPin[3]), 0, 1023, 0, 127); if (prevPot4 != Pot4) { controlChange(CHANNEL, PotNote[3], Pot4); prevPot4 = Pot4; } if (Btn1.fallingEdge()) noteOn(CHANNEL, ButtonNote[0], 127); else if (Btn1.risingEdge()) noteOff(CHANNEL, ButtonNote[0], 0); else if (Btn2.fallingEdge()) noteOn(CHANNEL, ButtonNote[1], 127); else if (Btn2.risingEdge()) noteOff(CHANNEL, ButtonNote[1], 0); else if (Btn3.fallingEdge()) noteOn(CHANNEL, ButtonNote[2], 127); else if (Btn3.risingEdge()) noteOff(CHANNEL, ButtonNote[2], 0); else if (Btn4.fallingEdge()) noteOn(CHANNEL, ButtonNote[3], 127); else if (Btn4.risingEdge()) noteOff(CHANNEL, ButtonNote[3], 0); }
Per scaricare il codice completo cliccare qui
Ciao, il vostri progetto l’ho trovato utile ed interessantissimo e vi ringrazio. Mi chiedevo però, perchè i tasti lavorano al contrario, cioè premendo il tasto si ha la nota off e solo al rilascio diventa ON? E’ un comportamento voluto?
Ciao, grazie per i complimenti, ora ricontrollerò il codice e il setup dell’hardware, non vorrei aver fatto confusione con lo schema di collegamento dei pulsanti. Momentaneamente se il comportamento è l’opposto a quello voluto, ti consiglio di intervenire direttamente sul codice.
Ciao, davvero un progetto super utile! A questo punto ti faccio una domanda, se volessi inserire dei sensori a questo progetto, come ad esempio un sensore di forza e un sensore ad ultrasuoni, che avranno la stessa funzione dei potenziometri, come dovrei fare? Grazie mille
Ciao Fabio, se sono analogici si comportano esattamente come un potenziometro per cui non devi fare grosse modifiche al codice.
Ciao uno di questi è un sensore ad ultrasuoni quindi ha uscita in digitale. In questo caso cosa posso fare?