Alsa e Gambas: Invio dati con l'uso delle Strutture

Da Gambas-it.org - Wikipedia.

Preambolo

In questa pagina verrà preso in considerazione l'uso delle Strutture per l'invio degli Eventi Midi ad ALSA.
Le Strutture, introdotte in Gambas con la versione 3.0, possono essere utilizzate per scrivere e gestire i messaggi Midi. Infatti dichiarando una Struttura (Struct) ci si riserva una quantità di memoria, definita dal tipo (byte, short, integer, etc.) dei valori dichiarati in ciascun membro della Struttura.
Dichiarando poi una variabile del tipo di quella Struttura, si ha automaticamente una zona di memoria utilizzabile.

Scrittura dei dati dei messaggi Midi nelle Strutture

Inseriremo una Struttura modello all'inizio della classe secondaria CAlsa.class. Porremo quindi, quali campi della struttura-tipo, i dati necessari per la definizione degli eventi Midi, come richiesti da ALSA, attribuendo a ciascun campo il tipo di dato desiderato da ALSA nel rispetto dell'ordine previsto dei dati, nonché del numero del byte al quale ciascun dato dovrà essere assegnato. Inseriremo non solo i dati comuni per tutti i messaggi Midi, ma anche quelli previsti per gli specifici messaggi:

In rosso sono rappresentati i membri che contengono dati identici per qualunque Evento Midi ALSA;
In verde sono rappresentati i membri che contengono dati relativi allo specifico Evento Midi ALSA.

struct snd_seq_event_t {                           Public Struct Snd_seq_event_t

snd_seq_event_type_t type                            type As Byte          byte di indice 0
unsigned char flags                                  flags As Byte
unsigned char tag                                    tag As Byte
unsigned char queue                                  queue As Byte
snd_seq_timestamp_t time
   ↳ snd_seq_tick_time_t / snd_seq_real_time_t    
        ↳ tick                ↳ tv_sec               tick_o_tv_sec As Integer
                              ↳ tv_nsec              tv_nsec As Integer
snd_seq_addr_t source
   ↳ unsigned char client                            source_client As Byte
   ↳ unsigned char port                              source_port As Byte
snd_seq_addr_t dest
   ↳ unsigned char client                            dest_client As Byte
   ↳ unsigned char port                              dest_port As Byte
snd_seq_ev_note_t note
   ↳ unsigned char channel                           channel As Byte       byte di indice 16
   ↳ unsigned char note                              note As Byte          byte di indice 17
   ↳ unsigned char velocity                          velocity As Byte      byte di indice 18
   ↳ unsigned char off_velocity                      off_velocity As Byte  byte di indice 19
unsigned int param                                   param As Integer      byte di indice 20
signed int value                                     value As Integer      byte di indice 24

}                                                  End Struct

I dati appartenenti agli specifici Messaggi Midi non vengono utilizzati tutti insieme da ciascun Evento Midi ALSA, ma solo alcuni di essi a seconda dello specifico Messaggio. Ovviamente nella Struttura-tipo essi saranno comunque posti e dichiarati, al fine di consentirne l'uso.

In Gambas si istanzierà una variabile del tipo della Struttura snd_seq_event_t, sopra descritta, con la quale gestire gli specifici Eventi Midi di ALSA, assegnando a ciascun membro (sia quelli comuni, sia quelli specifici di ogni tipo di Evento Midi ALSA) il relativo valore. Ovviamente si potrà evitare di richiamare i membri che eventualmente non devono essere lasciati con valore zero.


Riassunto delle fasi di creazione, allocamento e invio degli Eventi Midi ad ALSA

Prima di mostrare dei codici esemplificativi sulle varie modalità di invio degli'Eventi Midi al sistema ALSA, è opportuno ricordare quanto descritto nela capitolo precedente "ALSA e Gambas - Gestione dei Messaggi Midi standard".

