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 e "lavorabile". Pertanto, non sarà più necessario l'uso di funzioni e stratagemmi per riservare una zona di memoria specifica, né di puntatori e di comandi Write per scriverci dentro. Per tale ragione le funzioni presenti nella classe secondaria CAlsa.class, comprese le dichiarazioni esterne, che facevano riferimento a quella zona allocata e ai suoi puntatori, devono essere modificate inserendo la dichiarazione di una variabile-Struttura che fa riferimento a quelle istanziate per ciascun messaggio Midi, le quali assumono - come vedremo più sotto - la composizione e l'organizzazione della Struttura-modello:

Private Extern snd_seq_event_output(handle As Pointer, variabileStruttura As eventimidiStruttura) As Integer

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.


I messaggi Midi specifici

Preambolo

Organizzeremo nella classe secondaria CAlsa.class le routine per la gestione dei Messaggi Midi. Poiché per i valori specifici di ciascun messaggio è prevista una disposizione all'interno della zona di memoria (come abbiamo già appreso con l'uso di un'area di memoria allocata) comune con altri messaggi, prevederemo - per ciascun gruppo di messaggi dalla Struttura simile - delle variabili-Struttura del tipo della Struttura dell'Evento Midi di ALSA, ad esempio:

ev As New EventoMidiAlsa

Con la variabile-Struttura "ev" si definirà concretamente il messaggio Midi richiesto, fissando in ciascun membro (quelli comuni e quelli specifici di ogni tipo di Evento Midi ALSA) il relativo valore. Si potrà evitare di richiamare i campi che eventualmente non servono.

Gruppo messaggi: NoteON - NoteOFF - Polyphonic Aftertouch

Questi tre messaggi Midi hanno tra loro in comune tre medesimi specifici dati posti ai byte di indice 16, 17 e 18 della "Struttura-modello", e che abbiamo chiamato: ".channel", ".note" e ".velocity".

 ev.type = tipo_Evento          ' = 6, 7 oppure 8 a seconda del tipo di Evento Midi ALSA
 ev.queue = outq
 ev.source_client = id
 ev.source_port = outport
 ev.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev.dest_port = SND_SEQ_ADDRESS_UNKNOWN
  ev.channel = valore
  ev.note = valore
  ev.velocity = valore

 err = snd_seq_event_output(handle, ev)   ' Si passa ad ALSA la Struttura appena valorizzata

Gruppo messaggi: Program Change - Channel Aftertouch (Key Pressure)

Questi due messaggi Midi hanno tra loro in comune due medesimi specifici dati posti ai byte di indice 16 e 24 della Struttura-modello, e che abbiamo chiamato: ".channel" e ".value".

 ev.type = tipo_Evento          ' = 11 oppure 12 a seconda del tipo di Evento Midi ALSA
 ev.queue = outq
 ev.source_client = id
 ev.source_port = outport
 ev.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev.dest_port = SND_SEQ_ADDRESS_UNKNOWN
  ev.channel = valore
  ev.value = valore

 err = snd_seq_event_output(handle, ev)       ' Si passa ad ALSA la Struttura appena valorizzata

Gruppo messaggi: Control Change - Pitch Bend (Pitch Wheel)

Questi due messaggi Midi hanno tra loro in comune tre medesimi specifici dati posti ai byte num. 16, 20 e 24 della Struttura-modello, e che abbiamo chiamato: ".channel", ".param" e ".value".

 ev.type = tipo_Evento          ' = 10 oppure 13 a seconda del tipo di Evento Midi ALSA
 ev.queue = outq
 ev.source_client = id
 ev.source_port = outport
 ev.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev.dest_port = SND_SEQ_ADDRESS_UNKNOWN
  ev.channel = 0
  ev.param = valore
  ev.value = valore

 err = snd_seq_event_output(handle, ev)       ' Si passa ad ALSA la Struttura appena valorizzata

Va detto che per l'Evento Pitch-Bend il membro più importante da valorizzare è ".value".

Esempi di codice

Nel seguente esempio dichiareremo una Struttura, chiamata snd_seq_event_t, che farà da modello alla particolare variabile di quel tipo di Struttura.
In questo esempio andremo a gestire soltanto tre tipi di Messaggi Midi: il Program Change, il Note On e il Note OFF.

Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte              '  byte indice n° 0
 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        ' byte n° 15
   channel As Byte           ' byte n° 16
   note As Byte              ' byte n° 17
   velocity As Byte          ' byte n° 18
   off_velocity As Byte      ' byte n° 19
     param As Integer          ' byte n° 20
     value As Integer          ' byte n° 24
