Differenze tra le versioni di "Alsa e Gambas: Ricezione dei dati Midi con l'uso dei File Descriptor"

Da Gambas-it.org - Wikipedia.
 
(4 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
===Introduzione===
+
==Introduzione==
L'uso dei ''File Descriptors'' <SUP>&#091;[[#Note|Nota 1]]&#093;</sup> è da considerarsi - allo stato attuale - il metodo più opportuno e corretto fra quelli individuati e praticabili per la ''ricezione'' dei messaggi Midi. E' inoltre pienamente coerente con molti programmi (ad esempio: ''aseqdump'', ''aplaymidi'', ''arecordmidi'', etc.), scritti in C, che infatti procedono al controllo dei ''file descriptors'' per vedere se c'è qualche dato Midi da leggere; dato che viene restituito poi da una specifica funzione di ALSA.
+
L'uso dei ''File Descriptors'' <SUP>&#091;[[#Note|nota 1]]&#093;</sup> è da considerarsi - allo stato attuale - <Span style="text-decoration:underline">il metodo più opportuno e corretto</span> fra quelli individuati e praticabili per la ''ricezione'' dei messaggi Midi. E' inoltre pienamente coerente con molti programmi (ad esempio: ''aseqdump'', ''aplaymidi'', ''arecordmidi'', etc.), scritti in C, che infatti procedono al controllo dei ''file descriptors'' per vedere se c'è qualche dato Midi da leggere; dato che viene restituito poi da una specifica funzione di ALSA.
  
 
Procederemo quindi alla realizzazione del nostro applicativo in Gambas seguendo, per quanto compatibile, l'organizzazione del codice dei predetti programmi in C. Tale codifica nella sua parte che ci interessa si sviluppa in sintesi con le seguenti funzioni di ALSA (che noi dovremo poi nel nostro applicativo ovviamente richiamare e gestire):
 
Procederemo quindi alla realizzazione del nostro applicativo in Gambas seguendo, per quanto compatibile, l'organizzazione del codice dei predetti programmi in C. Tale codifica nella sua parte che ci interessa si sviluppa in sintesi con le seguenti funzioni di ALSA (che noi dovremo poi nel nostro applicativo ovviamente richiamare e gestire):
  
* ''npfds = snd_seq_poll_descriptors_count(seq, POLLIN)'': con questa funzione viene chiesto ad ALSA quanti descrittori vi sono per essere letti. La variabile "''npfds''" è un integer; mentre POLLIN è una costante integer di ALSA uguale ad 1 (il suo valore può essere facilmente individuato lanciando da terminale il comando: ''grep -r POLLIN /usr/include/*'') ed andrà dichiarata anticipatamente.
+
* ''npfds = snd_seq_poll_descriptors_count(seq, POLLIN)'': con questa funzione viene chiesto ad ALSA quanti descrittori vi sono per essere letti. La variabile ''npfds'' è un Integer; mentre POLLIN è una costante integer di ALSA uguale ad 1 (il suo valore può essere facilmente individuato lanciando da terminale il comando: ''grep -r POLLIN /usr/include/*'' ) ed andrà dichiarata anticipatamente.
  
* '' pfds = alloca(sizeof(*pfds * npfds)'': con questa riga di codice viene allocata sufficiente memoria per contenere i descrittori. Essa è pari al singolo ''file descriptor'' moltiplicato per il numero dei ''file descriptor'' restituiti dalla precedente funzione di ALSA. La variabile ''pfds'' è un puntatore che nei codici in C di quei programmi è dichiarato come puntatore ad una "''struct pollfd''"; è insomma un puntatore ad un insieme di descrittori <SUP>&#091;[[#Note|Nota 2]]&#093;</sup>. E' dunque necessario verificare, ai fini della stesura del codice del nostro applicativo in Gambas, quanti byte sono occupati da questa struttura, e quindi indispensabili per noi poi da riservare. La "''struct pollfd''" è così descritta dal comando "''man poll''":
+
* '' pfds = alloca(sizeof(*pfds * npfds)'': con questa riga di codice viene allocata sufficiente memoria per contenere i descrittori. Essa è pari al singolo ''file descriptor'' moltiplicato per il numero dei ''file descriptor'' restituiti dalla precedente funzione di ALSA. La variabile ''pfds'' è un puntatore che nei codici in C di quei programmi è dichiarato come puntatore ad una ''struct pollfd''; è insomma un Puntatore ad un insieme di descrittori <SUP>&#091;[[#Note|nota 2]]&#093;</sup>. E' dunque necessario verificare, ai fini della stesura del codice del nostro applicativo in Gambas, quanti byte sono occupati da questa struttura, e quindi indispensabili per noi poi da riservare.
 +
<BR>La ''struct pollfd'' è così descritta dal comando ''man poll'':
 
  struct pollfd {
 
  struct pollfd {
 
   int fd; /* file descriptor */
 
   int fd; /* file descriptor */
Riga 16: Riga 17:
 
* ''snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN)'': con questa funzione si chiede ad ALSA di riempire la memoria puntata da ''pfds'' con i descrittori rilevanti individuati dalla precedente prima funzione. Questa chiamata passa il puntatore alla zona di memoria riservata, ed il numero di descrittori che possono essere scritti nella zone di memoria, affinché ALSA non vada a scrivere fuori di quell'area.
 
* ''snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN)'': con questa funzione si chiede ad ALSA di riempire la memoria puntata da ''pfds'' con i descrittori rilevanti individuati dalla precedente prima funzione. Questa chiamata passa il puntatore alla zona di memoria riservata, ed il numero di descrittori che possono essere scritti nella zone di memoria, affinché ALSA non vada a scrivere fuori di quell'area.
  
Per ciò che riguarda la scrittura del codice del nostro applicativo in Gambas, si tratta semplicemente di seguire sostanzialmente la medesima procedura <SUP>&#091;[[#Note|Nota 3]]&#093;</sup>: chiedere ad ALSA quanti descrittori servono, riservare la memoria per essi, e poi chiedere nuovamente ad ALSA di riempire la zona puntata dal Puntatore ''pfds''. Quando la variabile ''pfds'' è stata riempita, vi troveremo ''npfds'' volte la struttura "''struct pollfd''". Siccome ALSA non sa quanto spazio è stato riservato, è necessario passargli questo puntatore, così ALSA non va a scrivere fuori dello spazio di memoria riservato.
+
Per ciò che riguarda la scrittura del codice del nostro applicativo in Gambas, si tratta semplicemente di seguire sostanzialmente la medesima procedura <SUP>&#091;[[#Note|Nota 3]]&#093;</sup>: chiedere ad ALSA quanti descrittori servono, riservare la memoria per essi, e poi chiedere nuovamente ad ALSA di riempire la zona puntata dal Puntatore ''pfds''. Quando la variabile ''pfds'' è stata riempita, vi troveremo ''npfds'' volte la struttura ''struct pollfd''. Siccome ALSA non sa quanto spazio è stato riservato, è necessario passargli questo puntatore, così ALSA non va a scrivere fuori dello spazio di memoria riservato.
In Gambas, poi, con un ''Puntatore'' o un ''Memory Stream'' potremo ottenere il ''file descriptor''; oppure si potrà dichiarare una propria Struttura, simile a quella "''struct pollfd''", chiedere quindi ad ALSA di riempirla, e poi accedere ai singoli ''file descriptor'' con delle linee di codice del tipo seguente:
+
In Gambas, poi, con un ''Puntatore'' o un ''Memory Stream'' potremo ottenere il ''file descriptor''; oppure si potrà dichiarare una propria Struttura, simile a quella ''struct pollfd'', chiedere quindi ad ALSA di riempirla, e poi accedere ai singoli ''file descriptor'' con delle linee di codice del tipo seguente:
 
  For i = 0 to npfds -1
 
  For i = 0 to npfds -1
 
   miodescrip = miastruttura[i].fd
 
   miodescrip = miastruttura[i].fd
 
   ...etc...
 
   ...etc...
 
