Differenze tra le versioni di "ALSA e Gambas - Subsistema Seq: introduzione"

Da Gambas-it.org - Wikipedia.
 
(17 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
====Preambolo====
+
===Preambolo===
 
Nella precedente [[Cosa_è_A.L.S.A.|sezione]] abbiamo visto in linea teorica la struttura generale di ALSA ed il suo rapporto con i Client, ossia con i programmi-utente che interagiscono con esso.
 
Nella precedente [[Cosa_è_A.L.S.A.|sezione]] abbiamo visto in linea teorica la struttura generale di ALSA ed il suo rapporto con i Client, ossia con i programmi-utente che interagiscono con esso.
  
<p>Nella presente sezione, invece, affronteremo praticamente l'argomento della programmazione di ALSA con Gambas.</p>
+
Nella presente sezione, invece, affronteremo praticamente l'argomento della programmazione di ALSA con Gambas.
  
<p>Metodologicamente intendiamo mostrare il rapporto fra ALSA e Gambas attraverso la costruzione di un semplice programma capace di inviare semplici dati Midi, e spiegandone adeguatamente ogni passaggio fondamentale della sua codifica.</p>
+
Metodologicamente intendiamo mostrare il rapporto fra ALSA e Gambas spiegandone possibilmente ogni passaggio fondamentale.
  
Innanzitutto imposteremo il nostro progetto assegnandogli una classe principale, ove inseriremo i comandi essenziali, ed una classe speciale ove scriveremo tutte le funzioni specifiche di ALSA.
+
Per poter utilizzare le funzioni di ALSA è necessario usare la libreria delle API di ALSA (attualmente: ''/usr/lib/x86_64-linux-gnu/libasound.so.2.0.0'') specificandola nel codice del progetto Gambas in maniera adeguata con l'istruzione "[http://gambaswiki.org/wiki/lang/library?l=it Library ]": <SUP>&#091;[[#Note|nota 1]]&#093;</sup>
 +
Library "'''libasound:2.0.0'''"
 +
Tale libreria è propriamente un'interfaccia ai drivers di ALSA <SUP>&#091;[[#Note|nota 2]]&#093;</sup>.
  
 +
==Richiamare le funzioni di ALSA==
 +
Tutte le funzioni di ALSA <SUP>&#091;[[#Note|nota 3]]&#093;</sup>, che si rendono a noi necessarie, presenti nella libreria delle API di ALSA sono esterne al sistema Gambas, e devono pertanto essere debitamente "richiamate". Per mezzo di tale chiamata esse vengono rese disponibili ed effettivamente utilizzabili nella nostra programmazione. Praticamente la chiamata avviene con una dichiarazione di una funzione esterna di ALSA mediante anticipata dalla parola-chiave "[[Extern:_richiamare_funzioni_esterne_a_Gambas|Extern]]" <SUP>&#091;[[#Note|nota 4]]&#093;</sup>.
  
Nella classe principale - che chiameremo: ''FMain.class'' - scriveremo innanzitutto le informazioni per creare la classe secondaria, che chiameremo: ''CAlsa.class'' .
+
Le funzioni proprie di ALSA sono degli ''handle'' <SUP>&#091;[[#Note|nota 5]]&#093;</sup> che ritornano sempre un codice di errore di tipo "Intero". Un valore di ritorno pari a zero significa che l'uso della funzione esterna ha avuto successo.
<BR>Scriveremo la funzione che chiama la routine analoga in ''CAlsa.class'', contenente il nome del nostro client che sarà attribuito effettivamente con una specifica funzione di ALSA che richiameremo in ''CAlsa.class'' .
 
 
 
 
 
Cominciamo poi a strutturare la classe che richiama le funzioni di ALSA: ''CALsa.class'' <SUP>&#091;[[#Note|Nota 1]]&#093;</sup>.
 
Per poter utilizzare le funzioni di ALSA è necessario usare la libreria delle API di ALSA (attualmente: ''/usr/lib/x86_64-linux-gnu/libasound.so.2.0.0'') specificandola nel codice del progetto Gambas in maniera adeguata con l'istruzione "''Library'' ":
 
Library "''libasound:2''"
 
Tale libreria è propriamente un'interfaccia ai drivers di ALSA <SUP>&#091;[[#Note|Nota 2]]&#093;</sup>.
 
 
 
 
 
Pertanto, il nostro codice comincerà ad assumere queste linee iniziali (distingueremo il codice della classe principale, e quello della classe particolare delle funzioni di ALSA):
 
 
 
In '''FMain.class''':
 
Public alsa as CAlsa
 
 
 
'''Public''' SUB Form_Open()
 
 
 
<FONT Color=gray>' ''Crea un Oggetto della Classe "CAlsa":''</font>
 
  alsa = New CAlsa as "alsa"
 
 
 
<FONT Color=gray>' ''Invoca la prima procedura della Classe "CAlsa":''</font>
 
  alsa.alsa_open("Progetto sequencer in Gambas")
 
 
 
'''End'''
 
 
 
 
 
In '''CAlsa.class''':
 
Library "libasound:2"
 
 
 
===Richiamare le funzioni di ALSA===
 
Tutte le funzioni di ALSA <SUP>&#091;[[#Note|Nota 3]]&#093;</sup>, che si rendono a noi necessarie, presenti nella libreria delle API di ALSA sono esterne al sistema Gambas, e devono pertanto essere debitamente "richiamate". Per mezzo di tale chiamata esse vengono rese disponibili ed effettivamente utilizzabili nella nostra programmazione. Praticamente la chiamata avviene con una dichiarazione di una normale subroutine di Gambas mediante il termine "'''Extern'''" <SUP>&#091;[[#Note|Nota 4]]&#093;</sup><SUP>&#091;[[#Note|Nota 5]]&#093;</sup>.
 
<p>Le funzioni proprie di ALSA sono degli ''handle'' <SUP>&#091;[[#Note|Nota 6]]&#093;</sup> che ritornano sempre un codice di errore: " ''error = snd_seq_xxx(yyy, ....)'' ", laddove la variabile "error" è un integer. Un valore di ritorno pari a zero significa che l'uso della funzione esterna ha avuto successo.</p>
 
  
 
=Il Client e le sue porte=
 
=Il Client e le sue porte=
  
 
====Linee generali====
 
====Linee generali====
Nella sezione precedente abbiamo detto che il dispositivo ALSA svolge sostanzialmente due compiti: quello di ''scheduling'', pianificare l'esecuzione di processi, ossia pianificare secondo una precisa sequenza, come in un elenco, l'invio degli eventi e quello di ''dispatching'', ossia di indirizzarli all'esatta destinazione nel momento giusto. La fase della gestione reale degli eventi è lasciata ai programmi applicativi-utente (come i sequencer), i quali, in qualità di ''Client'' <SUP>&#091;[[#Note|Nota 6 bis]]&#093;</sup>, comunicano tali dati con il dispositivo ALSA (più precisamente con uno dei suoi sub-sistemi; per il Midi: ''seq'') che invece fa da ''Server''.
+
Nella sezione precedente abbiamo detto che il dispositivo ALSA svolge sostanzialmente due compiti: quello di ''scheduling'', pianificare l'esecuzione di processi, ossia pianificare secondo una precisa sequenza, come in un elenco, l'invio degli eventi e quello di ''dispatching'', ossia di indirizzarli all'esatta destinazione nel momento giusto. La fase della gestione reale degli eventi è lasciata ai programmi applicativi-utente (come i sequencer), i quali, in qualità di ''Client'' <SUP>&#091;[[#Note|nota 6]]&#093;</sup>, comunicano tali dati con il dispositivo ALSA (più precisamente con uno dei suoi sub-sistemi; per il Midi: ''seq'') che invece fa da ''Server''.
<BR>Abbiamo anche detto che il nostro applicativo deve possedere le funzioni di un Client. Più precisamente una parte di esso svolgerà le funzioni di Client. Si rende pertanto necessario creare appunto un ''Client'', capace di comunicare con gli altri dispositivi esterni e certamente con il dispositivo ALSA. Per creare il nostro Client dobbiamo utilizzare particolari funzioni di ALSA, che dovranno essere preventivamente "richiamate"; attraverso con una particolare dichiarazione contenente l'istruzione "'''Extern'''".
+
<BR>Abbiamo anche detto che il nostro applicativo deve possedere le funzioni di un Client. Più precisamente una parte di esso svolgerà le funzioni di Client. Si rende pertanto necessario creare appunto un ''Client'', capace di comunicare con gli altri dispositivi esterni e soprattutto con il dispositivo ALSA. Per creare il nostro Client dobbiamo utilizzare la funzione esterna di ALSA:
 +
[https://www.alsa-project.org/alsa-doc/alsa-lib/group___sequencer.html#ga95462dc59c0319e186cda713ecfb4ed3 int snd_seq_open() ]
 +
opportunamente dichiarata mediante l'istruzione '''[[Extern:_richiamare_funzioni_esterne_a_Gambas|Extern]]'''.
 +
 
 
<p>Il nostro progetto seguirà più o meno la struttura base di ogni applicazione in ALSA:</p>
 
<p>Il nostro progetto seguirà più o meno la struttura base di ogni applicazione in ALSA:</p>
<p>1) aprire il subsistema specifico (in questo caso "seq" del dispositivo ALSA mediante l'apposita funzione;</p>
+
<p>1) aprire il subsistema specifico (in questo caso "seq" del dispositivo ALSA mediante la predetta apposita funzione "snd_seq_open()");</p>
 
<p>2) impostare i parametri di detta funzione;</p>
 
<p>2) impostare i parametri di detta funzione;</p>
 
<p>3) ricevere e/o inviare dati Midi al dispositivo;</p>
 
<p>3) ricevere e/o inviare dati Midi al dispositivo;</p>
<p>4) chiudere il dispostivo.</p>
+
<p>4) chiudere il dispostivo (usando l'apposita funzione "snd_seq_close()").</p>
  
 
=Scrittura ed invio degli eventi Midi=
 
=Scrittura ed invio degli eventi Midi=
Riga 68: Riga 43:
 
Riguardo al punto 1, ALSA vuole una zona di memoria contenente, in posizioni specifiche, i valori adatti per la preparazione e per la realizzazione dell'evento Midi. Nello specifico, ALSA vuole una zona di memoria contigua, dove nel primo byte c'è il tipo di evento, nel secondo byte i flag, dal terzo al sesto byte il timestamp e via dicendo.  
 
Riguardo al punto 1, ALSA vuole una zona di memoria contenente, in posizioni specifiche, i valori adatti per la preparazione e per la realizzazione dell'evento Midi. Nello specifico, ALSA vuole una zona di memoria contigua, dove nel primo byte c'è il tipo di evento, nel secondo byte i flag, dal terzo al sesto byte il timestamp e via dicendo.  
  
Riguardo al punto 2, va definita una zona di memoria. Per preparare e modificare questa zona di memoria è possibile usare una delle seguenti risorse:
+
Riguardo al punto 2, va definita una zona di memoria. Per preparare e gestire questa zona di memoria è possibile usare una delle seguenti risorse:
* [[Alsa_e_Gambas:_Uso_di_un'area_di_memoria_allocata|'''''Area di memoria allocata''''']];
+
* [[Alsa_e_Gambas:_Invio_dati_con_l'uso_delle_Strutture|'''''Struttura''''']];
* [[Alsa_e_Gambas:_Uso_delle_Strutture|'''''Struttura''''']];
+
* [[Alsa_e_Gambas:_Invio_dati_con_l'uso_di_un'area_di_memoria_allocata|'''''Area di memoria allocata''''']];
* [[Alsa_e_Gambas:_Uso_di_una_Classe_specifica|'''''Classe specifica''''']].
+
* [[Alsa_e_Gambas:_Invio_dati_con_l'uso_di_una_Classe_specifica|'''''Classe specifica''''']].
  
 
Riguardo infine al punto 3, l'organizzazione generale significa: ogni volta che viene inviato un evento ad ALSA, è necessario passargli una zona di memoria che sarà sempre la stessa, ri-scrivendola ogni volta, evitando (anche se potrebbe essere un’alternativa) di crearla al momento. Per preparare un evento, inoltre, visto che ci sono membri comuni, si userà una sola routine comune a tutti gli eventi.
 
Riguardo infine al punto 3, l'organizzazione generale significa: ogni volta che viene inviato un evento ad ALSA, è necessario passargli una zona di memoria che sarà sempre la stessa, ri-scrivendola ogni volta, evitando (anche se potrebbe essere un’alternativa) di crearla al momento. Per preparare un evento, inoltre, visto che ci sono membri comuni, si userà una sola routine comune a tutti gli eventi.
Riga 77: Riga 52:
  
 
==La struttura virtuale degli eventi MIDI in ALSA==
 
==La struttura virtuale degli eventi MIDI in ALSA==
Gli eventi Midi gestiti dal subsistema ''seq'' di ALSA sono temporizzati mediante dei ''timestamp'', e sono organizzati secondo un'apposita struttura precisa di dati: <SUP>&#091;[[#Note|Nota 7]]&#093;</sup>
+
Gli eventi Midi gestiti dal subsistema ''seq'' di ALSA sono temporizzati mediante dei ''timestamp'', e sono organizzati secondo un'apposita struttura precisa di dati: <SUP>&#091;[[#Note|nota 7]]&#093;</sup>
  
 
<TABLE>
 
<TABLE>
 
       <TR>
 
       <TR>
         <TD>'''Type'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
+
         <TD>'''type'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''Flags'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
+
         <TD>'''flags'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''Tag'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
+
         <TD>'''tag'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''Queue'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
+
         <TD>'''queue'''</td><TD> </td><TD> un singolo byte</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''Time''',</td><TD> composto da:</td><TD> </td>
+
         <TD>'''time''',</td><TD> composto da:</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Tick''' o '''Real-Time Tv_sec'''</td><TD> un integer</td>
+
         <TD>          </td><TD>'''tick''' o '''real-time tv_sec'''</td><TD> un integer</td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Real-Time Tv_nsec'''</td><TD> un integer</td>
+
         <TD>          </td><TD>'''rReal-time tv_nsec'''</td><TD> un integer</td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''Source''',</td><TD> composto da:</td><TD> </td>
+
         <TD>'''source''',</td><TD> composto da:</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Id_client'''</td><TD> un singolo byte</td>
+
         <TD>          </td><TD>'''client'''</td><TD> un singolo byte</td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Porta_client'''</td><TD> un singolo byte</td>
+
         <TD>          </td><TD>'''port'''</td><TD> un singolo byte</td>
 
       </tr>
 
       </tr>
 
     <TR>
 
     <TR>
         <TD>'''Dest''',</td><TD> composto da:</td><TD> </td>
+
         <TD>'''dest''',</td><TD> composto da:</td><TD> </td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Client_dest'''</td><TD> un singolo byte</td>
+
         <TD>          </td><TD>'''client'''</td><TD> un singolo byte</td>
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>          </td><TD>'''Porta_dest'''</td><TD> un singolo byte</td>
+
         <TD>          </td><TD>'''port'''</td><TD> un singolo byte</td>
 
       </tr>
 
       </tr>
 
</table>
 
</table>
 +
Da considerare che i membri ''time'', ''source'' e ''dest'' sono dati <span style= "text-decoration:underline">''strutturati''</span>, cioé contengono al loro interno altri membri. Per questi dati il membro è uno solo, ma per ragioni tecniche viene spezzato in due istruzioni. Quindi un evento in ALSA contiene 4 dati semplici + 3 strutturati "doppi" (totale sette), ma i tre strutturati vengono scritti, per ragioni tecniche, con un passaggio doppio, e quindi il totale delle scritture diventa 10.
  
Da considerare che i membri ''Time'', ''Source'' e ''Dest'' sono dati <span style= "text-decoration:underline">''strutturati''</span>, cioé contengono al loro interno altri membri. Per questi dati il membro è uno solo, ma per ragioni tecniche viene spezzato in due istruzioni. Quindi un evento in ALSA contiene 4 dati semplici + 3 strutturati "doppi" (totale sette), ma i tre strutturati vengono scritti, per ragioni tecniche, con un passaggio doppio, e quindi il totale delle scritture diventa 10.
+
In particolare riguardo ai membri della Struttura ALSA ''[https://www.alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__event__t.html snd_seq_event_t ]'' degli ''Eventi Midi'': <SUP>&#091;[[#Note|nota 8]]&#093;</sup>:
<p>In particolare riguardo ai membri della Struttura ALSA degli Eventi Midi: <SUP>&#091;[[#Note|Nota 7 bis]]&#093;</sup>:
+
* '''type''' contiene il tipo di evento;
* '''Type''' contiene il tipo di evento;
+
* '''flags''' può contenere il ''Timestamp mode'' (''Real-Time'' o ''Tick-Time'', ''Assoluto'' o ''Relativo''), il ''Data storage type'' ed il ''scheduling priority'';
* '''Flags''' può contenere il ''Timestamp mode'' (''Real-Time'' o ''Tick-Time'', ''Assoluto'' o ''Relativo''), il ''Data storage type'' ed il ''scheduling priority'';
+
* '''tag''' può contenere un marcatore arbitrario;
* '''Tag''' può contenere un marcatore arbitrario;
+
* '''queue''' contiene il valore per lo ''Scheduling queue'';
* '''Queue''' contiene il valore per lo ''Scheduling queue'';
+
* '''time''' contiene al suo interno due mebri entrambi di un integer: il primo ed il secondo per il ''timestamp'' specificato in ''Real time'', ossia in formato "Orario" e più speficamente il primo in secondi e l'altro in nanosecondi; oppure solo il primo per il ''timestamp'' specificato in ''Midi Tick'' (in tal caso il secondo membro non serve e può essere posto a zero);
* '''Time''' contiene al suo interno due mebri entrambi di un integer: il primo ed il secondo per il ''timestamp'' specificato in ''Real time''; oppure solo il primo per il ''timestamp'' specificato in ''Midi Tick'', in tal caso il secondo membro non serve e può essere posto a zero;
+
* '''source''' contiene l'indirizzo del sorgente dell'<I>Evento Midi</i>. E' formato da due membri: il primo l'identificativo numerico del Client sorgente dei dati, il secondo il numero della sua porta;
* '''Source''' contiene l'indirizzo del sorgente dell'evento. E' formato da due membri: il primo l'identificativo numerico, il secondo il numero della porta;
+
* '''dest''' contiene l'indirizzo del destinatario dell'<I>Evento Midi</i>. E' formato da due membri: il primo l'identificativo numerico del Client destinatario, il secondo il numero della sua porta.
* '''Dest''' contiene l'indirizzo del destinatario dell'evento. E' formato da due membri: il primo l'identificativo numerico, il secondo il numero della porta.</p>
 
 
 
Complessivamente i dati generali, comuni a tutti i messaggi Midi, occupano ben 16 byte della memoria allocata:
 
 
 
  
 +
Complessivamente questi primi dati generali, qui sopra considerati, occupano ben 16 byte della memoria allocata:
 
<TABLE>
 
<TABLE>
 
       <TR>
 
       <TR>
 
         <TD><Font color="blue">'''Byte'''</font>:</td><TD>0</td><TD></td><TD>1</td><TD></td><TD>2</td><TD></td><TD>3</td><TD></td><TD>4 5 6 7</td><TD></td><TD>8 9 10  11</td><TD></td><TD>12</td><TD></td><TD>13</td><TD></td><TD>14</td><TD></td><TD>15</td></tr>
 
         <TD><Font color="blue">'''Byte'''</font>:</td><TD>0</td><TD></td><TD>1</td><TD></td><TD>2</td><TD></td><TD>3</td><TD></td><TD>4 5 6 7</td><TD></td><TD>8 9 10  11</td><TD></td><TD>12</td><TD></td><TD>13</td><TD></td><TD>14</td><TD></td><TD>15</td></tr>
 
       <TR>
 
       <TR>
           <TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴———</td><TD></td><TD>┴————</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td></tr>
+
           <TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD>┠ ─ ─ ─</td><TD></td><TD>┠ ─ <Font color="white">─</font>─ <Font color="white">─</font>─</td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td></tr>
 
       <TR>
 
       <TR>
           <TD><Font color="red">'''Dati'''</font>:</td><TD>'''T'''ype</td><TD>│</td><TD>'''F'''lags</td><TD>│</td><TD>'''T'''ag</td><TD>│</td><TD>'''Q'''ueue</td><TD>│</td><TD>'''T'''ick / '''R'''eal Tv_sec</td><TD>│</td><TD>0 / '''R'''eal Tv_nsec</td><TD>│</td><TD>'''I'''d_client</td><TD>│</td><TD>'''P'''orta_client</td><TD>│</td><TD>'''C'''lient_dest</td><TD>│</td><TD>'''P'''orta_dest</td></tr>
+
           <TD><Font color="red">'''Dati'''</font>:</td><TD>'''T'''ype</td><TD>│</td><TD>'''F'''lags</td><TD>│</td><TD>'''T'''ag</td><TD>│</td><TD>'''Q'''ueue</td><TD>│</td><TD>'''T'''ick_time / '''R'''eal_time Tv_sec</td><TD>│</td><TD>0 / '''R'''eal_time Tv_nsec</td><TD>│</td><TD>'''S'''ource_client</td><TD>│</td><TD>'''P'''ort_client</td><TD>│</td><TD>'''D'''est_client</td><TD>│</td><TD>'''D'''dest_port</td></tr>
  
 
</table>
 
</table>
Riga 148: Riga 121:
  
 
====Il Timestamp====
 
====Il Timestamp====
Il ''Timestamp'' di un evento può essere specificato in: <SUP>&#091;[[#Note|Nota 7 ter]]&#093;</sup>
+
Il ''Timestamp'' di un evento può essere specificato in: <SUP>&#091;[[#Note|nota 9]]&#093;</sup>
* ''song ticks'', corrisponde ai ''tick'' del Midi.
+
* ''tick_time'', corrisponde ai ''tick'' del Midi.
* ''real time'', corrisponde all'orologio, e la risoluzione è in secondi e in nanosecondi;
+
* ''real_time'', corrisponde al formato orario, e la sua risoluzione è in secondi e in nanosecondi;
 
<p>Il ''Timestamp'' può inoltre essere:
 
<p>Il ''Timestamp'' può inoltre essere:
 
* ''assoluto'', quando il tempo è determinato dal momento in cui la coda di eventi ha inizio;
 
* ''assoluto'', quando il tempo è determinato dal momento in cui la coda di eventi ha inizio;
Riga 158: Riga 131:
  
 
===Gli eventi ed i messaggi MIDI in particolare===
 
===Gli eventi ed i messaggi MIDI in particolare===
 
 
Come è noto, i messaggi Midi si dividono in:
 
Come è noto, i messaggi Midi si dividono in:
 
* messaggi di '''Stato''', che identificano il numero del Canale ed il tipo di evento Midi trasmesso;
 
* messaggi di '''Stato''', che identificano il numero del Canale ed il tipo di evento Midi trasmesso;
Riga 203: Riga 175:
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
         <TD>'''velocity''' / '''unused'''</td><TD> </td><TD> un singolo byte <SUP>&#091;[[#Note|Nota 8]]&#093;</sup></td><TD> </td>
+
         <TD>'''velocity''' / '''unused'''</td><TD> </td><TD> un singolo byte
 
       </tr>
 
       </tr>
 
       <TR>
 
       <TR>
Riga 223: Riga 195:
 
         <TD><Font color="blue">'''Byte'''</font>:</td><TD></td><TD>16</td><TD></td><TD>17</td><TD></td><TD>18</td><TD></td><TD>19</td><TD></td><TD>20 21 22 23</td><TD></td><TD>24 25 26 27</td></tr>
 
         <TD><Font color="blue">'''Byte'''</font>:</td><TD></td><TD>16</td><TD></td><TD>17</td><TD></td><TD>18</td><TD></td><TD>19</td><TD></td><TD>20 21 22 23</td><TD></td><TD>24 25 26 27</td></tr>
 
       <TR>
 
       <TR>
         <TD></td><TD>(da 0 a 15 i dati comuni)...</td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—</td><TD></td><TD>┴—————</td><TD></td><TD>┴—————</td></tr>
+
         <TD></td><TD>(da 0 a 15 i dati comuni)...</td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD></td><TD>┠<Font color="white">──</font>─<Font color="white">──</font>─ <Font color="white">─</font>─</td><TD></td><TD>┠<Font color="white">──</font>─<Font color="white">──</font>─ <Font color="white">─</font>─</td></tr>
 
       <TR>
 
       <TR>
 
           <TD><Font color="red">'''Dati'''</font>:</td><TD></td><TD>'''c'''hannel</td><TD>│</td><TD>'''n'''ote</td><TD>│</td><TD>'''v'''elocity</td><TD>|</td><TD>'''o'''ff_velocity</td><TD>│</td><TD>'''d'''uration/'''p'''aram</td><TD>│</td><TD>'''v'''alue</td></tr>
 
           <TD><Font color="red">'''Dati'''</font>:</td><TD></td><TD>'''c'''hannel</td><TD>│</td><TD>'''n'''ote</td><TD>│</td><TD>'''v'''elocity</td><TD>|</td><TD>'''o'''ff_velocity</td><TD>│</td><TD>'''d'''uration/'''p'''aram</td><TD>│</td><TD>'''v'''alue</td></tr>
Riga 229: Riga 201:
 
</table>
 
</table>
  
<P>In ALSA il tipo di evento Midi viene distinto mediante una definizione identificativa, rappresentata da una costante numerica, che dovremo scrivere come costanti all'interno di CAlsa.class, ed assegnare al membro "''type''" della struttura sopra descritta dell'evento Midi:</p>
+
<P>In ALSA il tipo di evento Midi viene distinto mediante una definizione identificativa, rappresentata da una enumerazione, e da assegnare al membro "''type'' " della struttura sopra descritta dell'evento Midi:</p>
 
+
  Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
  (Const SND_SEQ_EVENT_NOTE As Byte = 5)
+
              SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND
Const SND_SEQ_EVENT_NOTEON As Byte = 6
 
Const SND_SEQ_EVENT_NOTEOFF As Byte = 7
 
Const SND_SEQ_EVENT_KEYPRESS As Byte = 8
 
Const SND_SEQ_EVENT_CONTROLLER As Byte = 10
 
Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11
 
Const SND_SEQ_EVENT_CHANPRESS As Byte = 12
 
Const SND_SEQ_EVENT_PITCHBEND As Byte = 13
 
 
Per vedere tramite codice Gambas il numero identificativo costante di alcuni più importanti eventi Midi, come gestiti ALSA, potremo adottare un breve applicativo, composto oltre che dal progetto principale Gambas, anche da una libreria condivisa .so, da noi appositamente scritta all'interno del medesimo progetto Gambas.
 
Per vedere tramite codice Gambas il numero identificativo costante di alcuni più importanti eventi Midi, come gestiti ALSA, potremo adottare un breve applicativo, composto oltre che dal progetto principale Gambas, anche da una libreria condivisa .so, da noi appositamente scritta all'interno del medesimo progetto Gambas.
 
  Library "/tmp/libalsa"
 
  Library "/tmp/libalsa"
Riga 247: Riga 212:
 
   
 
   
 
   
 
   
  '''Public''' Sub Main()
+
  Public Sub Main()
 
    
 
    
 
   Creaso()
 
   Creaso()
Riga 253: Riga 218:
 
   Eventi_ALSA()
 
   Eventi_ALSA()
 
    
 
    
  '''End'''
+
  End
 
   
 
   
 
   
 
   
  '''Private''' Procedure Creaso()
+
  Private Procedure Creaso()
 
    
 
    
 
   File.Save("/tmp/libalsa.c", "#include <stdio.h>\n#include <alsa/seq_event.h>\n\n" &
 
   File.Save("/tmp/libalsa.c", "#include <stdio.h>\n#include <alsa/seq_event.h>\n\n" &
Riga 277: Riga 242:
 
   Shell "gcc -o /tmp/libalsa.so /tmp/libalsa.c -shared -lasound -fPIC" Wait
 
   Shell "gcc -o /tmp/libalsa.so /tmp/libalsa.c -shared -lasound -fPIC" Wait
 
    
 
    
  '''End'''
+
  End
 
+
Nel nostro progetto iniziale inseriremo per ciascun evento Midi un ''Button'' per attivare dalla classe principale del nostro progetto ogni evento mediante una specifica routine da scrivere invece all'interno della classe secondaria (CAlsa.class).
 
 
Nel nostro progetto iniziale inseriremo per ciascun evento Midi un ''button'' per attivare dalla classe principale del nostro progetto ogni evento mediante una specifica routine da scrivere invece all'interno della classe secondaria (CAlsa.class).
 
  
 
====Dimensione finale della zona di memoria riservata====
 
====Dimensione finale della zona di memoria riservata====
 
 
La imprescindibile zona di memoria riservata sarà dunque impegnata dalle due parti:
 
La imprescindibile zona di memoria riservata sarà dunque impegnata dalle due parti:
* la prima, quella dei dati comuni a tutti gli eventi Midi (<Font color="#00AA00">'''16'''</font> byte complessivamente: dal byte n° 0 al byte n° 15);
+
* la prima (un po' più generica) (<Font color=darkgreen>'''16'''</font> byte complessivamente: dal byte n° 0 al byte n° 15);
* la seconda, quella dei dati specifici del particolare singolo evento Midi (<Font color="#00AFCC">'''12'''</font> byte complessivamente: dal byte n° 16 al byte n° 27).
+
* la seconda, quella dei valori specifici del particolare singolo ''Evento Midi'' (<Font color="#00AFCC">'''12'''</font> byte complessivamente: dal byte n° 16 al byte n° 27);
 +
per un totale di '''28''' byte.
  
 
Come sappiamo, i singoli specifici eventi Midi utilizzeranno solo alcuni settori, dunque solo alcuni byte, della seconda parte della memoria allocata.
 
Come sappiamo, i singoli specifici eventi Midi utilizzeranno solo alcuni settori, dunque solo alcuni byte, della seconda parte della memoria allocata.
  
 
===Accodare gli eventi in un buffer "intermedio"===
 
===Accodare gli eventi in un buffer "intermedio"===
 +
Al termine della scrittura nella zona di memoria riservata di tutti i dati necessari per definire l'evento Midi desiderato, sarà inserita la funzione di ALSA:
 +
''[https://www.alsa-project.org/alsa-doc/alsa-lib/group___seq_event.html#ga41be1e09173957944352c50067a686ea int snd_seq_event_output_buffer(snd_seq_t * seq, snd_seq_event_t * ev)]''
 +
che pone in uscita un ''Evento Midi'' e più precisamente ''accoda'' un ''Evento Midi'' in un buffer intermedio.
 +
<BR>Tale funzione ritorna il numero (integer) di ''Eventi Midi'' rimanenti da far uscire, oppure un codice di errore se negativo.
  
Al termine della scrittura nella zona di memoria riservata di tutti i dati necessari per definire l'evento Midi desiderato, sarà inserita la funzione di ALSA, espressa in C:
+
Si potrà utilizzare anche la funzione esterna:
  ''int snd_seq_event_output(snd_seq_t * seq, snd_seq_event_t * ev)''
+
  ''[https://www.alsa-project.org/alsa-doc/alsa-lib/group___seq_event.html#ga0360e41b946c9948ad717beef7c5ab61 int snd_seq_event_output(snd_seq_t * seq, snd_seq_event_t * ev)]]''
che pone in uscita un evento e più precisamente ''accoda'' un evento in un buffer intermedio.
 
<BR>Tale funzione ritorna il numero (integer) di eventi rimanenti da far uscire, oppure un codice di errore se negativo.
 
<BR>Detta funzione di ALSA dovrà essere dichiarata con '''Extern''', in questo modo:
 
<FONT Color= #B22222>Private Extern snd_seq_event_output(handle As Pointer, ev As Pointer) As Integer</font>
 
e nella routine sarà invocata così:
 
err = snd_seq_event_output(handle, ev)        <FONT Color=gray>' ''output an event</font>
 
 
printerr("Evento Midi = ", err)
 
 
 
  
 
===Passare ad ALSA i valori presenti nella memoria allocata===
 
===Passare ad ALSA i valori presenti nella memoria allocata===
 
+
Al termine di ciascuna routine, finalizzata all'invio di ciascun ''Evento Midi'', come qui sopra appena descritto, deve essere richiamata la funzione di ALSA:
Nel nostro progetto esemplificativo iniziale al termine di ciascuna routine, finalizzata all'invio dell'evento Midi desiderato, viene prevista e richiamata la subroutine "''flush( )''", presente nella classe secondaria ''CAlsa.class'', la quale contiene la funzione di ALSA, espressa in C:
+
  ''[https://www.alsa-project.org/alsa-doc/alsa-lib/group___seq_event.html#ga64a0ed5488504ef91b5b6b92172bc0aa int snd_seq_drain_output(snd_seq_t * seq)]''
  ''int snd_seq_drain_output(snd_seq_t * seq)''
+
che ''sversa'' (invia) il contenuto della zona di memoria allocata (il predetto buffer intermedio) nel subsistema "seq" di ALSA.
che sversa il contenuto della zona di memoria allocata al sequencer ALSA. Tale funzione ritorna un integer uguale a zero, se tutti gli eventi sono stati inviati al sequencer, e quindi se il buffer intermedio è stato svuotato. Ritorna un valore negativo ritorna un errore.
+
<BR>Tale funzione ritorna un integer uguale a zero, se tutti gli eventi sono stati inviati al sequencer, e quindi se il buffer intermedio è stato svuotato. Ritorna un valore negativo ritorna un errore.
<BR>Più in particolare, questa funzione viene invocata dopo ogni evento; e questo assicura che l'evento venga processato in tempo, svuotando il buffer intermedio precedentemente riempito di dati dalla funzione: "''snd_seq_event_output'' " <SUP>&#091;[[#Note|Nota 9]]&#093;</sup> descritta nel precedente paragrafo.
+
<BR>Come già accennato, questa funzione va invocata dopo '''ogni''' ''Evento Midi'': ciò assicura che l'Evento venga processato in tempo, svuotando il buffer intermedio precedentemente riempito di dati dalla funzione: "snd_seq_event_output()" <SUP>&#091;[[#Note|nota 10]]&#093;</sup> sopra descritta.
 
 
Detta funzione "''snd_seq_drain_output'' " dovrà essere dichiarata con '''Extern''', in questo modo:
 
<FONT Color= #B22222>Private Extern snd_seq_drain_output(handle As Pointer) As Integer</font>
 
Verrà scritta così in un'apposita routine:
 
'''Public''' Sub flush()
 
 
 
  Dim err As Integer
 
 
 
  err = snd_seq_drain_output(handle)    <FONT Color=gray>' ''drain output buffer to sequencer</font>
 
  Printerr("Flush", err)
 
 
 
'''End'''
 
  
  
  
 
=Note=
 
=Note=
[1] Si suggerisce di dare un'occhiata al capitolo [[Un_contributo_sulle_funzioni_esterne_in_Gambas_a_cura_di_Doriano_B.|Interfacciare Gambas con librerie esterne]], redatta da D. Blengino, presente nell'area "Guide della Comunità", ove sarà possibile avere le informazioni basilari necessarie per la gestione delle API di ALSA. Lì, tra l'altro, sono anche presenti esempi esplicativi della funzione Extern per la gestione di ALSA con Gambas, e l'intera codifica di una classe CAlsa.Class relativa all'esempio di una Drum Machine. Si precisa che la conoscenza della funzione Extern, relativa alle dichiarazioni esterne in Gambas, nonché delle API di ALSA (richiamate appunto dalla funzione Extern), è comunque presupposto necessario ed imprescindibile per la gestione in Gambas dei dati Midi con ALSA.  
+
[1] Si suggerisce di dare un'occhiata alla pagina: [[Extern:_richiamare_funzioni_esterne_a_Gambas|Extern: richiamare funzioni esterne a Gambas]]
 +
<BR>La conoscenza dell'istruzione "Extern", relativa alle dichiarazioni esterne in Gambas, nonché delle API di ALSA (richiamate appunto dalla funzione "Extern"), è comunque presupposto necessario ed imprescindibile per la gestione in Gambas dei dati Midi con ALSA.  
  
 
[2] Una libreria, come lo stesso nome indica, contiene una raccolta di subroutine da utilizzare più volte da più programmi.
 
[2] Una libreria, come lo stesso nome indica, contiene una raccolta di subroutine da utilizzare più volte da più programmi.
Riga 334: Riga 281:
 
[4] Una dichiarazione Extern fa riferimento all'ultima istruzione LIBRARY incontrata. Se l'ultima Library non è quella che contiene la funzione successiva alla libreria medesima, detta funzione non potrà essere richiamata dal programma. Pertanto, se si devono usare più librerie, queste o si dichiarano di volta in volta prima della funzione di riferimento (ma sempre all'esterno della routine contenente detta funzione), oppure si specificano all'interno di ogni loro dichiarazione.
 
[4] Una dichiarazione Extern fa riferimento all'ultima istruzione LIBRARY incontrata. Se l'ultima Library non è quella che contiene la funzione successiva alla libreria medesima, detta funzione non potrà essere richiamata dal programma. Pertanto, se si devono usare più librerie, queste o si dichiarano di volta in volta prima della funzione di riferimento (ma sempre all'esterno della routine contenente detta funzione), oppure si specificano all'interno di ogni loro dichiarazione.
  
[5] Sull'uso di ''Extern'' vedere: [[Extern:_richiamare_funzioni_esterne_a_Gambas|Extern: richiamare funzioni esterne a Gambas]].
+
[5] Un Handle (''maniglia'') rappresenta "''una '''variabile''' associata a un oggetto complesso, che lo identifica''". Possiamo dire che in modo figurato questa ''maniglia'' è la ''protuberanza'' con la quale si interagisce con l'oggetto medesimo. In gambas, per esempio, qualsiasi oggetto (form, o altro) è un ''handle'', e per creare un oggetto/handle in Gambas, come è noto, bisogna scrivere "NEW oggetto_da_creare", in ALSA invece la funzione per creare l'handle è, come sappiamo: ''variab_int = snd_seq_etc_etc(xxx, ......) as integer''.
 
 
[6] Un Handle (''maniglia'') rappresenta "''una '''variabile''' associata a un oggetto complesso, che lo identifica''". Possiamo dire che in modo figurato questa ''maniglia'' è la ''protuberanza'' con la quale si interagisce con l'oggetto medesimo. In gambas, per esempio, qualsiasi oggetto (form, o altro) è un ''handle'', e per creare un oggetto/handle in Gambas, come è noto, bisogna scrivere "NEW oggetto_da_creare", in ALSA invece la funzione per creare l'handle è, come sappiamo: ''variab_int = snd_seq_etc_etc(xxx, ......) as integer''.
 
  
[6 bis] Sul rapporto Client/Server in ALSA: https://www.alsa-project.org/alsa-doc/alsa-lib/seq.html
+
[6] Sul rapporto Client/Server in ALSA: https://www.alsa-project.org/alsa-doc/alsa-lib/seq.html
  
 
[7] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__event__t.html
 
[7] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__event__t.html
  
[7 bis] Vedi anche D. Blengino: [[Un_contributo_sulle_funzioni_esterne_in_Gambas_a_cura_di_Doriano_B.#Definizione_degli_eventi_Midi_in_ALSA|Definizione degli eventi Midi in ALSA]].
+
[8] Vedi anche D. Blengino: [[Un_contributo_sulle_funzioni_esterne_in_Gambas_a_cura_di_Doriano_B.#Definizione_degli_eventi_Midi_in_ALSA|Definizione degli eventi Midi in ALSA]].
 
 
[7 ter] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/unionsnd__seq__timestamp__t.html
 
  
[8] Questo tipo di dato può essere definito anche '''''Short''''', qualora non si intenda utilizzare l'evento ALSA: ''SND_SEQ_EVENT_NOTE''. Si dovrà ovviamente eliminare il successivo dato ''Off_Velocity'', poiché il tipo ''Short'' occupa 2 byte.
+
[9] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/unionsnd__seq__timestamp__t.html
  
[9] Distinzione fra la funzione ''err = snd_seq_event_output(handle, ev)'' ed ''err = snd_seq_drain_output(handle)'':
+
[10] Distinzione fra la funzione ''err = snd_seq_event_output(handle, ev)'' ed ''err = snd_seq_drain_output(handle)'':
 
* ''err = snd_seq_event_output(handle, ev)'' --> accoda gli eventi in un buffer "intermedio". Quando tale buffer è pieno, viene svuotato automaticamente nel buffer-ALSA. Lo svuotamento di tale buffer intermedio può essere indotto anche se esso non è pieno.
 
* ''err = snd_seq_event_output(handle, ev)'' --> accoda gli eventi in un buffer "intermedio". Quando tale buffer è pieno, viene svuotato automaticamente nel buffer-ALSA. Lo svuotamento di tale buffer intermedio può essere indotto anche se esso non è pieno.
 
* ''err = snd_seq_drain_output(handle)'' --> svuota "a richiesta" il buffer intermedio (anche quindi se il buffer intermedio non è pieno).
 
* ''err = snd_seq_drain_output(handle)'' --> svuota "a richiesta" il buffer intermedio (anche quindi se il buffer intermedio non è pieno).
Riga 355: Riga 298:
  
 
=Riferimenti=
 
=Riferimenti=
[1] http://www.alsa-project.org/alsa-doc/alsa-lib/group___sequencer.html
+
* http://www.alsa-project.org/alsa-doc/alsa-lib/group___sequencer.html
 
+
* http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html
[2] http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html
+
* https://github.com/michaelwu/alsa-lib/blob/master/src/seq/seq.c
 
+
* http://www.alsa-project.org/alsa-doc/alsa-lib/seq__event_8h-source.html
[3] https://github.com/michaelwu/alsa-lib/blob/master/src/seq/seq.c
+
* https://fundamental-code.com/midi/
 
 
[4] http://www.alsa-project.org/alsa-doc/alsa-lib/seq__event_8h-source.html
 

Versione attuale delle 09:20, 5 dic 2023

Preambolo

Nella precedente sezione abbiamo visto in linea teorica la struttura generale di ALSA ed il suo rapporto con i Client, ossia con i programmi-utente che interagiscono con esso.

Nella presente sezione, invece, affronteremo praticamente l'argomento della programmazione di ALSA con Gambas.

Metodologicamente intendiamo mostrare il rapporto fra ALSA e Gambas spiegandone possibilmente ogni passaggio fondamentale.

Per poter utilizzare le funzioni di ALSA è necessario usare la libreria delle API di ALSA (attualmente: /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0) specificandola nel codice del progetto Gambas in maniera adeguata con l'istruzione "Library ": [nota 1]

Library "libasound:2.0.0"

Tale libreria è propriamente un'interfaccia ai drivers di ALSA [nota 2].

Richiamare le funzioni di ALSA

Tutte le funzioni di ALSA [nota 3], che si rendono a noi necessarie, presenti nella libreria delle API di ALSA sono esterne al sistema Gambas, e devono pertanto essere debitamente "richiamate". Per mezzo di tale chiamata esse vengono rese disponibili ed effettivamente utilizzabili nella nostra programmazione. Praticamente la chiamata avviene con una dichiarazione di una funzione esterna di ALSA mediante anticipata dalla parola-chiave "Extern" [nota 4].

Le funzioni proprie di ALSA sono degli handle [nota 5] che ritornano sempre un codice di errore di tipo "Intero". Un valore di ritorno pari a zero significa che l'uso della funzione esterna ha avuto successo.

Il Client e le sue porte

Linee generali

Nella sezione precedente abbiamo detto che il dispositivo ALSA svolge sostanzialmente due compiti: quello di scheduling, pianificare l'esecuzione di processi, ossia pianificare secondo una precisa sequenza, come in un elenco, l'invio degli eventi e quello di dispatching, ossia di indirizzarli all'esatta destinazione nel momento giusto. La fase della gestione reale degli eventi è lasciata ai programmi applicativi-utente (come i sequencer), i quali, in qualità di Client [nota 6], comunicano tali dati con il dispositivo ALSA (più precisamente con uno dei suoi sub-sistemi; per il Midi: seq) che invece fa da Server.
Abbiamo anche detto che il nostro applicativo deve possedere le funzioni di un Client. Più precisamente una parte di esso svolgerà le funzioni di Client. Si rende pertanto necessario creare appunto un Client, capace di comunicare con gli altri dispositivi esterni e soprattutto con il dispositivo ALSA. Per creare il nostro Client dobbiamo utilizzare la funzione esterna di ALSA:

int snd_seq_open() 

opportunamente dichiarata mediante l'istruzione Extern.

Il nostro progetto seguirà più o meno la struttura base di ogni applicazione in ALSA:

1) aprire il subsistema specifico (in questo caso "seq" del dispositivo ALSA mediante la predetta apposita funzione "snd_seq_open()");

2) impostare i parametri di detta funzione;

3) ricevere e/o inviare dati Midi al dispositivo;

4) chiudere il dispostivo (usando l'apposita funzione "snd_seq_close()").

Scrittura ed invio degli eventi Midi

Linee generali

Dobbiamo innanzitutto comprendere tre questioni:

1. che cosa vuole ALSA;

2. con quali strumenti preparare e modificare la zona di memoria richiesta;

3. l'organizzazione generale del programma.


Riguardo al punto 1, ALSA vuole una zona di memoria contenente, in posizioni specifiche, i valori adatti per la preparazione e per la realizzazione dell'evento Midi. Nello specifico, ALSA vuole una zona di memoria contigua, dove nel primo byte c'è il tipo di evento, nel secondo byte i flag, dal terzo al sesto byte il timestamp e via dicendo.

Riguardo al punto 2, va definita una zona di memoria. Per preparare e gestire questa zona di memoria è possibile usare una delle seguenti risorse:

Riguardo infine al punto 3, l'organizzazione generale significa: ogni volta che viene inviato un evento ad ALSA, è necessario passargli una zona di memoria che sarà sempre la stessa, ri-scrivendola ogni volta, evitando (anche se potrebbe essere un’alternativa) di crearla al momento. Per preparare un evento, inoltre, visto che ci sono membri comuni, si userà una sola routine comune a tutti gli eventi.


La struttura virtuale degli eventi MIDI in ALSA

Gli eventi Midi gestiti dal subsistema seq di ALSA sono temporizzati mediante dei timestamp, e sono organizzati secondo un'apposita struttura precisa di dati: [nota 7]

type un singolo byte
flags un singolo byte
tag un singolo byte
queue un singolo byte
time, composto da:
tick o real-time tv_sec un integer
rReal-time tv_nsec un integer
source, composto da:
client un singolo byte
port un singolo byte
dest, composto da:
client un singolo byte
port un singolo byte

Da considerare che i membri time, source e dest sono dati strutturati, cioé contengono al loro interno altri membri. Per questi dati il membro è uno solo, ma per ragioni tecniche viene spezzato in due istruzioni. Quindi un evento in ALSA contiene 4 dati semplici + 3 strutturati "doppi" (totale sette), ma i tre strutturati vengono scritti, per ragioni tecniche, con un passaggio doppio, e quindi il totale delle scritture diventa 10.

In particolare riguardo ai membri della Struttura ALSA snd_seq_event_t degli Eventi Midi: [nota 8]:

  • type contiene il tipo di evento;
  • flags può contenere il Timestamp mode (Real-Time o Tick-Time, Assoluto o Relativo), il Data storage type ed il scheduling priority;
  • tag può contenere un marcatore arbitrario;
  • queue contiene il valore per lo Scheduling queue;
  • time contiene al suo interno due mebri entrambi di un integer: il primo ed il secondo per il timestamp specificato in Real time, ossia in formato "Orario" e più speficamente il primo in secondi e l'altro in nanosecondi; oppure solo il primo per il timestamp specificato in Midi Tick (in tal caso il secondo membro non serve e può essere posto a zero);
  • source contiene l'indirizzo del sorgente dell'Evento Midi. E' formato da due membri: il primo l'identificativo numerico del Client sorgente dei dati, il secondo il numero della sua porta;
  • dest contiene l'indirizzo del destinatario dell'Evento Midi. E' formato da due membri: il primo l'identificativo numerico del Client destinatario, il secondo il numero della sua porta.

Complessivamente questi primi dati generali, qui sopra considerati, occupano ben 16 byte della memoria allocata:

Byte:01234 5 6 78 9 10 1112131415
┠ ─ ─ ─┠ ─
Dati:TypeFlagsTagQueueTick_time / Real_time Tv_sec0 / Real_time Tv_nsecSource_clientPort_clientDest_clientDdest_port


Dopo i precedenti dati generali comuni dovranno seguire nella zona di memoria riservata i dati specifici del particolare messaggio Midi.

Il Timestamp

Il Timestamp di un evento può essere specificato in: [nota 9]

  • tick_time, corrisponde ai tick del Midi.
  • real_time, corrisponde al formato orario, e la sua risoluzione è in secondi e in nanosecondi;

Il Timestamp può inoltre essere:

  • assoluto, quando il tempo è determinato dal momento in cui la coda di eventi ha inizio;
  • relativo, quando il tempo è determinato dal momento in cui l'evento della coda è stato inviato.

Gli eventi ed i messaggi MIDI in particolare

Come è noto, i messaggi Midi si dividono in:

  • messaggi di Stato, che identificano il numero del Canale ed il tipo di evento Midi trasmesso;
  • messaggi di Dati, che sono i valori relativi ai messaggi di Stato, specificandone caratteristiche modificabili qualitative e quantitative.

Tenendo conto qui delle finalità meramente didattiche del nostro progetto applicativo, considereremo soltanto gli eventi Midi fondamentali direttamente legati alla esecuzione musicale: i Channel Voice Messages.

In tale circostanza l'evento Midi è organizzato da ALSA utilizzando due Strutture ed in base al tipo di Messaggio Midi. La prima Struttura, come abbiamo già visto, è comune a tutti i tipi di Messaggi Midi. La seconda Struttura è diversa a seconda del Messaggio Midi, ricevuto o da inviare.

In particolare per ospitare i dati dei Messaggi:

  • NoteOn;
  • NoteOff;
  • Polyphonic Aftertouch;

verrà utilizzata una Struttura che è dichiarata nel file d'intestazione di Alsa seq_event.h come segue:

typedef struct snd_seq_ev_note {
	unsigned char channel;      ' /**< channel number */
	unsigned char note;         ' /**< note */
	unsigned char velocity;     ' /**< velocity */
	unsigned char off_velocity; ' /**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */
	unsigned int duration;      ' /**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */
} snd_seq_ev_note_t;

Come specificato nel relativo commento, l'ultimo membro della suddetta Struttura è preso in considerazione solo nel caso di uso dell'evento Midi ALSA Event_NOTE.

Invece, per ospitare i dati dei restanti Messaggi:

  • Control Change;
  • Program Change;
  • Channel Aftertouch (Key Pressure);
  • Pitch Bend (Pitch Wheel);

verrà utilizzata una Struttura che è dichiarata nel file d'intestazione di Alsa seq_event.h come segue:

typedef struct snd_seq_ev_ctrl {
	unsigned char channel;     ' /**< channel number */
	unsigned char unused[3];   ' /**< reserved */
	unsigned int param;        ' /**< control parameter */
	signed int value;          ' /**< control value */
} snd_seq_ev_ctrl_t;

Volendo fondere le due precedenti Strutture, otteniamo un'unica organizzazione dei dati i cui membri, che andranno a contenere i dati degli specifici Messaggi Midi, sono i seguenti:

channel un singolo byte
note / unused un singolo byte
velocity / unused un singolo byte
off_velocity / unused un singolo byte
duration / param un Integer
---- / value un Integer


Ciascun evento Midi utilizza soltanto 2 o 3 (a seconda del messaggio) di questi dati sopra descritti, i quali nella memoria riservata occupano i rispettivi byte come appresso riportato:

Byte:1617181920 21 22 2324 25 26 27
(da 0 a 15 i dati comuni)...────────
Dati:channelnotevelocity|off_velocityduration/paramvalue

In ALSA il tipo di evento Midi viene distinto mediante una definizione identificativa, rappresentata da una enumerazione, e da assegnare al membro "type " della struttura sopra descritta dell'evento Midi:

Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, 
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND

Per vedere tramite codice Gambas il numero identificativo costante di alcuni più importanti eventi Midi, come gestiti ALSA, potremo adottare un breve applicativo, composto oltre che dal progetto principale Gambas, anche da una libreria condivisa .so, da noi appositamente scritta all'interno del medesimo progetto Gambas.

Library "/tmp/libalsa"

' void Eventi_ALSA()
' Mostra i valori delle Costanti identificatrici degli eventi ALSA.
Private Extern Eventi_ALSA()


Public Sub Main()
 
 Creaso()
 
 Eventi_ALSA()
 
End


Private Procedure Creaso()
 
 File.Save("/tmp/libalsa.c", "#include <stdio.h>\n#include <alsa/seq_event.h>\n\n" &
           "void Eventi_ALSA() {\n\n" &
           "   printf(\"\e[1m      E V E N T I          Id\e[0m\\n\\n\");\n" &
           "   printf(\"SND_SEQ_EVENT_NOTEON =      %d\\n\", SND_SEQ_EVENT_NOTEON);\n" &
           "   printf(\"SND_SEQ_EVENT_NOTEOFF =     %d\\n\", SND_SEQ_EVENT_NOTEOFF);\n" &  
           "   printf(\"SND_SEQ_EVENT_KEYPRESS =    %d\\n\", SND_SEQ_EVENT_KEYPRESS);\n" & 
           "   printf(\"SND_SEQ_EVENT_CONTROLLER = %d\\n\", SND_SEQ_EVENT_CONTROLLER);\n" & 
           "   printf(\"SND_SEQ_EVENT_PGMCHANGE =  %d\\n\", SND_SEQ_EVENT_PGMCHANGE);\n" & 
           "   printf(\"SND_SEQ_EVENT_CHANPRESS =  %d\\n\", SND_SEQ_EVENT_CHANPRESS);\n" &
           "   printf(\"SND_SEQ_EVENT_PITCHBEND =  %d\\n\", SND_SEQ_EVENT_PITCHBEND);\n" &             
           "   printf(\"SND_SEQ_EVENT_START =      %d\\n\", SND_SEQ_EVENT_START);\n" &             
           "   printf(\"SND_SEQ_EVENT_CONTINUE =   %d\\n\", SND_SEQ_EVENT_CONTINUE);\n" &             
           "   printf(\"SND_SEQ_EVENT_STOP =       %d\\n\", SND_SEQ_EVENT_STOP);\n" &
           "   printf(\"SND_SEQ_EVENT_TEMPO =      %d\\n\", SND_SEQ_EVENT_TEMPO);\n" &
           "   printf(\"SND_SEQ_EVENT_SENSING =    %d\\n\", SND_SEQ_EVENT_SENSING);\n" &
           "   printf(\"SND_SEQ_EVENT_ECHO =       %d\\n\", SND_SEQ_EVENT_ECHO);\n\n}")
   
 Shell "gcc -o /tmp/libalsa.so /tmp/libalsa.c -shared -lasound -fPIC" Wait
 
End

Nel nostro progetto iniziale inseriremo per ciascun evento Midi un Button per attivare dalla classe principale del nostro progetto ogni evento mediante una specifica routine da scrivere invece all'interno della classe secondaria (CAlsa.class).

Dimensione finale della zona di memoria riservata

La imprescindibile zona di memoria riservata sarà dunque impegnata dalle due parti:

  • la prima (un po' più generica) (16 byte complessivamente: dal byte n° 0 al byte n° 15);
  • la seconda, quella dei valori specifici del particolare singolo Evento Midi (12 byte complessivamente: dal byte n° 16 al byte n° 27);

per un totale di 28 byte.

Come sappiamo, i singoli specifici eventi Midi utilizzeranno solo alcuni settori, dunque solo alcuni byte, della seconda parte della memoria allocata.

Accodare gli eventi in un buffer "intermedio"

Al termine della scrittura nella zona di memoria riservata di tutti i dati necessari per definire l'evento Midi desiderato, sarà inserita la funzione di ALSA:

int snd_seq_event_output_buffer(snd_seq_t * seq, snd_seq_event_t * ev)

che pone in uscita un Evento Midi e più precisamente accoda un Evento Midi in un buffer intermedio.
Tale funzione ritorna il numero (integer) di Eventi Midi rimanenti da far uscire, oppure un codice di errore se negativo.

Si potrà utilizzare anche la funzione esterna:

int snd_seq_event_output(snd_seq_t * seq, snd_seq_event_t * ev)]

Passare ad ALSA i valori presenti nella memoria allocata

Al termine di ciascuna routine, finalizzata all'invio di ciascun Evento Midi, come qui sopra appena descritto, deve essere richiamata la funzione di ALSA:

int snd_seq_drain_output(snd_seq_t * seq)

che sversa (invia) il contenuto della zona di memoria allocata (il predetto buffer intermedio) nel subsistema "seq" di ALSA.
Tale funzione ritorna un integer uguale a zero, se tutti gli eventi sono stati inviati al sequencer, e quindi se il buffer intermedio è stato svuotato. Ritorna un valore negativo ritorna un errore.
Come già accennato, questa funzione va invocata dopo ogni Evento Midi: ciò assicura che l'Evento venga processato in tempo, svuotando il buffer intermedio precedentemente riempito di dati dalla funzione: "snd_seq_event_output()" [nota 10] sopra descritta.


Note

[1] Si suggerisce di dare un'occhiata alla pagina: Extern: richiamare funzioni esterne a Gambas
La conoscenza dell'istruzione "Extern", relativa alle dichiarazioni esterne in Gambas, nonché delle API di ALSA (richiamate appunto dalla funzione "Extern"), è comunque presupposto necessario ed imprescindibile per la gestione in Gambas dei dati Midi con ALSA.

[2] Una libreria, come lo stesso nome indica, contiene una raccolta di subroutine da utilizzare più volte da più programmi.

[3] Ci sono due metodi per gestire con ALSA i dati Midi: il metodo definito "rawmidi" ed il metodo "sequencer". Il metodo rawmidi, che ha a che fare con i byte Midi essenziali, è ad un livello inferiore e semplice, mentre il metodo sequencer opera a livello più alto e complesso. In questa guida ci occuperemo della gestione dei dati Midi mediante il metodo sequencer.

[4] Una dichiarazione Extern fa riferimento all'ultima istruzione LIBRARY incontrata. Se l'ultima Library non è quella che contiene la funzione successiva alla libreria medesima, detta funzione non potrà essere richiamata dal programma. Pertanto, se si devono usare più librerie, queste o si dichiarano di volta in volta prima della funzione di riferimento (ma sempre all'esterno della routine contenente detta funzione), oppure si specificano all'interno di ogni loro dichiarazione.

[5] Un Handle (maniglia) rappresenta "una variabile associata a un oggetto complesso, che lo identifica". Possiamo dire che in modo figurato questa maniglia è la protuberanza con la quale si interagisce con l'oggetto medesimo. In gambas, per esempio, qualsiasi oggetto (form, o altro) è un handle, e per creare un oggetto/handle in Gambas, come è noto, bisogna scrivere "NEW oggetto_da_creare", in ALSA invece la funzione per creare l'handle è, come sappiamo: variab_int = snd_seq_etc_etc(xxx, ......) as integer.

[6] Sul rapporto Client/Server in ALSA: https://www.alsa-project.org/alsa-doc/alsa-lib/seq.html

[7] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/structsnd__seq__event__t.html

[8] Vedi anche D. Blengino: Definizione degli eventi Midi in ALSA.

[9] Vedere: https://www.alsa-project.org/alsa-doc/alsa-lib/unionsnd__seq__timestamp__t.html

[10] Distinzione fra la funzione err = snd_seq_event_output(handle, ev) ed err = snd_seq_drain_output(handle):

  • err = snd_seq_event_output(handle, ev) --> accoda gli eventi in un buffer "intermedio". Quando tale buffer è pieno, viene svuotato automaticamente nel buffer-ALSA. Lo svuotamento di tale buffer intermedio può essere indotto anche se esso non è pieno.
  • err = snd_seq_drain_output(handle) --> svuota "a richiesta" il buffer intermedio (anche quindi se il buffer intermedio non è pieno).


Riferimenti