End Struct

Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Private Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254
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_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_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_connect_to ( snd_seq_t * seq, int myport, int dest_client, int dest_port )
' Connect from the given receiver port in the current client to the given destination client:port.
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, dest_client As Integer, dest_port As Integer) As Integer

' int snd_seq_alloc_named_queue (snd_seq_t * seq, CONST char * name)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer, name As String) As Integer
 
' int snd_seq_event_output (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output(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 Sub Main()

 Dim handle As Pointer
 Dim err, id, porta, que As Integer
 Dim ev_midi As New Snd_seq_event_t   ' Dichiara la variabile del tipo della Struttura "snd_seq_event_t"

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 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, "Test")
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

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

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

' Assume che l'ID/porta dell'altro Client (Softsynth) sia 128:0
 err = snd_seq_connect_to(handle, porta, 128, 0)
 If err < 0 Then error.Raise("Errore: " & snd_strerror(err))

' Program Change
 ev_midi.type = SND_SEQ_EVENT_PGMCHANGE
 ev_midi.flags = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick_o_tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.value = 44    ' Imposta il suono dello strumento musicale da usare

 InviaEventoMidi(handle, ev_midi)

' Note ON
 ev_midi.type = SND_SEQ_EVENT_NOTEON
 ev_midi.flags = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick_o_tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.note = 64       ' Imposta una nota Midi da eseguire
 ev_midi.velocity = 100  ' Imposta la velocità di tocco
 
 InviaEventoMidi(handle, ev_midi)

' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
 Wait 3

' Note OFF
 ev_midi.type = SND_SEQ_EVENT_NOTEOFF
 ev_midi.flags = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick_o_tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.note = 64       ' Imposta una nota Midi da eseguire
 ev_midi.velocity = 0    ' Imposta la velocità di tocco
 
 InviaEventoMidi(handle, ev_midi)

' Va in Chiusura:
 snd_seq_close(handle)

End

Private Procedure InviaEventoMidi(p As Pointer, ev As Snd_seq_event_t)

 Dim err As Integer

 err = snd_seq_event_output(p, ev)
 If err < 0 Then error.Raise("Errore: " & snd_strerror(err))

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

End


In questo altro esempio saranno suonate 8 note della scala musicale di Do maggiore.
Dal punto di vista del Midi, quindi, dovremo inviare Messaggi Midi "Note On", per attivare l'esecuzione di una nota Midi musicale, e Messaggi Midi "Note Off" per fermare l'esecuzione della nota. Pre-invieremo anche un Messaggio Midi "Program Change" per impostare il suono dello strumento selezionato sullo standard General Midi.
Sappiamo che ALSA accetta eventi Midi solo se inviati nella modalità rigida rappresentata dalla Struttura "snd_seq_event_t". Pertanto, gli eventi Midi di ALSA, che corrispondono ai Messaggi Midi, devono essere rigidamente e correttamente configurati e inviati ad ALSA attraverso questa Struttura. Nel codice stabiliremo alcuni membri di quella Struttura in modo generico e valido per i tre Midi Message che dovremo inviare.
Prima di avviare il programma, è necessario avviare il Softsyhth "Qsynth" per consentire l'ascolto del suono. Il codice rileverà automaticamente il programma "Qsynth" (che è un altro "Client" ALSA) e si connetterà ad esso. Speciali routine si occuperanno dei dati relativi allo specifico Midi Message, che verranno inviati al sottosistema "Seq" di ALSA.

Private sndseq As Pointer
    
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 ' La porta di questo programma Client di ALSA può essere "letta" dal un altro Client esterno di ALSA
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
 
' Numerazione per specificare i Messaggi 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
 
' int snd_seq_open (snd_seq_t **handle, const char * name, int streams, int mode)
' Open the ALSA sequencer.
Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer

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

' 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)
' Create a port - simple version.
Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer
 
' int snd_seq_alloc_queue (snd_seq_t *handle)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(handle As Pointer) As Integer

' int snd_seq_connect_to (snd_seq_t *seq, int my_port, int dest_client, int dest_port)
' Simple subscription (w/o exclusive & time conversion).
Private Extern snd_seq_connect_to(seq As Pointer, my_port As Integer, dest_client As Integer, dest_port As Integer) As Integer

' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output(handle As Pointer, ev As Snd_seq_event_t)
 
' 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
 
' int snd_seq_close (snd_seq_t *handle)
' Close the sequencer.
Private Extern snd_seq_close(handle As Pointer) As Integer
 
 
Public Sub Main()
    
 Dim note As Byte[] = [60, 62, 64, 65, 67, 69, 71, 72]    ' Le 8 note Midi da eseguire
 Dim source_dest As New Byte[4]
 Dim n As Byte
 Dim ev As New Snd_seq_event_t
    
' Crea il Client di ALSA:
 CreaClient(source_dest)
    
' Imposta l'Evento Midi di ALSA con alcuni valori:
 With ev
   .flags = 0
   .tag = 0
   .queue = 0
   .tick_o_tv_sec = 0
   .source_client = source_dest[0]
   .source_port = source_dest[1]
   .dest_client = source_dest[2]
   .dest_port = source_dest[3]
   .channel = 0
 End With
   
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio-Midi "Program Change", pecificando nel secondo argomento il numero identificativo GM dello strumento musicale da utilizzare:
 program_change(ev, 48)
    
 For n = 0 To note.Max
' Invoca la sotto-procedura per inviare ad ALSA il Messaggio-MIDI "Note ON":
   Note_On(ev, note[n])

' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
   Wait 1

' Invoca la sotto-procedura per inviare ad ALSA il Messaggio-MIDI "Note OFF":
   Note_Off(ev, note[n])
 Next
   
 snd_seq_close(sndseq)
 
End

