Alsa e Gambas: Client e porte in Invio dati

Da Gambas-it.org - Wikipedia.

Funzione per consentire la relazione del programma Midi con ALSA - Creazione del Client

Per creare un client di ALSA [Note 1], è necessario utilizzare la funzione all'uopo dedicata di ALSA: [Note 2]

int snd_seq_open (snd_seq_t **seqp, const char *name, int streams, int mode)

Questa, come abbiamo già detto, fornisce un handle (la variabile di tipo Puntatore di Puntatore seqp) che consente al programma di relazionarsi con ALSA. Nello specifico apre il sequencer di ALSA, creando un nuovo handle. Essa apre cioè una connessione all'interfaccia del kernel del sequencer ALSA. Crea dunque un client, e pertanto possiamo dire che rende client il nostro progetto.

L'impostazione dei parametri di detta funzione è la seguente:

  • seqp : un puntatore [Note 3] al puntatore snd_seq_t (per questo **seqp è appunto un puntatore ad un puntatore e presenta due asterischi);
  • name : il nome del nostro sequencer. E' un nome stringa che possiede uno speciale significato in ALSA, e solitamente è necessario usare il termine "default". Solo successivamente si potrà porre un nome stringa di nostra scelta.
  • streams : la modalità in lettura e/o scrittura del sub-sistema "Sequencer" di ALSA.

Questa modalità può essere:

- SND_SEQ_OPEN_OUTPUT: apre il "Sequencer" solo per l'output (Uscita). Tale modalità è identificata con un valore costante di tipo integer = 1;

- SND_SEQ_OPEN_INPUT: apre il "Sequencer" solo per l'input (Entrata). Tale modalità è identificata con un valore costante di tipo integer = 2;

- SND_SEQ_OPEN_DUPLEX: apre il "Sequencer" sia per l'output che per l'input. Tale modalità è identificata con un valore costante di tipo integer = 3.

  • mode : può assumere il valore di 0, oppure SND_SEQ_NONBLOCK. Esso rappresenta l'apertura del sequencer ALSA in modalità non-blocking, cioè non-bloccante: i dati non bloccheranno in entrata l'esecuzione del programma.


Nella scrittura del nostro codice dobbiamo, come abbiamo imparato, richiamare tale funzione con Extern:

Private EXTERN snd_seq_open(seqp AS Pointer, name as String, streams as Integer, mode as Integer) as Integer

La chiamata ci restituirà un integer, poiché - come abbiamo già detto - ogni funzione esterna di ALSA ritorna un codice di errore (anche quando non si deve necessariamente creare un oggetto/handle). Questa funzione sarà utilizzata nella routine di Gambas così:

err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)

la porta (con riferimento al capitolo che stiamo trattando) ha caratteristica OUTPUT.

Per gestire in Gambas il primo parametro (**seqp) della funzione esterna "snd_seq_open " utilizziamo la funzione:

VarPtr(Variable) AS Pointer

la quale passa l'indirizzo del puntatore alla variabile "handle". Detta funzione di Gambas, cioè, ritorna un puntatore alla variabile "handle".


Attribuire al "Client" un nome

Potremo poi, volendo, attribuire al "Client" un nome da noi scelto (che avremo scelto già nella chiamata della funzione di apertura di ALSA effettuata nella classe principale). Per fare questo è necessario richiamare una funzione esterna di ALSA - che in C è:

int snd_seq_set_client_name(snd_seq_t *seq, const char *name)

con il solito Extern:

Private EXTERN snd_seq_client_name(seq AS Pointer, name AS String) AS Integer

Questa funzione sarà utilizzata in Gambas come segue:

 err = snd_seq_client_name(handle, myname)


Ottenere il numero identificativo del Client

Possiamo inoltre ottenere il valore numerico identificativo del nostro Client. Questo identificativo numerico risulta indispensabile per conoscere od impostare le informazioni sul client. Solitamente ad un client-utente viene attribuito un identificativo tra 128 e 191. Richiamiamo ancora una funzione esterna di ALSA - che in C è:

int snd_seq_client_id(snd_seq_t *seq)

ovviamente dichiarata in Gambas così:

Private EXTERN snd_seq_client_id(seq As Pointer) As Integer

Questa funzione sarà utilizzata in Gambas come segue:

 id = snd_seq_client_id(handle)


Creazione della Porta e sua capacità

Dopo aver dato il nome al Client, creiamo effettivamente la sua porta conferendogli la capacità. [Note 4]
Per creare una porta di normale uso si richiamerà la funzione esterna di ALSA - che in C è:

int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type)

in Gambas tale dichiarazione sarà:

Private EXTERN  snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer.</p>

Questa funzione sarà utilizzata in Gambas come segue:

 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)

Creiamo la Porta del Client con capacità "READ" (ossia di essere letta da altri Client di ALSA), nonché con capacità generica di interpretare i dati Midi, ed in più la dichiariamo appartenente alla nostra applicazione, cioè al nostro sequencer-Client.

Laddove le costanti:

  • SND_SEQ_PORT_CAP_READ = 1; La Porta del sequencer-Client può essere letta da un altro Client di ALSA (il sequencer-Client può inviare dati alla "sua" Porta)
  • SND_SEQ_PORT_TYPE_MIDI_GENERIC = 2;
  • SND_SEQ_PORT_TYPE_APPLICATION = 1048576 (1<<20).