Ottenuti tali descrittori di file, bisognerà inserirli nel ''message loop'' di Gambas; quindi procedere nel modo solito con un ''event-driven''. In sostanza sarebbe necessario utilizzare nativamente in Gambas un meccanismo che generi un evento, quando il ''file descriptor'' diventa "''ready''".
 
Ottenuti tali descrittori di file, bisognerà inserirli nel ''message loop'' di Gambas; quindi procedere nel modo solito con un ''event-driven''. In sostanza sarebbe necessario utilizzare nativamente in Gambas un meccanismo che generi un evento, quando il ''file descriptor'' diventa "''ready''".
Ciò non era possibile, ma su nostra sollecitazione Benôit Minisini con la revisione #4160 di Gambas 3 ha messo a punto un "trucco" (come defnito da lui stesso) per consentire di leggere un ''file descriptor'' passato dalle funzioni <SUP>&#091;[[#Note|Nota 4]]&#093;</sup>. La soluzione dopo vari test risulta funzionante.
+
Ciò non era possibile, ma su nostra sollecitazione Benôit Minisini con la revisione SVN #4160 di Gambas 3 ha messo a punto un "trucco" (come defnito da lui stesso) per consentire di leggere un ''file descriptor'' passato dalle funzioni <SUP>&#091;[[#Note|Nota 4]]&#093;</sup>. La soluzione dopo vari test risulta funzionante.
  
 
* ''snd_seq_event_input(seq, &event)'': questa è in fine la funzione esterna di ALSA utilizzata dai programmi, scritti in C, sopra elencati, dalla quale si vanno a leggere i giusti, veri ed utili messaggi Midi.
 
* ''snd_seq_event_input(seq, &event)'': questa è in fine la funzione esterna di ALSA utilizzata dai programmi, scritti in C, sopra elencati, dalla quale si vanno a leggere i giusti, veri ed utili messaggi Midi.
  
  
===La scrittura in Gambas: obiettivo e strategia===
+
==La scrittura in Gambas: obiettivo e strategia==
 
Ricordiamo che il nostro ''obiettivo'' è quello di "<SPAN style="text-decoration:underline">ricevere</span>" messaggi Midi. Per fare ciò, si dovranno risolvere tre problemi:
 
Ricordiamo che il nostro ''obiettivo'' è quello di "<SPAN style="text-decoration:underline">ricevere</span>" messaggi Midi. Per fare ciò, si dovranno risolvere tre problemi:
 
* avere una porta attraverso la quale far entrare i dati;
 
* avere una porta attraverso la quale far entrare i dati;
 
* attivare la connessione <Span style="text-decoration:underline">dal</span> dispositivo Midi esterno;
 
* attivare la connessione <Span style="text-decoration:underline">dal</span> dispositivo Midi esterno;
 
* attivare la procedura capace di intercettare i messaggi Midi che dovranno essere letti <Span style="text-decoration:underline">esclusivamente</span> dalla funzione di ALSA "snd_seq_event_input(seq_&event()", quando sarà sollevato un evento attraverso i dati provenienti dal ''file descriptor'' passato da ALSA.
 
* attivare la procedura capace di intercettare i messaggi Midi che dovranno essere letti <Span style="text-decoration:underline">esclusivamente</span> dalla funzione di ALSA "snd_seq_event_input(seq_&event()", quando sarà sollevato un evento attraverso i dati provenienti dal ''file descriptor'' passato da ALSA.
<BR>La ''strategia'' attiene invece alla modalità di intercettazione dei messaggi Midi: essa sarà attuata ponendo il ''file descriptor'', passato dalla funzione di ALSA "snd_seq_poll_descriptor()", sotto ''osservazione'' per la Lettura (Read) mediante un comando simile a: ''hfile = OPEN "......" For Read Watch''. Normalmente, il programma "dorme", ma quando giungono dati al quel ''file descriptor'', esso diventa "''ready'' ", noi non andiamo a leggere i dati dal ''file descriptor'', cioè dalla variabile ''hfile'', ma il meccanismo OPEN... deve "svegliare" il programma e ''sollevare'' un evento Gambas, come ''File_Read()''. In fine andremo a leggere all'interno di questa seconda routine ''sollevata'' i <Span style="text-decoration:underline">veri e utili</span> dati degli Eventi Midi di ALSA Midi attraverso l'apposita funzione esterna: "<B>snd_seq_event_input()</b>".
+
<BR>La ''strategia'' attiene invece alla modalità di intercettazione dei messaggi Midi: essa sarà attuata ponendo il ''file descriptor'', passato dalla funzione di ALSA "snd_seq_poll_descriptor()", sotto ''osservazione'' per la Lettura (Read) mediante un comando simile a: ''hfile = OPEN "......" For Read Watch''. Normalmente, il programma "dorme", ma quando giungono dati al quel ''file descriptor'', esso diventa "''ready'' ", noi non andiamo a leggere i dati dal ''file descriptor'', cioè dalla variabile ''hfile'', ma il meccanismo OPEN... deve "svegliare" il programma e ''sollevare'' un evento Gambas, come "File_Read()". In fine andremo a leggere all'interno di questa seconda routine ''sollevata'' i <Span style="text-decoration:underline">veri e utili</span> dati degli Eventi Midi di ALSA Midi attraverso l'apposita funzione esterna: "<B>snd_seq_event_input()</b>".
 
<BR>Ripetiamo: bisogna ottenere da ALSA il ''file descriptor'' giusto. Tale ''file descriptor'' <SPAN style="text-decoration:underline">non va letto</span>, ossia <Span style="text-decoration:underline">non</span> vanno raccolti i dati da tale file, ma bisogna utilizzarlo <Span style="text-decoration:underline">soltanto</span> per ottenere un evento "_Read": quando tale ''File desciptor'' passa un valore, significa che c'è "altrove" un Evento Midi di ALSA da leggere.
 
<BR>Ripetiamo: bisogna ottenere da ALSA il ''file descriptor'' giusto. Tale ''file descriptor'' <SPAN style="text-decoration:underline">non va letto</span>, ossia <Span style="text-decoration:underline">non</span> vanno raccolti i dati da tale file, ma bisogna utilizzarlo <Span style="text-decoration:underline">soltanto</span> per ottenere un evento "_Read": quando tale ''File desciptor'' passa un valore, significa che c'è "altrove" un Evento Midi di ALSA da leggere.
 
<BR>I dati Midi utili vanno letti da ALSA. L'Evento "File_Read()" deve essere scatenato quando un dato Midi è pronto per essere letto. Questo dato però va letto dall'apposita funzione di ALSA e <Span style="text-decoration:underline">non</span> dal ''file descriptor'', il quale ci deve <Span style="text-decoration:underline">esclusivamente</span> e <Span style="text-decoration:underline">semplicemente</span> dire <Span style="text-decoration:underline">se</span> ci sono in arrivo dei dati da leggere ("altrove").
 
<BR>I dati Midi utili vanno letti da ALSA. L'Evento "File_Read()" deve essere scatenato quando un dato Midi è pronto per essere letto. Questo dato però va letto dall'apposita funzione di ALSA e <Span style="text-decoration:underline">non</span> dal ''file descriptor'', il quale ci deve <Span style="text-decoration:underline">esclusivamente</span> e <Span style="text-decoration:underline">semplicemente</span> dire <Span style="text-decoration:underline">se</span> ci sono in arrivo dei dati da leggere ("altrove").
  