Private Function CreaClient(srcdst As Byte[])

 Dim rit As Integer
    
 rit = snd_seq_open(VarPtr(sndseq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il sub-sistema 'seq' di ALSA: " & snd_strerror(rit))
 
 srcdst[0] = snd_seq_client_id(sndseq)
    
 srcdst[1] = snd_seq_create_simple_port(sndseq, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If srcdst[1] < 0 Then Error.Raise("Impossibile creare la porta di Uscita dei dati ALSA !")
 
 Fluidsynth(srcdst)
    
' Connette questo Client-Gambas a un altro Client (per esempio "QSynth"):
 rit = snd_seq_connect_to(sndseq, srcdst[1], srcdst[2], srcdst[3])
 If rit < 0 Then Error.Raise("Impossivbile connettersi al Client di ALSA destinatario dei dati: " & snd_strerror(rit))
 
 snd_seq_alloc_queue(sndseq)
    
End

Private Procedure program_change(ev_prch As Snd_seq_event_t, strum As Integer)
 
 With ev_prch
' Specifica che è un Messaggio Midi "Program Change":
   .type = SND_SEQ_EVENT_PGMCHANGE
' Assegna il valore dello strumento musicale secondo lo standard "General Midi":
   .value = strum
 End With
     
 InviaEvento(ev_prch)
   
End

Private Procedure Note_On(ev_nton As Snd_seq_event_t, nota As Byte)

 With ev_nton
' Specifica che è un Messaggio Midi "Note ON":
   .type = SND_SEQ_EVENT_NOTEON
' Specifica il numero della nota MIDI da eseguire:
   .note = nota
' Specifica il valore della "Velocity":
   .velocity = 64
 End With
 
 InviaEvento(ev_nton)
  
End

Private Procedure Note_Off(ev_ntoff As Snd_seq_event_t, nota As Byte)

 With ev_ntoff
' Specifica che è un Messaggio Midi "Note OFF":
   .type = SND_SEQ_EVENT_NOTEOFF
' Specifica il numero della nota MIDI da eseguire:
   .note = nota
' Specifica il valore della "Velocity":
   .velocity = 0
 End With
 
 InviaEvento(ev_ntoff)
  
End

Private Procedure InviaEvento(evento As Snd_seq_event_t)

  snd_seq_event_output(sndseq, evento)
 
  snd_seq_drain_output(sndseq)
  
End

Private Function Fluidsynth(sd As Byte[])

 Dim s As String
 Dim ss As String[]
 Dim c As Short
    
 s = File.Load("/proc/asound/seq/clients")
 If InStr(s, "FLUID Synth") == 0 Then Error.Raise("FluidSinth non trovato !")
 ss = Split(s, "\n")
 For c = 0 To ss.Max
   If InStr(ss[c], "FLUID Synth") > 0 Then
     sd[2] = Scan(ss[c], "Client * : \"FLUID Synth*")[0]
     sd[3] = Scan(ss[c + 1], "  Port * : \"Synth*")[0]
   Endif
 Next
 
End

Di seguito un programma simile al primo sopra già visto, ma ora proposto in versione grafica con l'uso di uno Slider per agire sul Messaggio Midi Pitch Bend:

 Library "libasound:2.0.0"

Public Struct snd_seq_event_t
 type As Byte              '  byte indice n° 0
 flag 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        ' byte n° 15
   channel As Byte           ' byte n° 16
   note As Byte              ' byte n° 17
   velocity As Byte          ' byte n° 18
   off_velocity As Byte      ' byte n° 19
     param As Integer          ' byte n° 20
     value As Integer          ' byte n° 24
End Struct

Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576
Private Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Private Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254
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_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_client_id (snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_connect_to ( snd_seq_t * seq, int myport, int dest_client, int dest_port )
' Connect from the given receiver port in the current client to the given destination client:port.
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, dest_client As Integer, dest_port As Integer) As Integer

' int snd_seq_alloc_named_queue (snd_seq_t * seq, CONST char * name)
' Allocate a queue.
Private Extern snd_seq_alloc_queue(seq As Pointer, name As String) As Integer
 
' int snd_seq_event_output (snd_seq_t * seq, snd_seq_event_t *ev)
' Output an event.
Private Extern snd_seq_event_output(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


Private handle As Pointer
Private ev_midi As New Snd_seq_event_t   ' Dichiara la variabile del tipo della Struttura "snd_seq_event_t"
Private id As Integer
Private porta As Integer
Private que As Integer

Public Sub _new()

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

End

Public Sub Button1_Click()

 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 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, "Test")
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

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

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

' Assume che l'ID/porta dell'altro Client (Softsynth) sia 128:0
 err = snd_seq_connect_to(handle, porta, 128, 0)
 If err < 0 Then error.Raise("Errore: " & snd_strerror(err))

 Slider1.Enabled = True

' Program Change
 ev_midi.type = SND_SEQ_EVENT_PGMCHANGE
 ev_midi.flag = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick o tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.value = 44    ' Imposta il suono dello strumento musicale da usare

 InviaEventoMidi()

' Note ON
 ev_midi.type = SND_SEQ_EVENT_NOTEON
 ev_midi.flag = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick o tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.note = 64       ' ' Imposta una nota Midi da eseguire
 ev_midi.velocity = 100  ' ' Imposta la velocità di tocco
 
 InviaEventoMidi()

' Imposta la durata dell'esecuzione della nota.
' Va precisato che l'uso della funzione "Wait" NON è un modo coerente con il protocollo di ALSA per la temporizzazione degli Eventi Midi: dato che ALSA usa a tale fine la cosiddetta "marcatura degli Eventi Midi" assegnando degli opportuni valori nel membro "flags" e in quelli del "Timestamp" della Struttura "snd_seq_event_t".
' Qui si fa uso della funzione "Wait" soltanto per meri motivi didattici ed esemplificativi del funzionamento degli altri aspetti trattati in questa pagina.
 Wait 10

' Note OFF
 ev_midi.type = SND_SEQ_EVENT_NOTEOFF
 ev_midi.flag = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick o tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.note = 64       ' Imposta una nota Midi da eseguire
 ev_midi.velocity = 0    ' Imposta la velocità di tocco
 
 InviaEventoMidi()

' Va in Chiusura:
 Slider1.Value = 0
 Slider1.Enabled = False
 snd_seq_close(handle)

End

Public Sub Slider1_Change()   ' Agendo sullo "Slider" si modifica il "Pitch-Bend" con effetto del glissato

' Pitch-Bend
 ev_midi.type = SND_SEQ_EVENT_PITCHBEND
 ev_midi.flag = 0
 ev_midi.tag = 0
 ev_midi.queue = que
 ev_midi.tick o tv_sec = 0
 ev_midi.source_client = id
 ev_midi.source_port = porta
 ev_midi.dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_midi.dest_port = SND_SEQ_ADDRESS_UNKNOWN
 ev_midi.channel = 0
 ev_midi.param = 0
 ev_midi.value = Slider1.Value    ' Imposta i valori del "Pitch-Bend"
 
 InviaEventoMidi()

End

Private Procedure InviaEventoMidi()

 Dim err As Integer

 err = snd_seq_event_output(handle, ev_Midi)
 If err < 0 Then error.Raise("Errore: " & snd_strerror(err))

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

End