Dunque possiamo elencare per sommicapi le seguenti fasi da seguire:
1) comporre l'Evento Midi ALSA nei membri essenziali della sua Struttura snd_seq_event_t, ed in particolare attribuire la temporizzazione mediante il "Timestamp" in "tick Midi" o in "real time";
2) allocare la Coda degli Eventi Midi ALSA con la funzione esterna snd_seq_alloc_queue();
3) avviare il controllo della Coda degli Eventi Midi ALSA con la funzione esterna snd_seq_control_queue(seq, q, type, value, 0, ev), passandole come 3° argomento la Costante di ALSA "SND_SEQ_EVENT_START";
4) accodare gli Eventi Midi nel buffer mediante la funzione esterna snd_seq_event_output_buffer();
5) inviare al sistema ALSA mediante la funzione esterna snd_seq_drain_output() l'intera Coda degli Eventi Midi, memorizzati nel buffer, affinche siano processati;
6) arrestare la Coda degli Eventi Midi ALSA richiamando la funzione esterna snd_seq_control_queue(seq, q, type, value, 0, ev), passandole però questa volta come suo 3° argomento la Costante di ALSA "SND_SEQ_EVENT_STOP".


Esempi di codice

1° esempio pratico

Nel seguente esempio in ambiente grafico avremo la Classe principale "FMain.Class" e una Classe secondaria, che incapsula le risorse di ALSA da utilizzare.
Dichiareremo una Struttura, chiamata snd_seq_event_t, che farà da modello alla particolare variabile di quel tipo di Struttura da usare per la organizzazione di ciascun Evento Midi ALSA.
In questo esempio sarà effettuato un invio diretto e immediato di un singolo Evento Midi per volta: cliccando su un qualunque tasto della tastiera del computer, si invierà un Evento Midi "NoteON"; rilasciando il tasto si invierà un Evento Midi "NoteOFF", per far cesssare l'esecuzione della nota Midi prima avviata.
Saranno gestiti comunque in apertura del programma anche gli Eventi Midi "Control Change" e "Program Change".
Riguardo al Timestamp di ogni Evento Midi ALSA inviato, trattandosi di invio singolo e diretto con esecuzione immediata è indifferente la modalità scelta: il membro time della predetta Struttura snd_seq_event_t relativo al Timestamp del singolo Evento Midi sarà posto a zero.

Dunque avremo la Classe principale "FMain.Class":

Private Const id_dev As Integer = 128   ' 14 = midi out oppure  128 (solitamente) = softsynth
Private Const p_dev As Integer = 0      ' porta del Client destinatario dei dati Midi: solitamente 0
Public alsa As CAlsa                    ' Classe che incapsula le funzioni ALSA
Private bo As Boolean

Public Sub Form_Open()

' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare:
 With alsa = New CAlsa As "Alsa"
' Apre ALSA e assegna un nome al Client:
   Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova")
' Sceglie la periferica (Softsynth) su cui suonare:
   .ImpostaDispositivo(id_dev, p_dev)
 End With

' Imposta il Volume gestendo l'Evento Midi "Control Change":
 alsa.ControlChange(0, 7, 64)
' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change":
 alsa.ProgramChange(0, 0, 44)

End

Public Sub Form_KeyPress()

 If bo Then Return 
' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-ON":
 alsa.NoteON(0, 64, 100)
 alsa.Flush()
 bo = True

End 

Public Sub Form_KeyRelease()

' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-OFF":
 alsa.NoteOFF(0, 64, 0)
 alsa.Flush()
 bo = False

End

Public Sub Form_Close()

 alsa.Chiude()

End

Nella Classe secondaria, chiamata "CAlsa", avremo il seguente codice:

Private handle As Pointer
Private id As Integer
Private s_port As Integer
Private que As Integer
Private dclient As Byte
Private dport As Byte


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte
 flags As Byte
 tag As Byte
 queue As Byte
 tick_o_tv_sec As Integer
 tv_nsec As Integer
 source_client As Byte
 source_port As Byte
 dest_client As Byte
 dest_port As Byte
 channel As Byte
 note As Byte
 velocity As Byte
 off_velocity As Byte
 param As Integer
 value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND

' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
' Creates a port with the given capability and type bits.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_alloc_queue (snd_seq_t * seq)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer
 
' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' const char * snd_strerror (int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_close (snd_seq_t * seq)
' Close the sequencer.
Private Extern snd_seq_close(seq As Pointer) As Integer


Public Function AlsaIni(nome As String) As String

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !")
 If err < 0 Then error.RAISE("Errore: " & snd_strerror(err))

 snd_seq_set_client_name(handle, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

 s_port = snd_seq_create_simple_port(handle, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 
 Print "Porta del programma = "; s_port
 If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port))

 que = snd_seq_alloc_queue(handle)
 If que < 0 Then error.Raise("Errore: " & snd_strerror(que))

 Return CStr(id) & ":" & CStr(s_port)

End

Public Procedure ImpostaDispositivo(client As Integer, port As Integer)

 dclient = client
 dport = port
 
End

Public Procedure ControlChange(canale As Byte, prm As Integer, value As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_CONTROLLER
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .param = prm
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ProgramChange(canale As Byte, non_usato As Byte, value As Integer)

 Dim err As Integer
 Dim ev_midi As New Snd_seq_event_t

 With ev_midi
   .type = SND_SEQ_EVENT_PGMCHANGE
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteON(canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEON
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteOFF(canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEOFF
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Flush()

 Dim err As Integer

 err = snd_seq_drain_output(handle)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Chiude()

 snd_seq_close(handle) 

End


2° esempio pratico

Di seguito un programma simile al precedente, nel quale si farà uso di uno Slider per agire sul Messaggio Midi Pitch Bend.

Nella Classe principale avremo il seguente codice:

Private Const id_dev As Integer = 128   ' 14 = midi out oppure  128 (solitamente) = softsynth
Private Const p_dev As Integer = 0       ' porta del Client destinatario dei dati Midi: solitamente 0
Public alsa As CAlsa                     ' Classe che incapsula le funzioni ALSA
Private bo As Boolean

Public Sub Form_Open()

 With Slider1
   .MinValue = -8192
   .MaxValue = 8192
 End With

' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare:
 With alsa = New CAlsa As "Alsa"
' Apre ALSA e assegna un nome al Client:
   Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova")
' Sceglie la periferica (Softsynth) su cui suonare:
   .ImpostaDispositivo(id_dev, p_dev)
 End With

' Imposta il Volume gestendo l'Evento Midi "Control Change":
 alsa.ControlChange(0, 7, 64)
' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change":
 alsa.ProgramChange(0, 0, 44)

End

Public Sub Form_KeyPress()

 If bo Then Return 
' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-ON":
 alsa.NoteON(0, 64, 100)
 alsa.Flush()
 bo = True

End 

Public Sub Form_KeyRelease()

' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-OFF":
 alsa.NoteOFF(0, 64, 0)
 alsa.Flush()
 bo = False

End

Public Sub Slider1_Change()

 alsa.PitchBend(0, 0, Slider1.Value)
 alsa.Flush()

End

Public Sub Form_Close()

 alsa.Chiude()

End

Nella Classe secondaria, chiamata "CAlsa", avremo il seguente codice:

Private handle As Pointer
Private id As Integer
Private s_port As Integer
Private que As Integer
Private dclient As Byte
Private dport As Byte


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte
 flags As Byte
 tag As Byte
 queue As Byte
 tick_o_tv_sec As Integer
 tv_nsec As Integer
 source_client As Byte
 source_port As Byte
 dest_client As Byte
 dest_port As Byte
 channel As Byte
 note As Byte
 velocity As Byte
 off_velocity As Byte
 param As Integer
 value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND

' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
' Creates a port with the given capability and type bits.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_alloc_queue (snd_seq_t * seq)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer
 
' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' const char * snd_strerror (int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_close (snd_seq_t * seq)
' Close the sequencer.
Private Extern snd_seq_close(seq As Pointer) As Integer


Public Function AlsaIni(nome As String) As String

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !")
 If err < 0 Then error.RAISE("Errore: " & snd_strerror(err))

 snd_seq_set_client_name(handle, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

 s_port = snd_seq_create_simple_port(handle, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 
 Print "Porta del programma = "; s_port
 If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port))

 que = snd_seq_alloc_queue(handle)
 If que < 0 Then error.Raise("Errore: " & snd_strerror(que))

 Return CStr(id) & ":" & CStr(s_port)

End

Public Procedure ImpostaDispositivo(client As Integer, port As Integer)

 dclient = client
 dport = port
 
End

Public Procedure ControlChange(canale As Byte, prm As Integer, value As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_CONTROLLER
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .param = prm
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ProgramChange(canale As Byte, non_usato As Byte, value As Integer)

 Dim err As Integer
 Dim ev_midi As New Snd_seq_event_t

 With ev_midi
   .type = SND_SEQ_EVENT_PGMCHANGE
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteON(canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEON
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteOFF(canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEOFF
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Flush()

 Dim err As Integer

 err = snd_seq_drain_output(handle)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure PitchBend(canale As Byte, prm As Integer, val As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_PITCHBEND
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .param = prm
   .value = val
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))
 
End

Public Procedure Stop()

 snd_seq_close(handle) 

End


Esempi con invio di più Eventi Midi ALSA temporizzati in sequenza

Vedremo di seguito due semplici esempi sull'uso del membro flags della Struttura snd_seq_event_t e della temporizzazione in "tick time" e in "real time".

Invio Eventi Midi con temporizzazione in "tick time"

Ricordiamo che la temporizzazione in ALSA nella modalità in "tick" Midi è attivata assegnando al membro flags della snd_seq_event_t la Costante "SND_SEQ_TIME_STAMP_TICK" di ALSA, mentre i valori temporali sono espressi appunto in "tick" Midi al membro tick del membro snd_seq_timestamp_t time della predetta Struttura.

Mostriamo un esempio pratico in ambiente grafico, nel quale cliccando su un "ToggleButton" si avvierà l'invio ad ALSA degli Eventi Midi e il loro processamento. Essi saranno eseguiti secondo la propria temporizzazione impostata nella Struttura snd_seq_event_t costitutiva.
Dunque avremo la Classe principale "FMain.Class":

Private Const id_dev As Integer = 128   ' 14 = midi out oppure  128 (solitamente) = softsynth
Private Const p_dev As Integer = 0      ' porta del Client destinatario dei dati Midi: solitamente 0
Public alsa As CAlsa                    ' Classe che incapsula le funzioni ALSA

 
Public Sub Form_Open()

' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare:
 With alsa = New CAlsa As "Alsa"
' Apre ALSA e assegna un nome al Client:
   Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova")
' Sceglie la periferica (Softsynth) su cui suonare:
   .ImpostaDispositivo(id_dev, p_dev)
 End With

End

Public Sub ToggleButton1_Click()

If ToggleButton1.Value Then
   With alsa
' Avvia la "Coda" degli Eventi Midi ALSA:
     .AvvioCoda()
' Imposta il Volume gestendo l'Evento Midi "Control Change":
     .ControlChange(0, 0, 0, 7, 50)
' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change":
     .ProgramChange(0, 0, 0, 0, 44)
' Imposta la prima nota Midi da eseguire:
     .NoteON(0, 0, 0, 64, 100)
' Imposta la nota Midi da silenziare dopo 96 tick dall'inizio della "Coda":
     .NoteOFF(0, 96, 0, 64, 0)

' Imposta il suono di un altro strumento musicale dopo 150 tick dall'inizio della "Coda" (quindi crea una pausa):
     .ProgramChange(0, 182, 0, 0, 18)
' Imposta la nota Midi da eseguire dopo 182 tick dall'inizio della "Coda":
     .NoteON(0, 182, 0, 66, 100)
' Imposta la nota Midi da silenziare dopo 300 tick dall'inizio della "Coda":
     .NoteOFF(0, 300, 0, 66, 0)

' Imposta il suono di un altro strumento musicale dopo 300 tick dall'inizio della "Coda":
     .ProgramChange(0, 300, 0, 0, 18)
' Imposta la nota Midi da eseguire dopo 300 tick dall'inizio della "Coda":
     .NoteON(0, 300, 0, 68, 100)
' Imposta la nota Midi da silenziare dopo 500 tick dall'inizio della "Coda":
     .NoteOFF(0, 500, 0, 68, 0)
' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda":
     .Flush()
   End With
 Else
   alsa.StopCoda()
   Me.Close
 Endif

End

Nella Classe secondaria, chiamata "CAlsa", vi sarà invece il seguente codice:

Private handle As Pointer
Private id As Integer
Private s_port As Integer
Private que As Integer
Private dclient As Byte
Private dport As Byte


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte
 flags As Byte
 tag As Byte
 queue As Byte
 tick_o_tv_sec As Integer
 tv_nsec As Integer
 source_client As Byte
 source_port As Byte
 dest_client As Byte
 dest_port As Byte
 channel As Byte
 note As Byte
 velocity As Byte
 off_velocity As Byte
 param As Integer
 value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS,
             SND_SEQ_EVENT_PITCHBEND, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_STOP = 32

' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
' Creates a port with the given capability and type bits.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_alloc_queue (snd_seq_t * seq)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer

' int snd_seq_control_queue(snd_seq_t * seq, int q, int type, int value, snd_seq_event_t * ev)
' Queue controls - start/stop/continue.
Private Extern snd_seq_control_queue(seq As Pointer, q As Integer, type As Integer, value As Integer, ev As Pointer) As Integer
 
' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' const char * snd_strerror (int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_close (snd_seq_t * seq)
' Close the sequencer.
Private Extern snd_seq_close(seq As Pointer) As Integer


Public Function AlsaIni(nome As String) As String

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !")
 If err < 0 Then error.RAISE("Errore: " & snd_strerror(err))

 snd_seq_set_client_name(handle, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

 s_port = snd_seq_create_simple_port(handle, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 
 Print "Porta del programma = "; s_port
 If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port))

 que = snd_seq_alloc_queue(handle)
 If que < 0 Then error.Raise("Errore: " & snd_strerror(que))

 Return CStr(id) & ":" & CStr(s_port)

End

Public Procedure ImpostaDispositivo(client As Integer, port As Integer)

 dclient = client
 dport = port
 
End

Public Procedure AvvioCoda()

 Dim err As Integer

 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_START, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ControlChange(flg As Byte, tick As Integer, canale As Byte, prm As Integer, value As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_CONTROLLER
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .param = prm
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ProgramChange(flg As Byte, tick As Integer, canale As Byte, non_usato As Byte, value As Integer)

 Dim err As Integer
 Dim ev_midi As New Snd_seq_event_t

 With ev_midi
   .type = SND_SEQ_EVENT_PGMCHANGE
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteON(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEON
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteOFF(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEOFF
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Flush()

 Dim err As Integer

 err = snd_seq_drain_output(handle)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure StopCoda()

 Dim err As Integer
 
 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_STOP, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

 snd_seq_close(handle)

End

Invio Eventi Midi con temporizzazione in "real time"

La temporizzazione in ALSA nella modalità in "real time" (temporizzazione in formato orario) è attivata assegnando al membro flags della snd_seq_event_t la Costante "SND_SEQ_TIME_STAMP_REAL" di ALSA.
Ricordiamo che questa modalità di temporizzazione degli Eventi Midi di ALSA adotta il sistema di misura dell'orario standard (ore, minuti, secondi), assegnando il valore in "secondi" al membro tv_sec e in "nanosecondi" al membro tv_nsec della sotto-Struttura snd_seq_real_time_t del membro time della Struttura principale snd_seq_event_t costitutiva.

Mostriamo un esempio identico al precedente, ma con impostazione della temporizzazione in Clock Midi (modalità "orario") espressa in secondi:
Dunque avremo la Classe principale "FMain.Class":

Private Const id_dev As Integer = 128   ' 14 = midi out oppure  128 (solitamente) = softsynth
Private Const p_dev As Integer = 0      ' porta del Client destinatario dei dati Midi: solitamente 0
Public alsa As CAlsa                    ' Classe che incapsula le funzioni ALSA

 
Public Sub Form_Open()

' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare:
 With alsa = New CAlsa As "Alsa"
' Apre ALSA e assegna un nome al Client:
   Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova")
' Sceglie la periferica (Softsynth) su cui suonare:
   .ImpostaDispositivo(id_dev, p_dev)
 End With

End

Public Sub ToggleButton1_Click()

If ToggleButton1.Value Then
   With alsa
' Avvia la "Coda" degli Eventi Midi ALSA:
     .AvvioCoda()
' Imposta il Volume gestendo l'Evento Midi "Control Change":
     .ControlChange(1, 0, 0, 7, 50)
' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change":
     .ProgramChange(1, 0, 0, 0, 44)
' Imposta la prima nota Midi da eseguire:
     .NoteON(1, 0, 0, 64, 100)
' Imposta la nota Midi da silenziare dopo 1 secondo dall'inizio della "Coda":
     .NoteOFF(1, 1, 0, 64, 0)

' Imposta il suono di un altro strumento musicale dopo 2 secondi dall'inizio della "Coda" (quindi crea una pausa):
     .ProgramChange(1, 2, 0, 0, 18)
' Imposta la nota Midi da eseguire dopo 2 secondi dall'inizio della "Coda":
     .NoteON(1, 2, 0, 66, 100)
' Imposta la nota Midi da silenziare dopo 3 secondi dall'inizio della "Coda":
     .NoteOFF(1, 3, 0, 66, 0)

' Imposta il suono di un altro strumento musicale dopo 3 secondi dall'inizio della "Coda":
     .ProgramChange(1, 3, 0, 0, 18)
' Imposta la nota Midi da eseguire dopo 3 secondi dall'inizio della "Coda":
     .NoteON(1, 3, 0, 68, 100)
' Imposta la nota Midi da silenziare dopo 5 secondi dall'inizio della "Coda":
     .NoteOFF(1, 5, 0, 68, 0)
' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda":
     .Flush()
   End With
 Else
   alsa.StopCoda()
   Me.Close
 Endif

End

Nella Classe secondaria, chiamata "CAlsa", vi sarà invece il seguente codice:

Private handle As Pointer
Private id As Integer
Private s_port As Integer
Private que As Integer
Private dclient As Byte
Private dport As Byte


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte
 flags As Byte
 tag As Byte
 queue As Byte
 tick_o_tv_sec As Integer
 tv_nsec As Integer
 source_client As Byte
 source_port As Byte
 dest_client As Byte
 dest_port As Byte
 channel As Byte
 note As Byte
 velocity As Byte
 off_velocity As Byte
 param As Integer
 value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS,
             SND_SEQ_EVENT_PITCHBEND, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_STOP = 32

' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
' Creates a port with the given capability and type bits.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_alloc_queue (snd_seq_t * seq)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer

' int snd_seq_control_queue(snd_seq_t * seq, int q, int type, int value, snd_seq_event_t * ev)
' Queue controls - start/stop/continue.
Private Extern snd_seq_control_queue(seq As Pointer, q As Integer, type As Integer, value As Integer, ev As Pointer) As Integer
 
' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' const char * snd_strerror (int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_close (snd_seq_t * seq)
' Close the sequencer.
Private Extern snd_seq_close(seq As Pointer) As Integer


Public Function AlsaIni(nome As String) As String

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !")
 If err < 0 Then error.RAISE("Errore: " & snd_strerror(err))

 snd_seq_set_client_name(handle, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

 s_port = snd_seq_create_simple_port(handle, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 
 Print "Porta del programma = "; s_port
 If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port))

 que = snd_seq_alloc_queue(handle)
 If que < 0 Then error.Raise("Errore: " & snd_strerror(que))

 Return CStr(id) & ":" & CStr(s_port)

End

Public Procedure ImpostaDispositivo(client As Integer, port As Integer)

 dclient = client
 dport = port
 
End

Public Procedure AvvioCoda()

 Dim err As Integer

 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_START, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ControlChange(flg As Byte, sec As Integer, canale As Byte, prm As Integer, value As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_CONTROLLER
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .param = prm
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ProgramChange(flg As Byte, sec As Integer, canale As Byte, non_usato As Byte, value As Integer)

 Dim err As Integer
 Dim ev_midi As New Snd_seq_event_t

 With ev_midi
   .type = SND_SEQ_EVENT_PGMCHANGE
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteON(flg As Byte, sec As Integer, canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEON
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteOFF(flg As Byte, sec As Integer, canale As Byte, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEOFF
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .channel = canale
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Flush()

 Dim err As Integer

 err = snd_seq_drain_output(handle)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure StopCoda()

 Dim err As Integer
 
 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_STOP, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

 snd_seq_close(handle)

End

Esempio in modalità temporizzazione in formato orario usando anche i nanosecondi

Di seguito ostriamo un semplice esempio simile al precedente, ma usando anche il membro ".tv_nsec" della Struttura snd_seq_event_t.
La Classe principale sarà la seguente:

Private Const id_dev As Integer = 128   ' 14 = midi out oppure  128 (solitamente) = softsynth
Private Const p_dev As Integer = 0      ' porta del Client destinatario dei dati Midi: solitamente 0
Public alsa As CAlsa                    ' Classe che incapsula le funzioni ALSA


Public Sub Form_Open()

 With alsa = New CAlsa As "Alsa"
   Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova")
   .ImpostaDispositivo(id_dev, p_dev)
 End With

End

Public Sub ToggleButton1_Click()

 Dim note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]
 Dim b As Byte
 Dim ns As Integer

 If ToggleButton1.Value Then
   With alsa
     .AvvioCoda()
     .ControlChange(1, 0, 7, 50)
     .ProgramChange(1, 0, 44)
     For b = 0 To note.Max
       If b == 3 Then ns = 750000000
' Imposta la nota Midi da eseguire.
' Aggiunge 750000000 nanosecondi.
       .NoteON(1, b, ns, note[b], 100)
       ns = 0
' Imposta la nota Midi da silenziare dall'inizio della "Coda".
' Alla terza nota aggiunge 750000000 nanosecondi.</font>
       If b == 2 Then ns = 750000000
       .NoteOFF(1, b + 1, ns, note[b], 0)
       ns = 0
     Next 
     .Flush()
   End With
 Else
   alsa.StopCoda()
   Me.Close
 Endif

End

La Classe secondaria per la gestione delle risorse di ALSA:

Private handle As Pointer
Private id As Integer
Private s_port As Integer
Private que As Integer
Private dclient As Byte
Private dport As Byte


Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte
 flags As Byte
 tag As Byte
 queue As Byte
 tick_o_tv_sec As Integer
 tv_nsec As Integer
 source_client As Byte
 source_port As Byte
 dest_client As Byte
 dest_port As Byte
 channel As Byte
 note As Byte
 velocity As Byte
 off_velocity As Byte
 param As Integer
 value As Integer
End Struct

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS,
             SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS,
             SND_SEQ_EVENT_PITCHBEND, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_STOP = 32

' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
' Set client name.
Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer

' int snd_seq_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type)
' Creates a port with the given capability and type bits.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer

' int snd_seq_alloc_queue (snd_seq_t * seq)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer

' int snd_seq_control_queue(snd_seq_t * seq, int q, int type, int value, snd_seq_event_t * ev)
' Queue controls - start/stop/continue.
Private Extern snd_seq_control_queue(seq As Pointer, q As Integer, type As Integer, value As Integer, ev As Pointer) As Integer
 
' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer

' int snd_seq_drain_output (snd_seq_t * seq)
' Drain output buffer to sequencer.
Private Extern snd_seq_drain_output(seq As Pointer) As Integer

' const char * snd_strerror (int errnum)
' Returns the message for an error code.
Private Extern snd_strerror(errnum As Integer) As String

' int snd_seq_close (snd_seq_t * seq)
' Close the sequencer.
Private Extern snd_seq_close(seq As Pointer) As Integer


Public Function AlsaIni(nome As String) As String

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !")
 If err < 0 Then error.RAISE("Errore: " & snd_strerror(err))

 snd_seq_set_client_name(handle, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

 s_port = snd_seq_create_simple_port(handle, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 
 Print "Porta del programma = "; s_port
 If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port))

 que = snd_seq_alloc_queue(handle)
 If que < 0 Then error.Raise("Errore: " & snd_strerror(que))

 Return CStr(id) & ":" & CStr(s_port)

End

Public Procedure ImpostaDispositivo(client As Integer, port As Integer)

 dclient = client
 dport = port
 
End

Public Procedure AvvioCoda()

 Dim err As Integer

 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_START, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ControlChange(flg As Byte, sec As Integer, prm As Integer, value As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_CONTROLLER
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .param = prm
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure ProgramChange(flg As Byte, sec As Integer, value As Integer)

 Dim err As Integer
 Dim ev_midi As New Snd_seq_event_t

 With ev_midi
   .type = SND_SEQ_EVENT_PGMCHANGE
   .flags = flg
   .tick_o_tv_sec = sec
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .value = value
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteON(flg As Byte, sec As Integer, ns As Integer, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEON
   .flags = flg
   .tick_o_tv_sec = sec
   .tv_nsec = ns
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure NoteOFF(flg As Byte, sec As Integer, ns As Integer, nota As Byte, vel As Byte)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_NOTEOFF
   .flags = flg
   .tick_o_tv_sec = sec
   .tv_nsec = ns
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = dclient
   .dest_port = dport
   .note = nota
   .velocity = vel
 End With

 err = snd_seq_event_output_buffer(handle, ev_midi)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure Flush()

 Dim err As Integer

 err = snd_seq_drain_output(handle)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

End

Public Procedure StopCoda()

 Dim err As Integer

 err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_STOP, 0, 0)
 If err < 0 Then Error.Raise("Errore: " & snd_strerror(err))

 snd_seq_close(handle)

End