Se non il programmatore non comprende l'importanza della distizione fra questi due passaggi: '''''avviso''''' e '''''lettura dei veri dati''''' (ossia se non si comprende appieno la strategia da seguire), non sarà possibile utilizzare il metodo dell'interrogazione dei ''file descriptor'' per ottenere gli Eventi Midi in ''ricezione''.
+
Se non il programmatore non comprende l'importanza della distinzione fra questi due passaggi: '''''avviso''''' e '''''lettura dei veri dati''''' (ossia se non si comprende appieno la strategia da seguire), non sarà possibile utilizzare il metodo dell'interrogazione dei ''file descriptor'' per ottenere gli Eventi Midi in ''ricezione''.
  
  
===La scrittura in Gambas: il codice===
+
==La scrittura in Gambas: il codice==
 
Considereremo qui i due passaggi sopra accennati: la creazione della porta di Entrata dell'applicativo e le routine per la ricezione dei messaggi Midi attraverso l'uso dei ''file descriptor''.
 
Considereremo qui i due passaggi sopra accennati: la creazione della porta di Entrata dell'applicativo e le routine per la ricezione dei messaggi Midi attraverso l'uso dei ''file descriptor''.
  
====Creazione della porta con capacità ''Write''====
+
===Creazione della porta con capacità ''Write''===
 
Dovremo effettuare una sottoscrizione della porta del nostro dispositivo con capacità ''scrivibile''; dovremo cioè creare una porta scrivibile da parte del dispositivo Midi esterno, e quindi leggibile da parte del nostro applicativo. Dunque, oltre alla funzione di creazione della porta di uscita del nostro applicativo, si dovrà inserire nel codice anche la funzione esterna per la creazione della porta di entrata dei dati dal dispositivo esterno. Tale funzione di Alsa in C è così dichiarata: ''int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type) , quindi come quella di uscita, e nel nostro codice Gambas sarà così dichiarata:
 
Dovremo effettuare una sottoscrizione della porta del nostro dispositivo con capacità ''scrivibile''; dovremo cioè creare una porta scrivibile da parte del dispositivo Midi esterno, e quindi leggibile da parte del nostro applicativo. Dunque, oltre alla funzione di creazione della porta di uscita del nostro applicativo, si dovrà inserire nel codice anche la funzione esterna per la creazione della porta di entrata dei dati dal dispositivo esterno. Tale funzione di Alsa in C è così dichiarata: ''int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type) , quindi come quella di uscita, e nel nostro codice Gambas sarà così dichiarata:
 
  Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
 
  Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
Riga 52: Riga 53:
 
  Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
 
  Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
  
====Connessione con il dispositivo Midi esterno====
+
===Connessione con il dispositivo Midi esterno===
 
Ovviamente, creata la porta ''scrivibile'' da parte di un dispositivo Midi esterno, si dovrà prevedere la connessione con tale dispositivo Midi.
 
Ovviamente, creata la porta ''scrivibile'' da parte di un dispositivo Midi esterno, si dovrà prevedere la connessione con tale dispositivo Midi.
 
<BR>Utilizzeremo a tal proposito la funzione esterna di Alsa: ''int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)'' , che sarà così dichiarata nel nostro codice Gambas:
 
<BR>Utilizzeremo a tal proposito la funzione esterna di Alsa: ''int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)'' , che sarà così dichiarata nel nostro codice Gambas:
Riga 59: Riga 60:
 
  err = snd_seq_connect_from(handle, inport, 24, 0)
 
  err = snd_seq_connect_from(handle, inport, 24, 0)
  
====Il codice per la ricezione dei messaggi Midi mediante i ''file descriptor''====
+
===Il codice per la ricezione dei messaggi Midi mediante i ''file descriptor''===
 
Come abbiamo avuto modo di esporre ampiamente nei paragrafi precedenti, nella scrittura del codice faremo uso di tre funzioni esterne di ALSA: le prime due ci daranno la possibilità di ricavare il ''file descriptor'' da passare al comando ''OPEN'', la terza quella di raccogliere i messaggi Midi a noi utili.
 
Come abbiamo avuto modo di esporre ampiamente nei paragrafi precedenti, nella scrittura del codice faremo uso di tre funzioni esterne di ALSA: le prime due ci daranno la possibilità di ricavare il ''file descriptor'' da passare al comando ''OPEN'', la terza quella di raccogliere i messaggi Midi a noi utili.
 
  private Const POLLIN As Short = 1
 
  private Const POLLIN As Short = 1
 
  private fd As File
 
  private fd As File
 
+
  Private Extern snd_seq_poll_descriptors_count(handle As Pointer, POLLIN As Short) As Integer
+
Library "libasound:2"
  Private Extern snd_seq_poll_descriptors(handle As Pointer, pfds As Pointer, npfds As Integer, POLLIN As Short)
+
  Private Extern snd_seq_event_input(handle as Pointer, pEventi as Pointer) as Integer
+
......
 +
<FONT Color=gray>' ''int snd_seq_poll_descriptors_count(snd_seq_t *seq, short events)''
 +
' ''Returns the number of poll descriptors.''</font>
 +
  Private Extern snd_seq_poll_descriptors_count(seq As Pointer, events As Short) As Integer
 +
 +
<FONT Color=gray>' ''int snd_seq_poll_descriptors(snd_seq_t *seq, struct pollfd *pfds, unsigned int space, short events)''
 +
' ''Get poll descriptors.''</font>
 +
  Private Extern snd_seq_poll_descriptors(seq As Pointer, pfds As Pollfd, space_ As Integer, events As Short) As Integer
 +
 +
<FONT Color=gray>' ''int snd_seq_event_input(snd_seq_t *seq, snd_seq_event_t **ev)''
 +
' ''Retrieve an event from sequencer.''</font>
 +
  Private Extern snd_seq_event_input(seq As Pointer, ev As Pointer) As Integer
 
   
 
   
 
   
 
   
 
  <FONT Color=gray>' '''''Routine di "Avviso": i dati ricevuti dal "file descriptor", posto alla fine, avvisano dell'arrivo dei dati di un Evento Midi di ALSA, i quali però saranno raccolti effettivamente da un'altra funzione esterna di ALSA nell'altra successiva routine:'''''</font>
 
  <FONT Color=gray>' '''''Routine di "Avviso": i dati ricevuti dal "file descriptor", posto alla fine, avvisano dell'arrivo dei dati di un Evento Midi di ALSA, i quali però saranno raccolti effettivamente da un'altra funzione esterna di ALSA nell'altra successiva routine:'''''</font>
  '''Public''' Procedure OsservaColTrucco()
+
  Public Procedure OsservaColTrucco()
 
   
 
   
 
   Dim npfds, i, numfd As Integer
 
   Dim npfds, i, numfd As Integer
Riga 101: Riga 113:
 
   Free(pfds)
 
   Free(pfds)
 
   
 
   
  '''End'''
+
  End
 
   
 
   
 
   
 
   
  '''Public''' Sub File_Read()  <FONT Color=gray>' '''''Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventio Midi di ALSA'''''</font>
+
  Public Sub File_Read()  <FONT Color=gray>' '''''Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventio Midi di ALSA'''''</font>
 
   
 
   
 
   Dim i As Integer
 
   Dim i As Integer
