Esempio di semplice Commutatore Midi con Alsa

Da Gambas-it.org - Wikipedia.

Mostreremo di seguito il codice di un semplice Client Midi di Alsa capace di ricevere dati Midi da un altro Client di Alsa (ad esempio da una tastiera Midi esterna) e di inviarli immediatamente a due o più altri Client attraverso una porta di Uscita distinta, in questo esempio, sulla base del numero di Canale dell'evento "NoteON" ricevuto. Al terzo parametro della funzione snd_seq_open(), necessaria per l'apertura del subsistema Seq di Alsa, va inserita la costante SND_SEQ_OPEN_DUPLEX per consentire il flusso di dati sia in Entrata che in Uscita.
I dati Midi ricevuti sono assegnati ad una variabile di tipo Struttura organizzata secondo lo schema tipico di un "Evento" Midi Alsa.

Sarà necessario effettuare le dovute connessioni via Alsa fra i vari Client. In particolare la tastiera esterna andrà collegata alla porta di Entrata del nostro applicativo, e le porte di Entrata degli altri Client di Alsa attivi, che saranno utilizzati, verranno connesse alle porte di Uscita del nostro applicativo Commutatore Midi.

Public Struct snd_seq_event_t
  type As Byte                ' byte n° 0
  flag As Byte
  tag As Byte 
  queue As Byte 
  timestamp1 As Integer 
  timestamp2 As Integer          
  source_id As Byte
  source_porta As Byte
  dest_id As Byte
  dest_porta As Byte
    channel As Byte             ' byte n° 16
    note As Byte
    velocity As Short
      primoValore As Integer      ' byte n° 20
      secondoValore As Integer    ' byte n° 24
End Struct


Private Const POLLIN As Short = 1
Private Const POLLFD As Byte = 8


Library "libasound:2.0.0"

Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Private Const SND_SEQ_PORT_CAP_WRITE As Integer = 2
Private Const SND_SEQ_PORT_CAP_SUBS_WRITE As Integer = 64
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_CAP_SUBS_READ As Integer = 32
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Private Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254
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

 
' const char * snd_strerror(int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_open (snd_seq_t **handle, const char *name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name(snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(handle As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t *seq)
' Get the client id.
Private Extern snd_seq_client_id(handle As Pointer) As Integer

' int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type)
' Create a port - simple version.
Private Extern snd_seq_create_simple_port(handle As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_poll_descriptors_count(snd_seq_t * seq, short events)
' Returns the number of poll descriptors.
Private Extern snd_seq_poll_descriptors_count(handle As Pointer, events As Short) As Integer

' int snd_seq_poll_descriptors(snd_seq_t * seq, struct pollfd * pfds, unsigned int space, short events)
' Get poll descriptors.
Private Extern snd_seq_poll_descriptors(handle As Pointer, pfds As Pointer, spaceI As Integer, events As Short) As Integer

' int snd_seq_event_input(snd_seq_t *handle, snd_seq_event_t **ev)
' Retrieve an event from sequencer.
Private Extern snd_seq_event_input(handle As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) As Integer


Public Sub Main()

 Dim seq As Pointer
 Dim idportaE, err, id As Integer
 Dim j As Byte
 Dim idportaU As New Integer[16]
   

  err = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_DUPLEX, 0)
  If err < 0 Then Error.Raise("Impossibile inizializzare il subsistema 'seq' di ALSA: " & snd_strerror(err))
  Print "Apertura del dispositivo 'seq' di Alsa:  regolare\nNome del dispositivo 'seq':  "; "'default'"
   
  err = snd_seq_set_client_name(seq, "Commutatore MIDI")
  If err < 0 Then Error.Raise("Impossibile assegnare un nome al Client applicativo di ALSA: " & snd_strerror(err))
 
  id = snd_seq_client_id(seq)
  Print "Numero identificativo del Commutatore MIDI:  "; id

  idportaE = snd_seq_create_simple_port(seq, "Porta di Entrata", SND_SEQ_PORT_CAP_WRITE Or SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION)
   If idportaE < 0 Then Error.Raise("Impossibile creare una porta del Commutatore Midi: " & snd_strerror(err))
   Print "Id porta di Entrata n.:  "; idportaE

   For j = 0 To 15   ' Crea 16 porte di Uscita dell'applicativo.
     idportaU[j] = snd_seq_create_simple_port(seq, "Porta di Uscita n. " & CStr(j), SND_SEQ_PORT_CAP_READ Or SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION)
     If idportaU[j] < 0 Then Error.Raise("Impossibile creare una porta del Commutatore Midi: " & snd_strerror(err))
     Print "Id porta di Uscita n.:  "; idportaU[j]
   Next

  intercettaMessaggiMidi(seq, id)

End


Public Procedure intercettaMessaggiMidi(seqiM As Pointer, idC As Integer)
  
 Dim ev As Pointer
 Dim midi@ As Snd_seq_event_t
    
  While True

    snd_seq_event_input(seqiM, VarPtr(ev))
    midi@ = ev


' Effettuiamo la commutazione delle porte di Uscita sulla base del canale dell'evento "NoteON".
' Ovviamente l'elemento discriminante potrebbe essere anche un altro.
' In questo esempio il Commutatore MIDI è connesso con due sue porte di Uscita ad due diversi Client Alsa attivi.
    With midi@

      If .note = 60 Then  ' Se i messaggi "NoteOn" giungono
        .channel = 0      ' già con canali diversi,
      Else                ' allora questa parte di codice
        .channel = 1      ' può essere eliminata.
      Endif               '
       
      .source_id = idC

      If .channel = 0 Then
        .source_porta = 1   ' Uscirà dalla porta di Uscita n. 1
      Else
        .source_porta = 2   ' Uscirà dalla porta di Uscita n. 2
      Endif
      .dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
      .dest_porta = SND_SEQ_ADDRESS_UNKNOWN
    End With

    snd_seq_event_output_direct(seqiM, midi@)
 
  Wend
  
End