Alsa e Gambas: Invio dei dati senza connessione della porta del programma con il Softsynth

Da Gambas-it.org - Wikipedia.

Per inviare utilmente dati Midi ad altro Client di ALSA, non è necessario connettere i due Client (l'applicativo Gambas ed il Softsynth) mediante la funzione esterna di ALSA "snd_seq_connect_to()", purché si presti attenzione ad alcuni elementi che integrano due casi.


1° caso

Nel primo caso il codice dovrà prevedere:

  • la presenza della funzione di ALSA "snd_seq_alloc_queue()", la quale restituisce l'identificativo della coda (queue )degli Eventi Midi ALSA da assegnarsi al membro "queue" della Struttura snd_seq_event_t;
  • al membro dest_client e a quello dest_port della Struttura relativa agli eventi Midi dovranno essere rispettivamente assegnati i valori dell'Id e della porta del Client (Softsynth), al quale l'applicativo Gambas dovrà inviare i dati Midi.

Il dispositivo destinatario degli Eventi Midi ALSA sarà individuato, dunque, esclusivamente attraverso il suo numero identificativo e il numero della sua porta quale Client di ALSA.

Mostriamo un esempio pratico:

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_o_tv_sec As Integer
  tv_nsec As Integer
  source_client As Byte
  source_port As Byte
  dest_client As Byte
  dest_port As Byte
  channel As Byte
  note As Byte
  velocity As Byte
  off_velocity As Byte
  param As Integer
  value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
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_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE
 
' 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

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

' 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_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(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
 
' int snd_seq_alloc_queue (snd_seq_t *handle)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(handle As Pointer) As Integer
 
' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output(handle As Pointer, ev As Snd_seq_event_t)

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' int snd_seq_close (snd_seq_t *handle)
' Close the sequencer.
Private Extern snd_seq_close(handle As Pointer) As Integer


Public Sub Main()

 Dim handle As Pointer
 Dim rit As Integer
 Dim note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]
 Dim id, porta, que, b As Byte
 Dim ev As New Snd_seq_event_t

 rit = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 id = snd_seq_client_id(handle)
 If id < 0 Then Error.Raise("Errore: " & String@(snd_strerror(rit)))

 porta = snd_seq_create_simple_port(handle, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If porta < 0 Then Error.Raise("Impossibile creare la porta di uscita dati ALSA !")

 que = snd_seq_alloc_queue(handle)

 With ev
   .queue = que
   .source_client = id
   .source_port = porta
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   .channel = 0
 End With
     
 program_change(handle, ev, 48)
  
 For b = 0 To note.Max
   Note_On(handle, ev, note[b])
' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
   Wait 1
   Note_Off(handle, ev, note[b])
 Next
 
 snd_seq_close(handle)

End

Private Procedure program_change(p As Pointer, ev_prch As Snd_seq_event_t, strum As Integer)

 With ev_prch
   .type = SND_SEQ_EVENT_PGMCHANGE
   .value = strum
 End With

 InviaEvento(p, ev_prch)
 
End

Private Procedure Note_On(p As Pointer, ev_nton As Snd_seq_event_t, nota As Byte)

 With ev_nton
   .type = SND_SEQ_EVENT_NOTEON
   .note = nota
   .velocity = 64
 End With

 InviaEvento(p, ev_nton)
 
End

Private Procedure Note_Off(p As Pointer, ev_ntoff As Snd_seq_event_t, nota As Byte)

 With ev_ntoff
   .type = SND_SEQ_EVENT_NOTEOFF
   .note = nota
   .velocity = 0
 End With

 InviaEvento(p, ev_ntoff)

End

Private Procedure InviaEvento(p As Pointer, evento As Snd_seq_event_t)

 snd_seq_event_output(p, evento)
 
 snd_seq_drain_output(p)

End


2° caso

Nel secondo caso si dovrà tenere conto di quanto segue:

  • non viene utilizzata la funzione esterna (usata invece nel caso precedente) specifica di ALSA per la creazione della porta del Client applicativo;
  • al membro queue della Struttura relativa agli eventi Midi dovrà essere assegnato il valore della Costante Alsa "SND_SEQ_QUEUE_DIRECT" (253);
  • al membro dest_id e a quello dest_port della Struttura relativa agli eventi Midi dovranno essere rispettivamente assegnati i valori dell'Id e della porta del Client (Softsynth), al quale l'applicativo Gambas dovrà inviare i dati Midi.

Mostriamo un esempio pratico:

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_o_tv_sec As Integer
  tv_nsec As Integer
  source_client As Byte
  source_port As Byte
  dest_client As Byte
  dest_port As Byte
  channel As Byte
  note As Byte
  velocity As Byte
  off_velocity As Byte
  param As Integer
  value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
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_QUEUE_DIRECT As Byte = 253
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE
 
' 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

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

' 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_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(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
 
' int snd_seq_alloc_queue (snd_seq_t *handle)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(handle As Pointer) As Integer
 
' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output(handle As Pointer, ev As Snd_seq_event_t)

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' int snd_seq_close (snd_seq_t *handle)
' Close the sequencer.
Private Extern snd_seq_close(handle As Pointer) As Integer


Public Sub Main()

 Dim handle As Pointer
 Dim rit As Integer
 Dim note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]
 Dim id, porta, b As Byte
 Dim ev As New Snd_seq_event_t

 rit = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 id = snd_seq_client_id(handle)
 If id < 0 Then Error.Raise("Errore: " & String@(snd_strerror(rit)))

 porta = snd_seq_create_simple_port(handle, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If porta < 0 Then Error.Raise("Impossibile creare la porta di uscita dati ALSA !")

 With ev
   .queue = SND_SEQ_QUEUE_DIRECT
   .source_client = id
   .source_port = porta
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   .channel = 0
 End With
     
 program_change(handle, ev, 48)
  
 For b = 0 To note.Max
   Note_On(handle, ev, note[b])
' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
   Wait 1
   Note_Off(handle, ev, note[b])
 Next
 
 snd_seq_close(handle)

End

Private Procedure program_change(p As Pointer, ev_prch As Snd_seq_event_t, strum As Integer)

 With ev_prch
   .type = SND_SEQ_EVENT_PGMCHANGE
   .value = strum
 End With

 InviaEvento(p, ev_prch)
 
End

Private Procedure Note_On(p As Pointer, ev_nton As Snd_seq_event_t, nota As Byte)

 With ev_nton
   .type = SND_SEQ_EVENT_NOTEON
   .note = nota
   .velocity = 64
 End With

 InviaEvento(p, ev_nton)
 
End

Private Procedure Note_Off(p As Pointer, ev_ntoff As Snd_seq_event_t, nota As Byte)

 With ev_ntoff
   .type = SND_SEQ_EVENT_NOTEOFF
   .note = nota
   .velocity = 0
 End With

 InviaEvento(p, ev_ntoff)

End

Private Procedure InviaEvento(p As Pointer, evento As Snd_seq_event_t)

 snd_seq_event_output(p, evento)
 
 snd_seq_drain_output(p)

End


Esempio più essenziale per invio diretto degli Eventi Midi ALSA

Mostriamo ora la modalità più breve ed essenziale per inviare ad altro Client di Alsa alcuni eventi Midi, e nella quale non è prevista la creazione di porte del nostro Client applicativo.
Si utilizzerà in questo caso la funzione esterna di ALSA: "snd_seq_event_output_direct()" per invio diretto di ciaascun Evento Midi ALSA.

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0

Library "libasound:2.0.0"

Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_o_tv_sec As Integer
  tv_nsec As Integer
  source_client As Byte
  source_port As Byte
  dest_client As Byte
  dest_port As Byte
  channel As Byte
  note As Byte
  velocity As Byte
  off_velocity As Byte
  param As Integer
  value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF

' 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

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

' 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

' int snd_seq_close (snd_seq_t *handle)
' Close the sequencer.
Private Extern snd_seq_close(handle As Pointer) As Integer


Public Sub Main()

 Dim seq As Pointer
 Dim evento As New Snd_seq_event_t
 Dim rit As Integer

 snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 With evento
   .type = SND_SEQ_EVENT_NOTEON
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   .channel = 0
   .note = 64
   .velocity = 100
 End With

 snd_seq_event_output_direct(seq, evento)

' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
 Wait 1

 evento.type = SND_SEQ_EVENT_NOTEOFF
 evento.velocity = 0
 snd_seq_event_output_direct(seq, evento)

 snd_seq_close(seq)

End