Riga 111: Riga 123:
 
   Dim b as Byte
 
   Dim b as Byte
 
   
 
   
   While True <Font Color= #006400><SUP>&#091;[[#Note|Nota 6]]&#093;</sup></font>
+
   Do <Font Color= #006400><B><SUP>&#091;[[#Note|Nota 6]]&#093;</sup></b></font>
 
   
 
   
 
  <Font Color= #006400>' '''''Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile pEventi.''</font>
 
  <Font Color= #006400>' '''''Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile pEventi.''</font>
 
     i = <FONT Color=#B22222><B>snd_seq_event_input</b></font>(handle, VarPtr(pEventi))
 
     i = <FONT Color=#B22222><B>snd_seq_event_input</b></font>(handle, VarPtr(pEventi))
 
   
 
   
     If i < 0 Then Return
+
     If i < 0 Then Break
 
   
 
   
 
  <Font Color= #006400>' ''Prepara, quindi, il Puntatore per estrarvi i dati Midi restituiti dalla precedente funzione di ALSA:''</font>
 
  <Font Color= #006400>' ''Prepara, quindi, il Puntatore per estrarvi i dati Midi restituiti dalla precedente funzione di ALSA:''</font>
Riga 131: Riga 143:
 
     ............
 
     ............
 
   
 
   
   Wend
+
   Loop
 
    
 
    
  '''End'''
+
  End
  
 
====Esempio pratico====
 
====Esempio pratico====
Riga 152: Riga 164:
 
   tag As Byte  
 
   tag As Byte  
 
   queue As Byte  
 
   queue As Byte  
   tick_time As Integer
+
   tick_o_Tv_sec As Integer
   real_time As Integer
+
   Tv_nsec As Integer
 
   source_id As Byte
 
   source_id As Byte
 
   source_porta As Byte
 
   source_porta As Byte
Riga 205: Riga 217:
 
   
 
   
 
   
 
   
  '''Public''' Sub Main()
+
  Public Sub Main()
 
   
 
   
 
   Dim err, id As Integer
 
   Dim err, id As Integer
Riga 256: Riga 268:
 
   Free(pfds)
 
   Free(pfds)
 
   
 
   
  '''End'''
+
  End
 +
 
   
 
   
  '''Public''' Sub File_Read() <Font Color=gray>' ''Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventi Midi di ALSA''</font>
+
  Public Sub File_Read() <Font Color=gray>' ''Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventi Midi di ALSA''</font>
 
   
 
   
 
   Dim i As Integer
 
   Dim i As Integer
Riga 268: Riga 281:
 
  <Font Color=gray>' '''''Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile "pEventi":'''''</font>
 
  <Font Color=gray>' '''''Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile "pEventi":'''''</font>
 
     i = snd_seq_event_input(handle, VarPtr(pEventi))
 
     i = snd_seq_event_input(handle, VarPtr(pEventi))
     If i < 0 Then Return
+
     If i < 0 Then Break
 
   
 
   
 
     midi = pEventi
 
     midi = pEventi
Riga 284: Riga 297:
 
   Loop
 
   Loop
 
   
 
   
  '''End'''
+
  End
  
===Uso contemporaneo di due o più dispositivi Midi esterni===
+
 
 +
==Uso contemporaneo di due o più dispositivi Midi esterni==
 
L'uso dei "''file descriptor''" ci permette, a differenza del ''file-device'', la fruizione più ampia delle facoltà del sistema ALSA, come ad esempio poter utilizzare <SPAN style ="text-decoration:underline">contemporaneamente</span> due o più dispositivi Midi esterni.
 
L'uso dei "''file descriptor''" ci permette, a differenza del ''file-device'', la fruizione più ampia delle facoltà del sistema ALSA, come ad esempio poter utilizzare <SPAN style ="text-decoration:underline">contemporaneamente</span> due o più dispositivi Midi esterni.
 
<BR>Per fare ciò, bisognerà effettuare delle integrazioni alla routine attinente alle connessioni del nostro applicativo:
 
<BR>Per fare ciò, bisognerà effettuare delle integrazioni alla routine attinente alle connessioni del nostro applicativo:
  ''<Font Color= #006400>' int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)''</font>
+
  <Font Color=gray>' ''int snd_seq_connect_from(snd_seq_t *seq, int myport, int src_client, int src_port)''
 +
' ''Simple subscription.''</font>
 
  Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer
 
  Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer
 +
 +
<Font Color=gray>' ''int snd_seq_connect_to(snd_seq_t *seq, int myport, int src_client, int src_port)''
 +
' ''Simple subscription.''</font>
 
  Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer
 
  Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer
 
   
 
   
  '''Public''' Sub setdevice(client As Integer, port As Integer)
+
  Public Sub setdevice(client As Integer, port As Integer)
 
   
 
   
 
   Dim err As Integer
 
   Dim err As Integer
Riga 300: Riga 318:
 
   dport = port
 
   dport = port
 
   
 
   
  ''<Font Color= #006400>' Ci connettiamo ad ALSA per inviargli i dati Midi ricevuti:''</font>
+
  <Font Color= #006400>' ''Ci connettiamo ad ALSA per inviargli i dati Midi ricevuti:''</font>
 
   err = snd_seq_connect_to(handle, outport, 14, dport)
 
   err = snd_seq_connect_to(handle, outport, 14, dport)
 
   printerr("Subscribe outport", err)
 
   printerr("Subscribe outport", err)
 
   
 
   
  ''<Font Color= #006400>' Connettiamo un primo dispositivo Midi esterno al nostro applicativo per ricevere i dati Midi
+
  <Font Color= #006400>' ''Connettiamo un primo dispositivo Midi esterno al nostro applicativo per ricevere i dati Midi
 
  '' ' (ipotizziamo che il suo numero di Id-Client sia 24):''</font>
 
  '' ' (ipotizziamo che il suo numero di Id-Client sia 24):''</font>
 
   err = snd_seq_connect_from(handle, inport, 24, 0)
 
   err = snd_seq_connect_from(handle, inport, 24, 0)
 
   printerr("Subscribe inport", err)
 
   printerr("Subscribe inport", err)
 
    
 
    
  '''''<Font Color= #006400>' Inseriamo una seconda chiamata alla funzione esterna di ALSA per utilizzare contemporaneamente un secondo dispositivo Midi esterno''' (ipotizziamo che il suo numero di Id-Client sia 28):''</font>
+
  <Font Color= #006400>' '''''Inseriamo una seconda chiamata alla funzione esterna di ALSA per utilizzare contemporaneamente un secondo dispositivo Midi esterno''' (ipotizziamo che il suo numero di Id-Client sia 28):''</font>
 
   err = snd_seq_connect_from(handle, inport, 28, 0)
 
   err = snd_seq_connect_from(handle, inport, 28, 0)
 
   printerr("Subscribe inport", err)
 
   printerr("Subscribe inport", err)
Riga 315: Riga 333:
 
   If err < 0 Then error.Raise("Error subscribe output device")
 
   If err < 0 Then error.Raise("Error subscribe output device")
 
   
 
   
  '''End'''
+
  End
  
  
Riga 322: Riga 340:
 
[1] Un ''File Descriptor'' è un ''handle'' di un file.
 
[1] Un ''File Descriptor'' è un ''handle'' di un file.
  
[2] ALSA non ritorna semplici ''file descriptor'', ma multipli "''struct pollfd''". E' quindi indispensabile sapere quanti byte occupa una struttura come quella, e quindi quanta memoria è richiesta in totale.
+
[2] ALSA non ritorna semplici ''file descriptor'', ma multipli "struct pollfd". E' quindi indispensabile sapere quanti byte occupa una struttura come quella, e quindi quanta memoria è richiesta in totale.
  
