Usare ALSA per far comunicare due o più programmi Gambas

Da Gambas-it.org - Wikipedia.

Introduzione

Uno dei modi possibili per effettuare il passaggio di dati fra due o più programmi Gambas è quello di servirsi del sistema Alsa.

Alsa è un sistema che consente la connessione fra più applicativi Client (pensate ad esempio a quello che si fa con QJack o con aconnect. I dati vengono passati da un applicativo all'altro, magari trasformati e rispediti al mittente, tramite le funzioni di Alsa.

Alsa, essendo un sistema con funzioni universali, può dunque permettere anche lo scambio di dati fra programmi scritti in linguaggi diversi (ad esempio fra un programma scritto in C ed un programma scritto in Gambas).

La strategia da utilizzare è quella, per esempio, di servirsi della procedura per l'invio e la ricezione fra i "Client" di Alsa dei dati Midi. L'uso di detta strategia per l'invio nel nostro caso di dati generici è possibile in quanto Alsa accetta valori numerici. Va sottolineato che potranno essere inviati tramite Alsa soltanto valori di tipo numerico. Poiché, però, è possibile ovviamnete l'invio di una quantità indefinita di eventi, contenenti nostri valori numerici, e poiché questi, come è noto, una volta ricevuti possono essere trasformati in caratteri secondo il protocollo ASCII con la funzione chr( ), sarà possibile al termine dell'intera procedura ottenere indirettamente anche delle stringhe di caratteri.


Esempio con trasmissione di dati fra due programmi

Facciamo l'esperimento con due semplicissimi programmi, che chiameremo chiamati: Programma_1 e Programma_2.

Scopo dell'esperimento è quello di provare la trasmissione di dati fa due applicativi Gambas tramite la loro connessione, in qualità di Client, ad Alsa. In particolare l'esperimento consiste nell'inviare da parte del Programma_1 il valore numerico: 9999 all'altro programma Gambas, Programma_2, il quale poi lo sommerà ad un'unità e lo rispedirà al Programma_1, che a sua volta mostrerà il nuovo valore sul proprio form in una TextLabel. Ricordare di lanciare prima il Programma_1, e solo dopo lanciare il Programma_2, assicurandosi che non siano stati già comunque lanciati altri applicativi Client di Alsa (Softsynth, Sequencer, programmi audio, etc).


La parte dedicata ad Alsa

Riporteremo qui solo i concetti ed i connessi esempi fondamentali.
Per entrambi i programmi la parte delicata e quella relativa al rapporto di ciascun applicativo con Alsa e con l'altro programma. Tale rapporto si instaura, ovviamente, mediante la creazione in ciascun programma di una porta "scrivibile" e di una "leggibile". Per la creazione di queste due porte rimandiamo all'apposito capitolo trattato in questa WIKI in riferimento alla gestione dei dati Midi con Alsa.
Per quanto riguarda, invece, le connessioni utilizzeremo le due funzioni esterne di Alsa, che in C sono così dichiarate:

int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)

e

int snd_seq_connect_to(seq as pointer, myport as integer, src_client as integer, src_port as integer)


Dunque nel Programma_1 imposteremo questo codice:

' Dichiariamo le due funzioni esterne:
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_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

Public Sub setdevice()
  
' Si connette "ad" Alsa:
 snd_seq_connect_to(handle, outport, 14, 0)

' Riceve la connessione "da" Alsa:
 snd_seq_connect_from(handle, inport, 14, outport)
  
End

Laddove il parametro outport è il numero della porta di uscita del Programma_1, e viene ottenuto da questa funzione esterna in fase di creazione della sua porta di uscita:

outport = snd_seq_create_simple_port(handle, "Seq-Out", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)

mentre il parametro inport è il numero della porta di entrata del Programma_1, e viene ottenuto da questa funzione esterna in fase di creazione della sua porta di entrata:

 inport = snd_seq_create_simple_port(handle, "Seq-In", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)

Il codice sopra quindi connetterà il Programma_1 direttamente ad Alsa, sia in entrata che in uscita; e pertanto riceverà dati da Alsa ed invierà dati ad Alsa.


Nel Programma_2, invece, imposteremo quest'altro codice:

' Dichiariamo le due funzioni esterne:
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_to(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

Public Sub setdevice(dclient As Integer)

' Si connette direttamente al "Programma_1":
 snd_seq_connect_to(handle, outport, dclient, 0)

' Riceve la connessione "da" Alsa:
 snd_seq_connect_from(handle, inport, 14, outport)
 printerr("Subscribe inport", err)

End

Laddove:

  • il parametro dclient è il numero identificativo Client del Programma_1, che potrà essere di volta in volta un numero compreso fra 128 e 191 (a seconda di quanti altri Client di Alsa sono già attivi al momento), e che potrà essere impostato anche in manierà fissa in codice. Mentre il successivo valore zero rappresenta il numero della porta del Programma_1;
  • il parametro outport è il numero della porta di uscita del Programma_2, e viene ottenuto da questa funzione esterna in fase di creazione della sua porta di uscita:
outport = snd_seq_create_simple_port(handle, "Seq-Out", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)
  • il parametro inport è il numero della porta di entrata del Programma_2, e viene ottenuto da questa funzione esterna in fase di creazione della sua porta di entrata:
 inport = snd_seq_create_simple_port(handle, "Seq-In", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)

Il codice sopra quindi connetterà il Programma_2 direttamente ad Alsa in entrata, mentre si connetterà al Programma_1 in uscita. Pertanto riceverà dati da Alsa ed invierà dati al Programma_1.


La parte relativa all'invio dei dati

I dati da inviare ad Alsa devono essere rigorosamente organizzati secondo uno schema, una rigida struttura |1|. In fase di preparazione di tali dati ben organizzati necessari per l'invio del messaggio ad Alsa bisognerà fra l'altro indicare a quale destinatario vanno consegnati i messaggi. Tali informazioni saranno poste nella struttura che organizza, come detto, i dati da trasmettere i dati, la quale potrà essere una classe specifica |1|. Nelle variabili del tipo di quella Classe specifica si porrà nei campi relativi al numero identificativo-Client del destinatario e della sua porta di entrata, per quanto riguarda il "Programma_1", rispettivamente il valore corrispondente appunto all'Id-Client di Alsa, solitamente 14, e della relativa porta di entrata, solitamente questa 0. Invece per quanto riguarda il "Programma_2", si porrà il valore corrispondente all'Id-Client del "Programma_1" e della porta di entrata del destinatario, solitamente questa 1.

Il codice dell'invio del nostro messaggio, formato da dati organizzati, potrà essere posto in altra Classe dei due programmi, ad esempio in quella principale; e potrà essere ad esempio del seguente tenore.

Per il "Programma_1":

Public Sub Button1_Click()

 cval = New CVal

 With cval
   .par1 = 50
   .par8 = 9999
   alsa.preparaEv(.par0, .par1, .par2, .par3, .par4, .par5, .par6, .par7, .par8)
 End With
 
 alsa.flush()

End

Laddove:

  • cval è una variabile del tipo della Classe CVal, la quale è così costituita:
Public par0 As Byte        ' Timestamp |1|
Public par1 As Byte        '  Type |1|
Public par2 As Byte        '  dest_id
Public par3 As Byte        '  dest_porta
Public par4 As Byte        '  Dato 1
Public par5 As Byte        '  Dato 2
Public par6 As Short       '  Dato 3
Public par7 As Integer     '  Dato 4
Public par8 As Integer     '  Dato 5

le variabili Dato n potranno essere utilizzate per "trasportare" i valori da noi desiderati;

  • .par1 è il tipo di Messaggio (Midi ovviamente) che dovrà essere compreso da Alsa (in questo caso 50 = SND_SEQ_EVENT_ECHO, ossia un'Eco standard di ritorno);
  • .par8 = 9999 è il dato che vogliamo inviare al Programma_2;
  • alsa.preparaEv(.par0, .par1, .par2, .par3, .par4, .par5, .par6, .par7, .par8) è la chiamata della funzione nella Classe CAlsa.class, che provvede all'invio dei dati. La funzione contiene 8 parametri sui quindici previsti dalla classe specifica che organizza i dati per Alsa. Il Timestamp sarà posto a 0 millisecondi se si vuole che Alsa trasmetta immediatamente il messaggio al Programma_2;
  • alsa.flush() la routine della classe.CAlsa contenente la funzione esterna per inviare definitivamente i dati ad Alsa.


Il "Programma_2" riceverà da Alsa - con l'uso della funzione esterna: int snd_seq_event_input(snd_seq_t *handle, snd_seq_event_t **ev), che sarà dichiarata e richiamata nel codice di entrambi i programmi in apposita routine-evento [nota 1] - un messaggio attraverso l'osservazione del file descriptor |1|. Tale messaggio conterrà il dato numerico 9999. Il "Programma_2", quindi, con un'operazione lo addizionerà ad 1 e lo rispedirà, così modificato, al mittente, ossia al Programma_1:

Public Sub alsa_echoRitorno(ritornoEco As Integer)
 
' Mostra il valore numerico ricevuto:
 TextLabel1.Text = Str(ritornoEco)
 
 ritornoEco = ritornoEco + 1

 cval = New Cval
 
 With cval
   .par1 = 50
   .par8 = ritornoEco
   alsa.preparaEv(.par1, .par0, .par2, .par3, .par4, .par5, .par6, .par7, .par8)
 End With
 
 alsa.flush()

End

Laddove il parametro .par8 = ritornoEco contiene il nuovo valore numerico da ritrasmettere al mittente, ossia al Programma_1.


Il "Programma_1" riceverà mediante il file descriptor |1| il messaggio, lo filtrerà, e mostrerà in una TextLabel il nuovo valore ricevuto dal "Programma_2".

Ed il cerchio così si chiude.

Ovviamente è possibile effettuare questa procedura anche con più programmi-Client di Alsa.


Note

[1] vedere la trattazione in questa WIKI del medesimo argomento nella pagina relativa alla gestione dei dati Midi con Alsa.