Raspberry Pi Pico & Midi

Inviare MIDI tramite Raspberry Pi Pico

Recentemente abbiamo avuto l'esigenza di inviare segnali MIDI (via USB) ad una workstation sulla quale girava Unreal Engine.

Banalizzando, Unreal Engine interpreta i segnali MIDI animando oggetti in un mondo virtuale.

Per far ciò ho costruito di un controller MIDI con un Raspberry Pi Pico.

Il prototipo, oltre al Raspberry Pi Pico, include:

  • due tasti
  • un sensore di presenza
  • un potenziometro rotativo

Alla pressione di uno dei due tasti viene inviata una nota diversa. Idem per il sensore di presenza.

Ruotando il potenziometro invece viene inviata sempre la stessa nota ma con una "velocity" differente.

Il prototipo è il seguente: 

Prototipo

Il codice è scritto in Python (CircuitPython per la precisione) ed è il seguente:

import time
import random
import usb_midi
import adafruit_midi
import digitalio
import analogio
import board
import math

from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_midi.pitch_bend import PitchBend
from adafruit_midi.control_change import ControlChange

button1 = digitalio.DigitalInOut(board.GP16)
button1.direction = digitalio.Direction.INPUT
button1.pull = digitalio.Pull.DOWN

button2 = digitalio.DigitalInOut(board.GP17)
button2.direction = digitalio.Direction.INPUT
button2.pull = digitalio.Pull.DOWN

pir1 = digitalio.DigitalInOut(board.GP18)
pir1.direction = digitalio.Direction.INPUT
pir1.pull = digitalio.Pull.DOWN

pot1 = analogio.AnalogIn(board.GP26)

midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
pirDetected = False
oldPot1 = 0;

def getPot1Value():
    v1 = pot1.value;
    time.sleep(0.05)
    v2 = pot1.value;
    time.sleep(0.05)
    v3 = pot1.value;
    
    return int((100/65535)* (v1+v2+v3)/3)

while True:
    if button1.value:
        midi.send(NoteOn(44, 100))
        time.sleep(0.5)
    
    if button2.value:
        midi.send(NoteOn(45, 100))
        time.sleep(0.5)
    
    if pir1.value != pirDetected:
        pirDetected=pir1.value
        if pirDetected == False:
            midi.send(NoteOn(46, 100))
            
    potVal = getPot1Value()
    if  oldPot1 != potVal and abs(oldPot1 - potVal) > 1:
        oldPot1 = potVal        
        midi.send(NoteOn(47, oldPot1))
        time.sleep(0.1)

Nonostante la banalità del codice, c'è da fare qualche considerazione.

Gestione dei tasti

Quando si preme un tasto non viene generato un segnale pulito. Non si passa da zero a uno e basta, ma viene generata una forma d'onda molto sporca e con diversi picchi. Esistono diversi modi per ovviare a questo inconveniente si software che hardware. Io ho scelto il più semplice dei modi. Ho aggiunto un ritardo software di 0.5 secondi.

Gestione del potenziometro

Il rilevamento della rotazione del potenziometro ha invece altri problemi.

Un potenziometro può essere modellizzato come 2 resistenze in serie (che variano in base all'angolo di rotazione) quindi un partitore resistivo. Il potenziometro ha infatti 3 piedini. Se ruotato a metà e il suo valore è di 10 K Ohm significa che abbiamo un partitore resistivo con le due resistenza da 5k ciascuno quindi la tensione sul piedino centrale è di 1/2 rispetto a quella sui sue piedini alle estremità. Il problema è causato principalmente dal fatto che la tensione ai capi del partitore resitivo non è perfettamente stabile e quindi, anche tenendo immobile il potenziometro, avremo delle piccolissime variazioni sul piedino centrale.

Il piedino centrale del potenziometro è connesso al convertitore analogico/digitale del Raspberry che essendo a 16bit, un valore pari a +3.3v viene convertito come 2^16 quindi 65535.

Ho ovviato a questo inconveniente con una duplice soluzione.

La prima è fare la media di tre rilevazioni distanziate di 0.05 secondi.

La seconda è ignorare variazioni molto piccole.

Ho inoltre aggiunto un ritardo di 0.1 secondi per evitare l'invio di troppe note in un tempo ristretto.

Per la scrittura di questo semplice codice ho utilizzato Thonny e la libreria "adafruit-circuitpython-midi".

Per visualizzare i comandi midi inviati ho utilizzato MidiView

Bonus - Far suonare il Raspberry Pi Pico

Eseguendo il seguente codice , il Raspberry invierà delle note che compongono una nota melodia.

Per far suonare le note inviate ho utilizzato PianoTime

midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.25)
midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.25)

midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)
midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)

midi.send(NoteOn("A#5", 60)) #la
time.sleep(0.25)
midi.send(NoteOn("A#5", 60)) #la
time.sleep(0.25)

midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.5)

midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)
midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)

midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)
midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)

midi.send(NoteOn("D#5", 60))  #re
time.sleep(0.25)
midi.send(NoteOn("D#5", 60))  #re
time.sleep(0.25)

midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.5)

midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)
midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)

midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)
midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)

midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)
midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)

midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.5)

midi.send(NoteOn("D#5", 60))  #re
time.sleep(0.5)

midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.25)
midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.25)

midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)
midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.25)

midi.send(NoteOn("A#5", 60)) #la
time.sleep(0.25)
midi.send(NoteOn("A#5", 60)) #la
time.sleep(0.25)

midi.send(NoteOn("G#5", 60)) #sol
time.sleep(0.5)

midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)
midi.send(NoteOn("F#5", 60)) #fa
time.sleep(0.25)

midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)
midi.send(NoteOn("E#5", 60)) #mi
time.sleep(0.25)

midi.send(NoteOn("D#5", 60))  #re
time.sleep(0.25)
midi.send(NoteOn("D#5", 60))  #re
time.sleep(0.25)

midi.send(NoteOn("C#5", 60)) #do
time.sleep(0.5)

Video su YouTube

Buon divertimento!

Se hai voglia di lasciarmi un commento mi trovi su Linkedin.