[3] I programmi in C, sopra citati, prevedono, in vero anche un ciclo infinito ed una chiamata alle funzioni ''snd_seq_nonblock()'' di ALSA e ''poll()'' del sistema, ma nel nostro applicativo in Gambas, come vedremo, questi due elementi non saranno necessari.
+
[3] I programmi in C, sopra citati, prevedono, in vero anche un ciclo infinito ed una chiamata alle funzioni "snd_seq_nonblock()" di ALSA e "poll()" del sistema, ma nel nostro applicativo in Gambas, come vedremo, questi due elementi non saranno necessari.
  
[4] Così si è emulata la capacità di personalizzare il ''message loop'', consentendo al programma di far monitorare dal sistema anche file ''arbitrari'' (nel nostro caso i "''file descriptor''" passati da ALSA) da noi desiderati, ossia anche gli eventi ai quali noi siamo appunto interessati.
+
[4] Così si è emulata la capacità di personalizzare il ''message loop'', consentendo al programma di far monitorare dal sistema anche file ''arbitrari'' (nel nostro caso i ''file descriptor'' passati da ALSA) da noi desiderati, ossia anche gli eventi ai quali noi siamo appunto interessati.
  
 
[5] Oltre alla visualizzazione nella console di Gambas, è possibile vedere il numero del ''file descriptor'', usato da ALSA, lanciando nel terminale - dopo aver trovato il PID del programma - i seguenti comandi:
 
[5] Oltre alla visualizzazione nella console di Gambas, è possibile vedere il numero del ''file descriptor'', usato da ALSA, lanciando nel terminale - dopo aver trovato il PID del programma - i seguenti comandi:
 
* ''lsof -p Pid_del_programma'': si potrà notare che il numero del ''file descriptor'', anche confrontandolo con quello mostrato dalla console di Gambas, è quello che nella lunga lista ha come "NAME" il percorso indicato: /dev/snd/seq;
 
* ''lsof -p Pid_del_programma'': si potrà notare che il numero del ''file descriptor'', anche confrontandolo con quello mostrato dalla console di Gambas, è quello che nella lunga lista ha come "NAME" il percorso indicato: /dev/snd/seq;
* ''strace -p Pid_del_programma'': ''strace'' è un comando per vedere le chiamate di sistema effettuate da un processo qualunque. La prima riga mostra hee la chiamata di una particolare funzione viene invocata su un determinato ''file descriptor'' (es.: fd=3), e ritorna con il valore ''n'' (cioé ci sono num. ''n'' ''file descriptor'' pronti per essere letti. Nel nostro esempio questo ''file descriptor'' è il numero 3 ed è pronto per essere letto).
+
* ''strace -p Pid_del_programma'': ''strace'' è un comando per vedere le chiamate di sistema effettuate da un processo qualunque. La prima riga mostra hee la chiamata di una particolare funzione viene invocata su un determinato ''file descriptor'' (es.: fd=3), e ritorna con il valore ''n'' (cioé ci sono num. ''n file descriptor'' pronti per essere letti. Nel nostro esempio questo ''file descriptor'' è il numero 3 ed è pronto per essere letto).
  
