Differenze tra le versioni di "Alsa e Gambas: Client e Porte in Invio dati - Connessione della porta del programma con il Client Softsynth"

Da Gambas-it.org - Wikipedia.
(Creata pagina con "Per essere in grado di inviare dati Midi via ALSA dal nostro applicativo ad un altro programma, è necessario che <SPAN Style="text-decoration:underline">entrambi i programmi...")
(Nessuna differenza)

Versione delle 17:24, 11 gen 2022

Per essere in grado di inviare dati Midi via ALSA dal nostro applicativo ad un altro programma, è necessario che entrambi i programmi si rapportino ad ALSA come suoi Client. Il sistema ALSA, pertanto, diventa Server dei due suoi Client.

Riferendoci al capitolo che stiamo trattando, quello relativo dell'invio dati Midi ad ALSA, la fattispecie più evidente e probabile è quella dell'invio di dati Midi a un Softsynth, affinché siano emessi i suoni e gli effetti connessi agli eventi Midi prodotti. In tal caso, dunque, anche il programma Softsynth prescelto, per poter ricevere i dati Midi, dovrà diventare un Client di ALSA, e il nostro applicativo, capace di inviare eventi Midi, dovrà connettersi a detto Softsynth.
In vero, la connessione avviene tra le porte dei due Client di ALSA, ed avviene sempre e comunque attraverso il sistema e le risorse di ALSA.

La connessione delle porte del nostro applicativo con quella del Client Softsynth [nota 1] può essere effettuata mediante la funzione:

int snd_seq_connect_to(snd_seq_t *seq, int myport, int src_client, int src_port)

Nel nostro codice Gambas la dichiareremo con la consueta funzione Extern:

Private EXTERN  snd_seq_connect_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

Questa funzione sarà utilizzata in routine come segue:

 err = snd_seq_connect_to(handle, outport, dclient, dport)

A questo punto, lanciando da terminale il comando: cat /proc/asound/seq/clients, possiamo verificare:

  • il numero identificativo e il nome del nostro Client;
  • il numero ed il nome della porta del nostro Client;
  • il numero identificativo del device (ad esempio QSynth) e della sua porta (con relativa indicazione delle capacità), al quale il Client si è connesso.

Cercando il nostro Client avremo infatti informazioni simili alle seguenti:

Client 129 : "Nome del nostro applicativo Client" [User]

Port   0 : "Nome della Porta" (--e-)

Connecting To: 128:0

che ovviamente vanno così interpretate: il Client avente numero identificativo 129 e chiamato "Nome del nostro applicativo Client", è connesso tramite la sua Porta, avente numero identificativo 0 e chiamata "Nome della porta", alla porta avente numero identificativo 0 di un altro Client di ALSA avente numero identificativo 128.

Esempio pratico

Vediamo un esempio di codice essenziale pratico, con il quale, dopo che sia stato preliminarmente lanciato il Softsynth (che così assumerà l'identificativo Client ALSA n. 128 e numero porta 0), il nostro applicativo si connetterà al Softsynth e gli invierà alcuni dati di eventi Midi ALSA gestiti con apposita Struttura:

Private sndseq As Pointer
Private Const cl_dest As Byte = 128 ' Identificativo del Client al quale connettersi
Private Const po_dest As Byte = 0 ' Numero della Porta del Client al quale connettersi


Library "libasound:2"

Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_time As Integer
  real_time 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
  duration 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 String

' 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_connect_to (snd_seq_t *seq, int my_port, int dest_client, int dest_port)
' Simple subscription (w/o exclusive & time conversion).
Private Extern snd_seq_connect_to(seq As Pointer, my_port As Integer, dest_client As Integer, dest_port As Integer) 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 note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]   ' [nota 2]
 Dim client As new Byte[2]
 Dim b As Byte
 Dim ev As New Snd_seq_event_t
 
' Crea il Client di ALSA:
 CreaClient(client)
  