La gestione dell'errore in ALSA

Alle funzioni che ritornano un valore di errore, aggiungeremo la gestione dell'errore.

L'intera funzione per gestire gli errori potrà essere ad esempio così concepita:

1. Come sappiamo, Alsa restituisce codici di errore numerici.

2. Otteniamo quindi il codice di errore, e lo passiamo ad Alsa usando la funzione esterna:

const char * snd_strerror (int errnum)

che per essere utilizzata ovviamente sarà debitamente richiamata con il solito Extern:

Private Extern snd_strerror(err As Integer) As Pointer

Si passerà alla funzione snd_strerror() il valore Intero ritornato, la quale in base al valore passato ritornerà un messaggio di spiegazione come una stringa che può essere agevolmente letta da Gambas.


Esempio pratico

Mostriamo di seguito, a compendio di quanto sopra descritto, un codice di applicazione a riga di comando, nel quale verrà inizializzata la libreria ALSA ed il suo subsistema Seq e soltanto creata la porta in lettura della nostra applicazione. Con il seguente codice dunque la nostra applicazione diventerà semplicemente così un Client di ALSA.

Library "libasound:2.0.0"

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
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_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576

' 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(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t *handle)
' 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(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' 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_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, id, porta As Integer
 
' Crea un Client di ALSA:
 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: " & snd_strerror(rit))
   
' Attribuisce un nome al Client:
 snd_seq_set_client_name(handle, "Client di ALSA")
   
' Ottiene il numero identificativo del Client:
 id = snd_seq_client_id(handle)
 Print "Identificativo del Client: "; id
 
' Crea una porta del Client; gli assegna la capacità di essere letta (da altri Client) ed effettua la sottoscrizione.
' Inoltre ottiene il numero identificativo della porta del Client.
 porta = snd_seq_create_simple_port(handle, "porta", SND_SEQ_PORT_CAP_READ Or SND_SEQ_PORT_CAP_SUBS_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: " & snd_strerror(rit))
  
 Wait 10
 
 ' Chiude il subsistema 'seq' di ALSA e distrugge il Client:
 snd_seq_close(handle)
 
End

Alcune informazioni sul nostro applicativo Client di Alsa sono visibili, fintanto che esso è in funzione, nel file "/proc/asound/seq/clients", ove sono elencati appunto i Client attivi di Alsa.

Va detto che, ovviamente, tutta la parte strettamente afferente alle funzioni esterne di ALSA potrebbe essere raccolta in un Modulo a se stante.


Note

[1] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/seq.html

[2] Vedi D. Blengino: Iniziare un dialogo con ALSA.

[3] I Puntatori (Pointer) sono variabili sulle quali si leggono e si scrivono indirizzi (i pointer contengono indirizzi). Ottenere un valore tramite un Puntatore si dice "dereferenziare il pointer", nel senso che il pointer viene interrogato, ed esso dice l'indirizzo di memoria ove cercare i dati.
Solitamente ai pointer è possibile assegnare il valore NULL, che significa "non esiste alcun indirizzo". Se i pointer sono NULL, non devono essere dereferenziati, pena (di solito) un errore del sistema operativo.

[4] Si è spiegato nella seguente pagina "Il Client di Alsa e le sue Porte" il concetto di "Capacità " della Porta del Client di ALSA ed il loro funzionamento.
In particolare si è detto che la definizione della "Capacità " ("READ", "WRITE" o "DUPLEX") della Porta deve essere considerata sempre dal punto di vista dell'altro Client di ALSA al quale ci si vuole connettere via ALSA.
Così dunque la Porta del Client dichiarata con Capacità "READ", vuol dire che gli altri Client di ALSA possono "leggere" i dati provenienti dal Client proprietario della Porta in questione.

Con il passaggio della Costante al terzo parametro della funzione "snd_seq_open()" si imposta la modalità di gestione del flusso di dati da parte del sub-sistema "Sequencer" di ALSA.
Così l'attribuzione a quel parametro formale, ad esempio, della Costante "SND_SEQ_OPEN_OUTPUT " sta a significare che il Sequencer di ALSA è capace di gestire il flusso di dati in Uscita. In tal caso ne consegue che la Porta del Client, utile per connettersi agli altri Client di ALSA, dovrà avere necessariamente una Capacità "READ.
Sempre restando all'esempio fatto, il Client creato può scrivere in uscita (OUTPUT) sulla "sua" Porta che ha la Capacità di farsi "leggere" dagli altri eventuali Client, ai quali lo stesso si è connesso.

Quindi in base a quale Costante sia stata passata al terzo argomento della funzione "snd_seq_open()", si dovrà impostare con apposite Costanti fornite da ALSA necessariamente una specifica "Capacità " della Porta del Client, come segue:

Stream ClientCapacità Porta
SND_SEQ_OPEN_OUTPUT ←→ READ (SND_SEQ_PORT_CAP_READ)
SND_SEQ_OPEN_INPUT ←→ WRITE (SND_SEQ_PORT_CAP_WRITE)
SND_SEQ_OPEN_DUPLEX ←→ DUPLEX (SND_SEQ_PORT_CAP_DUPLEX)