[6] Tutto il meccanismo si basa sul fatto di poter dire al sistema operativo: "quando ci sono dati da leggere, avvertimi". Il tutto si traduce, nel programma, che l'evento ''File_Read()'' viene scatenato.
+
[6] Tutto il meccanismo si basa sul fatto di poter dire al sistema operativo: "quando ci sono dati da leggere, avvertimi". Il tutto si traduce, nel programma, che l'evento "File_Read()" viene scatenato.
Il meccanismo consente a un programma di poter "dormire", perciò senza impegnare la CPU, per tutto il tempo che non ci sono dati disponibili. Se si lancia un "''ps ax''" in console, sarà possibile infatti notare  che ci sono decine di processi che girano, anche se la CPU è scarica. Tutti quei programmi stanno aspettando qualcosa... ed è solo grazie a questo meccanismo che possono girare tutti insieme senza sovraccaricare la CPU.
+
Il meccanismo consente a un programma di poter "dormire", perciò senza impegnare la CPU, per tutto il tempo che non ci sono dati disponibili. Se si lancia un "ps ax" in console, sarà possibile infatti notare  che ci sono decine di processi che girano, anche se la CPU è scarica. Tutti quei programmi stanno aspettando qualcosa... ed è solo grazie a questo meccanismo che possono girare tutti insieme senza sovraccaricare la CPU.
<BR>Sempre per il risparmio di CPU, e questo è il punto centrale della questione, il sistema operativo si limita ad avvertire il programma -una volta sola- che qualche cosa è pronto da leggere. A questo punto il programma si sveglia, va a leggere ciò che c'è da leggere, e torna a dormire. Naturalmente, può passare un certo tempo tra l'avvertimento del sistema e la reazione (lettura del file) del programma (il programma potrebbe essere impegnato a fare qualche cosa d'altro). Se durante questo tempo arrivano altri dati, il sistema non manda ulteriori avvertimenti, cioè non verrà generato un secondo evento, e pertanto quei dati questi rischiano di accatastarsi uno sull'altro. Quindi, quando l'evento ''File_Read()'' si scatena, non si è in grado di sapere quanti eventi (o quanti byte) sono pronti da leggere - si sa solo che c'è "qualche cosa". Quindi bisogna leggere tutto quello che c'è, tutto ciò che è disponibile, non un solo dato per volta; e bisogna provare a leggere finché ce n'è. Se non si fa così, è probabile che alcuni dati rimangano indietro.
+
<BR>Sempre per il risparmio di CPU, e questo è il punto centrale della questione, il sistema operativo si limita ad avvertire il programma -una volta sola- che qualche cosa è pronto da leggere. A questo punto il programma si sveglia, va a leggere ciò che c'è da leggere, e torna a dormire. Naturalmente, può passare un certo tempo tra l'avvertimento del sistema e la reazione (lettura del file) del programma (il programma potrebbe essere impegnato a fare qualche cosa d'altro). Se durante questo tempo arrivano altri dati, il sistema non manda ulteriori avvertimenti, cioè non verrà generato un secondo evento, e pertanto quei dati questi rischiano di accatastarsi uno sull'altro. Quindi, quando l'evento "File_Read()" si scatena, non si è in grado di sapere quanti eventi (o quanti byte) sono pronti da leggere - si sa solo che c'è "qualche cosa". Quindi bisogna leggere tutto quello che c'è, tutto ciò che è disponibile, non un solo dato per volta; e bisogna provare a leggere finché ce n'è. Se non si fa così, è probabile che alcuni dati rimangano indietro.
 
<BR>Immaginiamo, per esempio, di formare un accordo di tre note: sicuramente uno dei tasti verrà premuto prima degli altri. Il primo dei tasti causerà il passaggio del file descriptor allo stato "pronto", ma se il programma non è velocissimo a leggere quel tasto, può darsi che nel frattempo venga accodato un altro tasto. A questo punto il programma avrà in sospeso un solo evento gambas, non due - quindi con quell'unico evento occorre leggere due tasti.
 
<BR>Immaginiamo, per esempio, di formare un accordo di tre note: sicuramente uno dei tasti verrà premuto prima degli altri. Il primo dei tasti causerà il passaggio del file descriptor allo stato "pronto", ma se il programma non è velocissimo a leggere quel tasto, può darsi che nel frattempo venga accodato un altro tasto. A questo punto il programma avrà in sospeso un solo evento gambas, non due - quindi con quell'unico evento occorre leggere due tasti.
<BR>Le istruzioni di lettura vanno dunque ripetute. Ciò comporta l'utilizzo di un ciclo: "''While True''". Ponendo ''True'', la condizione risulta sempre vera, generando virtualmente un ciclo infinito. Dal ciclo si uscirà soltanto quando ALSA dirà che non c'è più niente da leggere.
+
<BR>Le istruzioni di lettura vanno dunque ripetute, comportando così l'utilizzo di un ciclo: "Do...Loop".
 +
<BR>Dal ciclo si uscirà soltanto quando non c'è nulla da leggere dalla funzione esterna "snd_seq_event_input()".

Versione attuale delle 06:54, 1 lug 2023

Introduzione

L'uso dei File Descriptors [nota 1] è da considerarsi - allo stato attuale - il metodo più opportuno e corretto fra quelli individuati e praticabili per la ricezione dei messaggi Midi. E' inoltre pienamente coerente con molti programmi (ad esempio: aseqdump, aplaymidi, arecordmidi, etc.), scritti in C, che infatti procedono al controllo dei file descriptors per vedere se c'è qualche dato Midi da leggere; dato che viene restituito poi da una specifica funzione di ALSA.

Procederemo quindi alla realizzazione del nostro applicativo in Gambas seguendo, per quanto compatibile, l'organizzazione del codice dei predetti programmi in C. Tale codifica nella sua parte che ci interessa si sviluppa in sintesi con le seguenti funzioni di ALSA (che noi dovremo poi nel nostro applicativo ovviamente richiamare e gestire):

  • npfds = snd_seq_poll_descriptors_count(seq, POLLIN): con questa funzione viene chiesto ad ALSA quanti descrittori vi sono per essere letti. La variabile npfds è un Integer; mentre POLLIN è una costante integer di ALSA uguale ad 1 (il suo valore può essere facilmente individuato lanciando da terminale il comando: grep -r POLLIN /usr/include/* ) ed andrà dichiarata anticipatamente.
  • pfds = alloca(sizeof(*pfds * npfds): con questa riga di codice viene allocata sufficiente memoria per contenere i descrittori. Essa è pari al singolo file descriptor moltiplicato per il numero dei file descriptor restituiti dalla precedente funzione di ALSA. La variabile pfds è un puntatore che nei codici in C di quei programmi è dichiarato come puntatore ad una struct pollfd; è insomma un Puntatore ad un insieme di descrittori [nota 2]. E' dunque necessario verificare, ai fini della stesura del codice del nostro applicativo in Gambas, quanti byte sono occupati da questa struttura, e quindi indispensabili per noi poi da riservare.


La struct pollfd è così descritta dal comando man poll:

struct pollfd {
  int fd; /* file descriptor */
  short events; /* requested events */
  short revents; /* returned events */
}

Da tale descrizione possiamo vedere che la struttura è costituita da tre valori: un Integer e due Short, per una somma complessiva pari ad: 8 byte occupati. Quindi nel nostro applicativo dovremo riservare una corrispondente area di memoria.

  • snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN): con questa funzione si chiede ad ALSA di riempire la memoria puntata da pfds con i descrittori rilevanti individuati dalla precedente prima funzione. Questa chiamata passa il puntatore alla zona di memoria riservata, ed il numero di descrittori che possono essere scritti nella zone di memoria, affinché ALSA non vada a scrivere fuori di quell'area.

Per ciò che riguarda la scrittura del codice del nostro applicativo in Gambas, si tratta semplicemente di seguire sostanzialmente la medesima procedura [Nota 3]: chiedere ad ALSA quanti descrittori servono, riservare la memoria per essi, e poi chiedere nuovamente ad ALSA di riempire la zona puntata dal Puntatore pfds. Quando la variabile pfds è stata riempita, vi troveremo npfds volte la struttura struct pollfd. Siccome ALSA non sa quanto spazio è stato riservato, è necessario passargli questo puntatore, così ALSA non va a scrivere fuori dello spazio di memoria riservato. In Gambas, poi, con un Puntatore o un Memory Stream potremo ottenere il file descriptor; oppure si potrà dichiarare una propria Struttura, simile a quella struct pollfd, chiedere quindi ad ALSA di riempirla, e poi accedere ai singoli file descriptor con delle linee di codice del tipo seguente:

For i = 0 to npfds -1
  miodescrip = miastruttura[i].fd
  ...etc...

Ottenuti tali descrittori di file, bisognerà inserirli nel message loop di Gambas; quindi procedere nel modo solito con un event-driven. In sostanza sarebbe necessario utilizzare nativamente in Gambas un meccanismo che generi un evento, quando il file descriptor diventa "ready". Ciò non era possibile, ma su nostra sollecitazione Benôit Minisini con la revisione SVN #4160 di Gambas 3 ha messo a punto un "trucco" (come defnito da lui stesso) per consentire di leggere un file descriptor passato dalle funzioni [Nota 4]. La soluzione dopo vari test risulta funzionante.

  • snd_seq_event_input(seq, &event): questa è in fine la funzione esterna di ALSA utilizzata dai programmi, scritti in C, sopra elencati, dalla quale si vanno a leggere i giusti, veri ed utili messaggi Midi.


La scrittura in Gambas: obiettivo e strategia

Ricordiamo che il nostro obiettivo è quello di "ricevere" messaggi Midi. Per fare ciò, si dovranno risolvere tre problemi:

  • avere una porta attraverso la quale far entrare i dati;
  • attivare la connessione dal dispositivo Midi esterno;
  • attivare la procedura capace di intercettare i messaggi Midi che dovranno essere letti esclusivamente dalla funzione di ALSA "snd_seq_event_input(seq_&event()", quando sarà sollevato un evento attraverso i dati provenienti dal file descriptor passato da ALSA.


La strategia attiene invece alla modalità di intercettazione dei messaggi Midi: essa sarà attuata ponendo il file descriptor, passato dalla funzione di ALSA "snd_seq_poll_descriptor()", sotto osservazione per la Lettura (Read) mediante un comando simile a: hfile = OPEN "......" For Read Watch. Normalmente, il programma "dorme", ma quando giungono dati al quel file descriptor, esso diventa "ready ", noi non andiamo a leggere i dati dal file descriptor, cioè dalla variabile hfile, ma il meccanismo OPEN... deve "svegliare" il programma e sollevare un evento Gambas, come "File_Read()". In fine andremo a leggere all'interno di questa seconda routine sollevata i veri e utili dati degli Eventi Midi di ALSA Midi attraverso l'apposita funzione esterna: "snd_seq_event_input()".
Ripetiamo: bisogna ottenere da ALSA il file descriptor giusto. Tale file descriptor non va letto, ossia non vanno raccolti i dati da tale file, ma bisogna utilizzarlo soltanto per ottenere un evento "_Read": quando tale File desciptor passa un valore, significa che c'è "altrove" un Evento Midi di ALSA da leggere.
I dati Midi utili vanno letti da ALSA. L'Evento "File_Read()" deve essere scatenato quando un dato Midi è pronto per essere letto. Questo dato però va letto dall'apposita funzione di ALSA e non dal file descriptor, il quale ci deve esclusivamente e semplicemente dire se ci sono in arrivo dei dati da leggere ("altrove").

Se non il programmatore non comprende l'importanza della distinzione fra questi due passaggi: avviso e lettura dei veri dati (ossia se non si comprende appieno la strategia da seguire), non sarà possibile utilizzare il metodo dell'interrogazione dei file descriptor per ottenere gli Eventi Midi in ricezione.


La scrittura in Gambas: il codice

Considereremo qui i due passaggi sopra accennati: la creazione della porta di Entrata dell'applicativo e le routine per la ricezione dei messaggi Midi attraverso l'uso dei file descriptor.

Creazione della porta con capacità Write

Dovremo effettuare una sottoscrizione della porta del nostro dispositivo con capacità scrivibile; dovremo cioè creare una porta scrivibile da parte del dispositivo Midi esterno, e quindi leggibile da parte del nostro applicativo. Dunque, oltre alla funzione di creazione della porta di uscita del nostro applicativo, si dovrà inserire nel codice anche la funzione esterna per la creazione della porta di entrata dei dati dal dispositivo esterno. Tale funzione di Alsa in C è così dichiarata: int snd_seq_create_simple_port(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type) , quindi come quella di uscita, e nel nostro codice Gambas sarà così dichiarata:

Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

mentre sarà richiamata all'interno della medesima routine della nostra classe CAlsa.class, ove è presente anche la funzione per la creazione del porta in uscita

 err = snd_seq_create_simple_port(handle, "Seq-Out", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)

laddove SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC, SND_SEQ_PORT_TYPE_APPLICATION sono tre costanti:

Private Const SND_SEQ_PORT_CAP_WRITE As Integer = 2
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576

Connessione con il dispositivo Midi esterno

Ovviamente, creata la porta scrivibile da parte di un dispositivo Midi esterno, si dovrà prevedere la connessione con tale dispositivo Midi.
Utilizzeremo a tal proposito la funzione esterna di Alsa: int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer) , che sarà così dichiarata nel nostro codice Gambas:

Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

