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); /* Invia il messaggio di Control Change */ 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