Inviare dati Midi da Arduino a Gambas
Nell'esempio preso in considerazione in questa pagina i dati Midi da inviare sono prestabiliti dal codice caricato in Arduino, e pertanto saranno inviati in modo automatizzato. In particolare il ciclo infinito di Arduino procederà come segue:
- invio di un messaggio Midi Program Change per impostare lo strumento musicale da utilizzare;
- invio di un messaggio Note On (status 144, nota 64, velocità 100);
- attesa per 700 millisecondi;
- invio di un messaggio Note Off (status 128, nota 64, velocità 0);
- attesa per 200 millisecondi;
- incremento del 2° valore del Program Change relativo allo strumento musicale da utilizzare.
Dunque il codice per Arduino sarà il seguente:
byte noteON[] = {144, 64, 100};
byte noteOFF[] = {128, 64, 0};
byte controlchange[] = {176, 0, 0};
int count = 0;
int str = 0;
void setup() {
Serial.begin(57600);
for (count=0;count<3;count++) {
Serial.write(controlchange[count]);
}
}
void loop() {
/* Invia il messaggio di Program Change */
Serial.write(192);
Serial.write(str);
/* Invia il messaggio di Note On */
for (count=0;count<3;count++) {
Serial.write(noteON[count]);
}
delay(700);
/* Invia il messaggio di Note Off */
for (count=0;count<3;count++) {
Serial.write(noteOFF[count]);
}
delay(200);
/* Incrementa la variabile "str" per cambiare strumento musicale del soundfont bank utilizzato */
++str;
if (str==128)
str = 0;
}
Il programma Gambas raccoglierà i valori inviati da Arduino (è necessario attivare il Componente gb.net) e li invierà ad Alsa - attraverso un'apposita Classe secondaria che chiameremo CAlsa - per l'esecuzione sonora.
Pertanto il codice della Classe principale sarà il seguente:
Private SerialPort1 As SerialPort
Private bb As New Byte[]
Private Const outdevice As Integer = 14 ' client 14: «Midi Through»
Private Const outport As Integer = 0 ' porta d'uscita
Public alsa As CAlsa
Public Sub Form_Open()
Me.Center
' Crea la classe "Clasa" per poterla usare e gestire le funzioni di Alsa:
With alsa = New CAlsa As "alsa"
' Apre Alsa e gli assegna un nome:
.alsa_open("Gambas-Midi-Arduino")
' Sceglie la periferica su cui suonare:
.setdevice(outdevice, outport)
End With
End
Public Sub Button1_Click()
With SerialPort1 = New SerialPort As "portaseriale"
.PortName = "/dev/ttyUSB0"
.Speed = 57600
.Parity = 0
.DataBits = 8
.StopBits = 1
.FlowControl = 0
.Open
End With
End
Public Sub portaseriale_Read()
Dim b As Byte
' Legge i dati dalla porta...
Read #SerialPort1, b
bb.Push(b)
Select Case bb[0]
Case 128 To 143
If bb.Count = 3 Then
Print "NoteOff: ", bb[0], bb[1], bb[2]
Print "----------------------------"
bb[0] = bb[0] And 15
alsa.noteoff(bb[0], bb[1], bb[2])
alsa.flush()
bb.Clear
Endif
Case 144 To 159
If bb.Count = 3 Then
Print "NoteOn: ", bb[0], bb[1], bb[2]
bb[0] = bb[0] And 15
alsa.noteon(bb[0], bb[1], bb[2])
alsa.flush()
bb.Clear
Endif
Case 176 To 191
If bb.Count = 3 Then
Print "Control Change: ", bb[0], bb[1], bb[2]
bb[0] = bb[0] And 15
alsa.controller(bb[0], bb[1], bb[2])
alsa.flush()
bb.Clear
Endif
Case 192 To 207
If bb.Count = 2 Then
Print "Program Change: ", bb[0], bb[1]
bb[0] = bb[0] And 15
alsa.programchange(bb[0], bb[1])
alsa.flush()
bb.Clear
Endif
End Select
End
Public Sub Form_Close()
If SerialPort1.Status = Net.Active Then SerialPort1.Close
End
Per la gestione dei dati Midi con il sub-sistema seq di Alsa mediante alcune sue funzioni esterne creeremo un'apposita Classe secondaria, chiamata CAlsa, il cui codice sarà il seguente:
Private handle As Pointer
Private id As Integer
Private outport As Integer
Private outq As Integer
Private dclient As Byte
Private dport As Byte
Public ev As Pointer
Private p As Stream
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Private Const SND_SEQ_EVENT_NOTEON As Byte = 6
Private Const SND_SEQ_EVENT_NOTEOFF As Byte = 7
Private Const SND_SEQ_EVENT_CONTROLLER As Byte = 10
Private Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Const SIZE_OF_SEQEV As Integer = 32
Library "libasound:2"
' int snd_seq_open (snd_seq_t **seqp, Private Const char * name, Int streams, Int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer
' int snd_seq_set_client_name(snd_seq_t* seq, Private Const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer
' int snd_seq_create_simple_port (snd_seq_t* seq, Private Const char* name, unsigned int caps, unsigned int type)
' Create a port - simple version.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer
' int snd_seq_alloc_named_queue (snd_seq_t * seq, const char * name)
' Allocate a queue with the specified name.
Private Extern snd_seq_alloc_named_queue(seq As Pointer, name As String) As Integer
' int snd_seq_connect_to (snd_seq_t *seq, int my_port, int dest_client, int dest_port)
' Simple subscription.
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer
' int snd_seq_event_output_buffer (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event onto the lib buffer without draining buffer.
Private Extern snd_seq_event_output_buffer(handle As Pointer, ev As Pointer) As Integer
' int snd_seq_drain_output (snd_seq_t *handle)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(handle As Pointer) As Integer
' const char* snd_strerror (int errnum)
' Returns the message for an error code. .
Private Extern snd_strerror(errnum As Integer) As Pointer
Public Sub alsa_open(nome As String)
Dim err As Integer
err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 0)
printerr("Apertura Alsa", err)
If err < 0 Then error.RAISE("Error opening alsa")
snd_seq_set_client_name(handle, nome)
id = snd_seq_client_id(handle)
Print "Alsa ClientID="; id
err = snd_seq_create_simple_port(handle, "Seq-Out", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)
Print "Porta d'Uscita = "; err
If err < 0 Then error.Raise("Error creating output port")
outport = err
err = snd_seq_alloc_named_queue(handle, "outqueue")
printerr("Creazione della coda dei dati ", err)
If err < 0 Then error.Raise("Error creating out queue")
outq = err
' Alloca un evento per gestirlo:
ev = Alloc(SIZE_OF_SEQEV)
p = Memory ev For Write
End
Public Sub setdevice(client As Integer, port As Integer)
Dim err As Integer
dclient = client
dport = port
err = snd_seq_connect_to(handle, outport, client, dport)
printerr("Subscribe outport", err)
If err < 0 Then error.Raise("Errore nella sottoscrizione del dispositivo di Uscita !")
End
Private Sub prepareev(type As Byte) As Pointer
Dim i As Integer
Dim ts, flags, tag As Byte
' Pulisce innanzitutto l'area di memoria dei dati dell'evento Midi:
For i = 0 To SIZE_OF_SEQEV - 1
Seek #p, i
Write #p, 0 As Byte
Next
Seek #p, 0
Write #p, type As Byte
ts = 0
flags = 0
Write #p, flags As Byte
tag = 0
Write #p, tag As Byte
Write #p, outq As Byte
Write #p, ts As Integer
ts = 0
Write #p, ts As Integer
Write #p, id As Byte
Write #p, outport As Byte
Write #p, dclient As Byte
Write #p, dport As Byte
End
°°°°°°°°°°° GESTIONE DEI SINGOLI MESSAGGI MIDI °°°°°°°°°°°
Public Sub noteon(channel As Byte, note As Byte, velocity As Byte)
Dim err As Integer
prepareev(SND_SEQ_EVENT_NOTEON)
Write #p, channel As Byte
Write #p, note As Byte
Write #p, velocity As Byte
err = snd_seq_event_output_buffer(handle, ev)
printerr("Noteon = ", err)
End
Public Sub noteoff(channel As Byte, note As Byte, velocity As Byte)
Dim err As Integer
prepareev(SND_SEQ_EVENT_NOTEOFF)
Write #p, channel As Byte
Write #p, note As Byte
Write #p, velocity As Byte
err = snd_seq_event_output_buffer(handle, ev)
printerr("Note OFF = ", err)
End
Public Sub controller(channel As Byte, valore1 As Integer, valore2 As Integer)
Dim err As Integer
prepareev(SND_SEQ_EVENT_CONTROLLER)
Write #p, channel As Byte
Seek #p, 20
Write #p, valore1 As Integer
Write #p, valore2 As Integer
err = snd_seq_event_output_buffer(handle, ev)
printerr("Controller = ", err)
End
Public Sub programchange(channel As Byte, valore1 As Byte)
Dim err As Integer
prepareev(SND_SEQ_EVENT_PGMCHANGE)
Write #p, channel As Byte
Seek #p, 24
Write #p, valore1 As Byte
err = snd_seq_event_output_buffer(handle, ev)
printerr("Program Change = ", err)
End
Public Sub flush()
Dim err As Integer
err = snd_seq_drain_output(handle)
Printerr("Flush", err)
End
°°°°°°°°°°° GESTIONE DEGLI ERRORI °°°°°°°°°°°
Public Sub errmsg(err As Integer) As String
Return String@(snd_strerror(err))
End
Private Sub printerr(operation As String, err As Integer)
If err < 0 Then Print operation; ": err = "; err; " ("; errmsg(err); ")"
End