e sarà così richiamata in routine (ponendo il caso che il numero identificativo Client del dispositivo Midi esterno sia 24 e la porta 0):

err = snd_seq_connect_from(handle, inport, 24, 0)

Il codice per la ricezione dei messaggi Midi mediante i file descriptor

Come abbiamo avuto modo di esporre ampiamente nei paragrafi precedenti, nella scrittura del codice faremo uso di tre funzioni esterne di ALSA: le prime due ci daranno la possibilità di ricavare il file descriptor da passare al comando OPEN, la terza quella di raccogliere i messaggi Midi a noi utili.

private Const POLLIN As Short = 1
private fd As File

Library "libasound:2"

......
' 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(seq 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(seq As Pointer, pfds As Pollfd, space_ As Integer, events As Short) As Integer

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


' Routine di "Avviso": i dati ricevuti dal "file descriptor", posto alla fine, avvisano dell'arrivo dei dati di un Evento Midi di ALSA, i quali però saranno raccolti effettivamente da un'altra funzione esterna di ALSA nell'altra successiva routine:
Public Procedure OsservaColTrucco()

 Dim npfds, i, numfd As Integer
 Dim pfds As Pointer
 Dim st As Stream

 npfds = snd_seq_poll_descriptors_count(handle, POLLIN)

' Alloca la memoria necessaria, tenendo conto del numero di byte occupati dalla "struct pollfd".
 pfds = Alloc(SizeOf(gb.Pointer), npfds)

 snd_seq_poll_descriptors(handle, pfds, npfds, POLLIN)

 st = Memory pfds For Read

 For i = 0 To npfds - 1

' Va comunque a leggere dal 1° byte:
   Seek #st, i * SizeOf(gb.Pointer)
   Read #st, numfd

' Solo per vedere in console quale numero di fd è stato passato da ALSA: [Nota 5]
   Print "numfd = "; numfd

' La seguente riga di comando è quella essenziale con il "trucco" per "osservare" il "file descriptor" passato da ALSA: il programma così resta in attesa di dati provenienti dal file descriptor per poter scatenare la sub-routine 'File_Read()'.
   fd = Open "." & CStr(numfd) For Read Watch

 Next

 Free(pfds)

End


Public Sub File_Read()   ' Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventio Midi di ALSA

 Dim i As Integer
 Dim pEventi as Pointer
 Dim st as Stream
 Dim b as Byte

 Do  [Nota 6]

' Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile pEventi.
   i = snd_seq_event_input(handle, VarPtr(pEventi))

   If i < 0 Then Break

' Prepara, quindi, il Puntatore per estrarvi i dati Midi restituiti dalla precedente funzione di ALSA:
    st = Memory pEventi For Read

' In questo caso va a leggere dal 1° byte per ottenere il "tipo" di Evento Midi secondo il protocollo di ALSA:
    Seek #st, 0
    Read #st, b

' Filtro, basato sul numero del "Type", per eliminare gli Eventi Midi che non ci interessano:
    If b > 14 Then Return

' Quindi le consuete funzioni per leggere gli altri byte, al fine di ottenere i restanti messaggi Midi che ci interessano.
   ............

 Loop
  
End

Esempio pratico

Mostriamo di seguito un esempio pratico, nel quale una tastiera Midi esterna (o altro dispositivo Midi), connessa al sistema interno di ALSA (ID=14:0), invia dei dati Midi, che saranno intercettati dal programma appresso esposto.
Per connettere il dispositivo esterno Midi ad ALSA, lanciare nel Terminale la seguente riga di comando:

~$ aconnect 24:0 14:0

laddove "24" è il numero identificativo del Client-dispositivo esterno (ovviamente si scriverà un altro numero, qualora esso sia diverso da questo).

Private Const POLLIN As Short = 1
Private handle As Pointer
Private fd As File


Library "libasound:2.0.0"

Public Struct snd_seq_event
  type As Byte                ' byte indice 0
  flag As Byte
  tag As Byte 
  queue As Byte 
  tick_o_Tv_sec As Integer
  Tv_nsec As Integer
  source_id As Byte
  source_porta As Byte
  dest_id As Byte
  dest_porta As Byte
    channel As Byte           ' byte indice 16
    note As Byte
    velocity As Byte
    off_velocity As Byte
      param As Integer        ' byte indice 20
      value As Integer        ' byte indice 24
End Struct

Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Private Const SND_SEQ_PORT_CAP_WRITE As Integer = 2
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_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE

' int snd_seq_open(snd_seq_t **seqp, const char * name, Int streams, Int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(Pseq 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_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_client_id(snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)
' Simple subscription (w/o exclusive & time conversion).
Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port 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(seq As Pointer, events As Short) As Integer

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

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


Public Sub Main()

 Dim err, id As Integer
 Dim inport As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 0)
 If err < 0 Then error.RAISE("Errore nell'apertura di ALSA !")

 snd_seq_set_client_name(handle, "Esempio Client")
 id = snd_seq_client_id(handle)
 Print "Alsa Client-ID = "; id

' Per poter leggere i dati ricevuti dalla propria porta, essa viene posta con capacità "Write", ossia "scrivibile" da parte dell'Altro dispositivo Client che invia appunto i dati:
 inport = snd_seq_create_simple_port(handle, "Porta applicativo", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)
 Print "Numero della porta input dell'applicazione = "; inport
 If inport < 0 Then error.Raise("Errore nella creazione della porta !")

' Si pongono: 14 (id del sistema audio interno ALSA) e 0 (num. della sua porta) per connettere il nostro Client ad ALSA e "ricevere" dati direttamente da essa:
 err = snd_seq_connect_from(handle, inport, 14, 0)
 If err < 0 Then error.Raise("Error subscribe input device")


' OSSERVA IL FILE-DESCRIPTOR

 Dim npfds, i, numfd As Integer
 Dim pfds As Pointer
 Dim st As Stream

 npfds = snd_seq_poll_descriptors_count(handle, POLLIN)