' Imposta l'Evento Midi di ALSA con alcuni valori:
 With ev
   .queue = 0
   .source_client = client[0]
   .source_port =client[1]
   .dest_client = cl_dest  ' Si può assegnare anche il valore della Costante "SND_SEQ_ADDRESS_SUBSCRIBERS", che è 254
   .dest_port = po_dest    ' Si può assegnare anche il valore della Costante "SND_SEQ_ADDRESS_UNKNOWN", che è 253
   .channel = 0
 End With
 
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Program Change", specificando nel secondo argomento il numero dello strumento musicale da usare:
 program_change(ev, 48)
 
 For b = 0 To note.Max
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Note ON":
   Note_On(ev, note[b])
   Wait 1
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Note OFF":
   Note_Off(ev, note[b])
 Next
   
 snd_seq_close(sndseq)
  
End


Private Function CreaClient(source As Byte[])
 
 Dim rit As Integer
 
 rit = snd_seq_open(VarPtr(sndseq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & snd_strerror(rit))
 
 source[0] = snd_seq_client_id(sndseq)
 
 source[1] = snd_seq_create_simple_port(sndseq, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If source[1] < 0 Then Error.Raise("Impossibile creare la porta di uscita dati ALSA !")
  
' Connette il Client Gambas all'altro Client (ad esempio QSynth):
 rit = snd_seq_connect_to(sndseq, source[1], cl_dest, po_dest)
 If rit < 0 Then Error.Raise("Impossibile connettersi al Client di ALSA destinatario dei dati: " & snd_strerror(rit))
   
 snd_seq_alloc_queue(sndseq)
 
End


Private Procedure program_change(ev_prch As Snd_seq_event_t, strum As Integer)
 
  With ev_prch
' Specifica che si tratta di un Messaggio Midi "Program Change":
    .type = SND_SEQ_EVENT_PGMCHANGE
' Assegna il valore relativo allo strumento musicale secondo l'elenco GM":
    .value = strum
  End With
   
  InviaEvento(ev_prch)
  
End


Private Procedure Note_On(ev_nton As Snd_seq_event_t, nota As Byte)
 
  With ev_nton
' Specifica che si tratta di un Messaggio Midi "Note ON":
    .type = SND_SEQ_EVENT_NOTEON
' Specifica il numero della nota Midi da eseguire:
    .note = nota
' Specifica il valore di "velocity":
    .velocity = 64
  End With
  
  InviaEvento(ev_nton)
  
End


Private Procedure Note_Off(ev_ntoff As Snd_seq_event_t, nota As Byte)
 
  With ev_ntoff
' Specifica che si tratta di un Messaggio Midi "Note OFF":
    .type = SND_SEQ_EVENT_NOTEOFF
    .note = nota
    .velocity = 0
  End With
  
  InviaEvento(ev_ntoff)
  
End


Private Procedure InviaEvento(evento As Snd_seq_event_t)
 
  snd_seq_event_output(sndseq, evento)
  
  snd_seq_drain_output(sndseq)
 
End


In quest'altro esempio, molto simile al precedente, il programma cercherà la presenza del softsynth tra i Client di ALSA, individuandone anche il numero della porta:

Private sndseq As Pointer


Library "libasound:2"

Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_time As Integer
  real_time 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
  duration 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    ' La porta del Client può essere "letta" da un altro Client esterno
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 String

' 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_connect_to (snd_seq_t *seq, int my_port, int dest_client, int dest_port)
' Simple subscription (w/o exclusive & time conversion).
Private Extern snd_seq_connect_to(seq As Pointer, my_port As Integer, dest_client As Integer, dest_port As Integer) 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 note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]
 Dim source_dest As New Byte[4]
 Dim b As Byte
 Dim ev As New Snd_seq_event_t
 
' Crea il Client di ALSA:
 CreaClient(source_dest)
 
' Imposta l'Evento Midi di ALSA con alcuni valori:
 With ev
   .flags = 0
   .tag = 0
   .queue = 0
   .tick_time = 0
   .real_time = 0
   .source_client = source_dest[0]
   .source_port = source_dest[1]
   .dest_client = source_dest[2]   ' si può assegnare anche il valore della Costante "SND_SEQ_ADDRESS_SUBSCRIBERS", che è 254
   .dest_port = source_dest[3]   ' si può assegnare anche il valore della Costante "SND_SEQ_ADDRESS_UNKNOWN", che è 253
   .channel = 0
 End With

' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Program Change", specificando nel secondo argomento il numero identificativo GM dello strumento musicale da usare:
 program_change(ev, 48)

 For b = 0 To note.Max
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Note ON":
   Note_On(ev, note[b])
   Wait 1
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio Midi "Note OFF":
   Note_Off(ev, note[b])
 Next
  
 snd_seq_close(sndseq)
  
End

Private Function CreaClient(srcdst As Byte[])
 
 Dim rit As Integer
 
 rit = snd_seq_open(VarPtr(sndseq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & snd_strerror(rit))

 srcdst[0] = snd_seq_client_id(sndseq)

 srcdst[1] = snd_seq_create_simple_port(sndseq, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If srcdst[1] < 0 Then Error.Raise("Impossibile creare la porta di uscita dati ALSA !")
 
 Fluidsynth(srcdst)
 
' Connette il Client Gambas all'altro Client (ad esempio QSynth):
 rit = snd_seq_connect_to(sndseq, srcdst[1], srcdst[2], srcdst[3])
 If rit < 0 Then Error.Raise("Impossibile connettersi al Client di ALSA destinatario dei dati: " & snd_strerror(rit))
  
 snd_seq_alloc_queue(sndseq)
 
End

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

 With ev_prch
' Specifica che si tratta di un Messaggio Midi "Program Change":
   .type = SND_SEQ_EVENT_PGMCHANGE
' Assegna il valore relativo allo strumento musicale secondo l'elenco GM":
   .value = strum
 End With
  
 InviaEvento(ev_prch)
 
End

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

 With ev_nton
' Specifica che si tratta di un Messaggio Midi "Note ON":
   .type = SND_SEQ_EVENT_NOTEON
' Specifica il numero della nota Midi da eseguire:
   .note = nota
' Specifica il valore di "velocity":
   .velocity = 64
 End With
 
 InviaEvento(ev_nton)
 
End

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

 With ev_ntoff
' Specifica che si tratta di un Messaggio Midi "Note OFF":
   .type = SND_SEQ_EVENT_NOTEOFF
   .note = nota
   .velocity = 0
 End With
 
 InviaEvento(ev_ntoff)
 
End

Private Procedure InviaEvento(evento As Snd_seq_event_t)

 snd_seq_event_output(sndseq, evento)
 
 snd_seq_drain_output(sndseq)

End

Private Function Fluidsynth(sd As Byte[])
 
 Dim s As String
 Dim ss As String[]
 Dim c As Short
 
 s = File.Load("/proc/asound/seq/clients")
 If InStr(s, "FLUID Synth") == 0 Then Error.Raise("FluidSinth non trovato !")
 ss = Split(s, "\n")
 For c = 0 To ss.Max
   If InStr(ss[c], "FLUID Synth") > 0 Then
     sd[2] = Scan(ss[c], "Client * : \"FLUID Synth*")[0]
     sd[3] = Scan(ss[c + 1], "  Port * : \"Synth*")[0]
   Endif
 Next
 
End


Note

[1] Qualora la connessione sia invece impostata esplicitamente con il subsistema ALSA:

client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'

(solitamente 14:0), bisognerà ricordare, dopo aver lanciato il nostro applicativo, di connettere - ad esempio con il comando aconnect da terminale - il sistema ALSA con il softsynth. In sostanza la catena di connessione risulterà in questo caso essere la seguente: Applicativo-->ALSA-->Softsynth.

[2] Corrispondenza tra "Note MIDI", frequenza sonora e note sul pentragramma