Inviare dati Midi da Arduino a Gambas

Da Gambas-it.org - Wikipedia.

Questa pagina prende in considerazione due casi:

  • il caso in cui i dati Midi da inviare sono prestabiliti dal codice caricato in Arduino, e pertanto saranno inviati in modo automatizzato, ossia con i tempi stabiliti dal predetto codice;
  • il caso il cui i dati Midi sono inviati quando l'utente chiude un circuito con un interruttore.


Dati inviati da Arduino in modalità automatizzata prestabilita da codice

In questo caso 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


Dati inviati da Arduino da parte dell'utente chiudendo il circuito con un interruttore

Paragrafo in costruzione !