' Alloca la memoria necessaria, tenendo conto del numero di byte occupati dalla "struct pollfd":
 pfds = Alloc(SizeOf(gb.Pointer), npfds)

 snd_seq_poll_descriptors(handle, pfds, npfds, POLLIN)

 st = Memory pfds For Read

 For i = 0 To npfds - 1
' Va a leggere dal 1° byte:
   Seek #st, i * SizeOf(gb.Pointer)
   Read #st, numfd

' Solo per vedere in console quale numero di fd è stato passato da ALSA:
   Print "numfd = "; numfd

' La seguente riga di comando è quella essenziale con il "trucco" per "osservare" il "file descriptor" passato da ALSA: il programma così resta in attesa di dati provenienti dal file descriptor per poter scatenare la sub-routine "File_Read()":
   fd = Open "." & CStr(numfd) For Read Watch
 Next

 Free(pfds)

End


Public Sub File_Read() ' Routine dove saranno letti e raccolti i "veri" dati relativi agli Eventi Midi di ALSA

 Dim i As Integer
 Dim pEventi As Pointer
 Dim midi As Snd_seq_event

 Do

' Questa è la funzione di ALSA che restituirà i "veri" dati degli Eventi Midi nella variabile "pEventi":
   i = snd_seq_event_input(handle, VarPtr(pEventi))
   If i < 0 Then Break

   midi = pEventi
   Select Case midi.type
     Case SND_SEQ_EVENT_NOTEON
       Print "Evento 'NoteOn': "; midi.channel, midi.note, midi.velocity
     Case SND_SEQ_EVENT_NOTEOFF
       Print "Evento 'NoteOff': "; midi.channel, midi.note, midi.velocity
     Case SND_SEQ_EVENT_CONTROLLER
       Print "Evento 'Control Change': "; midi.channel, midi.param, midi.value
     Case SND_SEQ_EVENT_PGMCHANGE
       Print "Evento 'Program Change': "; midi.channel, midi.value
   End Select

 Loop

End


Uso contemporaneo di due o più dispositivi Midi esterni

L'uso dei "file descriptor" ci permette, a differenza del file-device, la fruizione più ampia delle facoltà del sistema ALSA, come ad esempio poter utilizzare contemporaneamente due o più dispositivi Midi esterni.
Per fare ciò, bisognerà effettuare delle integrazioni alla routine attinente alle connessioni del nostro applicativo:

' int snd_seq_connect_from(snd_seq_t *seq, int myport, int src_client, int src_port)
' Simple subscription.
Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

' int snd_seq_connect_to(snd_seq_t *seq, int myport, int src_client, int src_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

Public Sub setdevice(client As Integer, port As Integer)

 Dim err As Integer
 
 dclient = client
 dport = port

' Ci connettiamo ad ALSA per inviargli i dati Midi ricevuti:
 err = snd_seq_connect_to(handle, outport, 14, dport)
 printerr("Subscribe outport", err)

' Connettiamo un primo dispositivo Midi esterno al nostro applicativo per ricevere i dati Midi
 ' (ipotizziamo che il suo numero di Id-Client sia 24):
 err = snd_seq_connect_from(handle, inport, 24, 0)
 printerr("Subscribe inport", err)
 
' Inseriamo una seconda chiamata alla funzione esterna di ALSA per utilizzare contemporaneamente un secondo dispositivo Midi esterno (ipotizziamo che il suo numero di Id-Client sia 28):
 err = snd_seq_connect_from(handle, inport, 28, 0)
 printerr("Subscribe inport", err)

 If err < 0 Then error.Raise("Error subscribe output device")

End


Note

[1] Un File Descriptor è un handle di un file.

[2] ALSA non ritorna semplici file descriptor, ma multipli "struct pollfd". E' quindi indispensabile sapere quanti byte occupa una struttura come quella, e quindi quanta memoria è richiesta in totale.

[3] I programmi in C, sopra citati, prevedono, in vero anche un ciclo infinito ed una chiamata alle funzioni "snd_seq_nonblock()" di ALSA e "poll()" del sistema, ma nel nostro applicativo in Gambas, come vedremo, questi due elementi non saranno necessari.

[4] Così si è emulata la capacità di personalizzare il message loop, consentendo al programma di far monitorare dal sistema anche file arbitrari (nel nostro caso i file descriptor passati da ALSA) da noi desiderati, ossia anche gli eventi ai quali noi siamo appunto interessati.

[5] Oltre alla visualizzazione nella console di Gambas, è possibile vedere il numero del file descriptor, usato da ALSA, lanciando nel terminale - dopo aver trovato il PID del programma - i seguenti comandi:

  • lsof -p Pid_del_programma: si potrà notare che il numero del file descriptor, anche confrontandolo con quello mostrato dalla console di Gambas, è quello che nella lunga lista ha come "NAME" il percorso indicato: /dev/snd/seq;
  • strace -p Pid_del_programma: strace è un comando per vedere le chiamate di sistema effettuate da un processo qualunque. La prima riga mostra hee la chiamata di una particolare funzione viene invocata su un determinato file descriptor (es.: fd=3), e ritorna con il valore n (cioé ci sono num. n file descriptor pronti per essere letti. Nel nostro esempio questo file descriptor è il numero 3 ed è pronto per essere letto).

[6] Tutto il meccanismo si basa sul fatto di poter dire al sistema operativo: "quando ci sono dati da leggere, avvertimi". Il tutto si traduce, nel programma, che l'evento "File_Read()" viene scatenato. Il meccanismo consente a un programma di poter "dormire", perciò senza impegnare la CPU, per tutto il tempo che non ci sono dati disponibili. Se si lancia un "ps ax" in console, sarà possibile infatti notare che ci sono decine di processi che girano, anche se la CPU è scarica. Tutti quei programmi stanno aspettando qualcosa... ed è solo grazie a questo meccanismo che possono girare tutti insieme senza sovraccaricare la CPU.
Sempre per il risparmio di CPU, e questo è il punto centrale della questione, il sistema operativo si limita ad avvertire il programma -una volta sola- che qualche cosa è pronto da leggere. A questo punto il programma si sveglia, va a leggere ciò che c'è da leggere, e torna a dormire. Naturalmente, può passare un certo tempo tra l'avvertimento del sistema e la reazione (lettura del file) del programma (il programma potrebbe essere impegnato a fare qualche cosa d'altro). Se durante questo tempo arrivano altri dati, il sistema non manda ulteriori avvertimenti, cioè non verrà generato un secondo evento, e pertanto quei dati questi rischiano di accatastarsi uno sull'altro. Quindi, quando l'evento "File_Read()" si scatena, non si è in grado di sapere quanti eventi (o quanti byte) sono pronti da leggere - si sa solo che c'è "qualche cosa". Quindi bisogna leggere tutto quello che c'è, tutto ciò che è disponibile, non un solo dato per volta; e bisogna provare a leggere finché ce n'è. Se non si fa così, è probabile che alcuni dati rimangano indietro.
Immaginiamo, per esempio, di formare un accordo di tre note: sicuramente uno dei tasti verrà premuto prima degli altri. Il primo dei tasti causerà il passaggio del file descriptor allo stato "pronto", ma se il programma non è velocissimo a leggere quel tasto, può darsi che nel frattempo venga accodato un altro tasto. A questo punto il programma avrà in sospeso un solo evento gambas, non due - quindi con quell'unico evento occorre leggere due tasti.
Le istruzioni di lettura vanno dunque ripetute, comportando così l'utilizzo di un ciclo: "Do...Loop".
Dal ciclo si uscirà soltanto quando non c'è nulla da leggere dalla funzione esterna "snd_seq_event_input()".