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:

Public Struct eventimidiStruttura

 type As Byte                byte n° 0 \
 flag As Byte                           \
 tag As Byte                             \
 queue As Byte                            \  dati comuni
 timestamp1 As Integer                     \ a tutti
 timestamp2 As Integer                     / i messaggi Midi    
 source_id As Byte                        /
 source_porta As Byte                    /
 dest_id As Byte                        /
 dest_porta As Byte          byte n° 15/
   channel As Byte                byte n° 16  \
   note As Byte                   byte n° 17   \  dati appartenenti
   velocity As Byte               byte n° 18    \ agli specifici
   off_velocity As Byte           byte n° 19    / messaggi Midi
     param As Integer              byte n° 20  /
     value As Integer              byte n° 24 /
  
End Struct


I dati appartenenti agli specifici messaggi Midi non vengono utilizzati tutti insieme da ciascun messaggio, 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 apposite che vadano bene per i messaggi appartenenti a quel gruppo.


Gruppo messaggi: NoteON - NoteOFF - Polyphonic Aftertouch

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

Dichiareremo una variabile ad hoc per questi tre messaggi Midi del tipo della Struttura-modello, della quale ultima assumerà così l'organizzazione interna dei campi ed il tipo dei valori:

ev_OnOffPoly As New eventimidiStruttura

Ora la nostra variabile-Struttura ev_OnOffPoly è operativa e può essere utilizzata per la gestione ed il successivo invio dei messaggi Midi ad ALSA.

L'uso di tale variabile-Struttura si ha quando si dovrà definire concretamente il messaggio Midi richiesto, fissando in ciascun campo i relativi valori. Si potrà evitare di richiamare i campi che eventualmente non servono.

Quindi dalla classe principale chiameremo la routine specifica per i tre eventi qui considerati, passandogli anche tre valori necessari:

Public Sub eventoOnOffPoly(def_Evento As Byte, nota as byte, velocitas As Byte)

Dim err As Integer

 ev_OnOffPoly.type = def_Evento          ' = 6, 7 oppure 8
 ev_OnOffPoly.queue = outq
 ev_OnOffPoly.source_id = id
 ev_OnOffPoly.source_porta = outport
 ev_OnOffPoly.dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_OnOffPoly.dest_porta = SND_SEQ_ADDRESS_UNKNOWN
  ev_OnOffPoly.channel = 0
  ev_OnOffPoly.note = 60
  ev_OnOffPoly.velocity = velocitas

 err = snd_seq_event_output(handle, ev_OnOffPoly)    ' si passano ad ALSA i valori presenti nella variabile-Struttura: ev_OnOffPoly
 printerr("OnOffPoly = ", err)

End


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

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

Dichiareremo una variabile ad hoc anche per questi due messaggi Midi del tipo della Struttura-modello, della quale ultima assumerà così l'organizzazione interna dei campi ed il tipo dei valori:

ev_PgmKeyPress As New eventimidiStruttura

Ora la nostra variabile-Struttura ev_PgmKeyPress è operativa e può essere utilizzata per la gestione ed il successivo invio dei messaggi Midi ad ALSA.

L'uso di tale variabile-Struttura si ha quando si dovrà definire concretamente il messaggio Midi richiesto, fissando in ciascun campo i relativi valori. Si potrà evitare di richiamare i campi che eventualmente non servono.

Quindi dalla classe principale chiameremo la routine specifica per i due eventi qui considerati, passandogli anche due valori necessari:

Public Sub eventoPgmKeyPress(def_Evento As Byte, sec_Valore As Integer)

Dim err As Integer

 ev_PgmKeyPress.type = def_Evento          ' = 11 oppure 12
 ev_PgmKeyPress.queue = outq
 ev_PgmKeyPress.source_id = id
 ev_PgmKeyPress.source_porta = outport
 ev_PgmKeyPress.dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_PgmKeyPress.dest_porta = SND_SEQ_ADDRESS_UNKNOWN
  ev_PgmKeyPress.channel = 0
  ev_PgmKeyPress.value = Valore     ' impostato nella classe principale

 err = snd_seq_event_output(handle, ev_PgmKeyPress)    ' si passano ad ALSA i valori presenti nella variabile-Struttura: ev_PgmKeyPress
 printerr("PgmKeyPress = ", err)

End


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, parametro e valore.

Dichiareremo una variabile ad hoc anche per questi due messaggi Midi del tipo della Struttura-modello, della quale ultima assumerà così l'organizzazione interna dei campi ed il tipo dei valori:

ev_CtrlPitBnd As New eventimidiStruttura

Ora la nostra variabile-Struttura ev_CtrlPitBnd è operativa e può essere utilizzata per la gestione ed il successivo invio dei messaggi Midi ad ALSA.

L'uso di tale variabile-Struttura si ha quando si dovrà definire concretamente il messaggio Midi richiesto, fissando in ciascun campo i relativi valori. Si potrà evitare di richiamare i campi che eventualmente non servono.

Quindi dalla classe principale chiameremo la routine specifica per i due eventi qui considerati, passandogli anche tre valori necessari:

Public Sub eventoPgmKeyPress(def_Evento As Byte, prm_Valore As Integer, sec_Valore As Integer)

Dim err As Integer

 ev_CtrlPitBnd.type = def_Evento          ' = 10 oppure 13
 ev_CtrlPitBnd.queue = outq
 ev_CtrlPitBnd.source_id = id
 ev_CtrlPitBnd.source_porta = outport
 ev_CtrlPitBnd.dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
 ev_CtrlPitBnd.dest_porta = SND_SEQ_ADDRESS_UNKNOWN
  ev_CtrlPitBnd.channel = 0
  ev_CtrlPitBnd.param = prm       ' impostato nella classe principale
  ev_CtrlPitBnd.value = Valore    ' impostato nella classe principale

 err = snd_seq_event_output(handle, ev_CtrlPitBnd)   ' si passano ad ALSA i valori presenti nella variabile-Struttura: CtrlPitBnd
 printerr("CtrlPitBnd = ", err)

End


Da notare che per il Pitch-Bend il dato del membro "parametro" è poco influente, e può essere quindi fissato a zero.


Esempi di codice

Codice definitivo sin qui descritto della classe CAlsa.class

Di seguito il nostro codice definitivo sin qui descritto della classe CAlsa.class: Dichiareremo una Struttura, chiamata midiStruttura, che farà da modello alle particolari variabili Strutture che useremo nella classe in questione
In questo esempio andremo a gestire soltanto due tipi di Messaggi Midi: il NoteOn ed il Program Change.
Ecco il codice nella classe CAlsa (gestiamo come esempio solo il NoteON ed il Program Change); mentre quello nella classe principale andrà modificato in alcune parti:

CLASSE: CAlsa.class

Public Struct midiStruttura

 type As Byte              '  byte n° 0
 flag As Byte
 tag As Byte
 queue As Byte
 timestamp1 As Integer
 timestamp2 As Integer
 source_id As Byte
 source_porta As Byte
 dest_id As Byte
 dest_porta 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



' Dichiariamo le particolari variabili strutture organizzate ad immagine della Struttura modello "midiStruttura":
Private ev_noteON As New MidiStruttura
Private ev_pgm As New MidiStruttura

Private Const SIZE_OF_SEQEV As Integer = 64
Private handle As Pointer
Private id As Integer
Private outport As Integer
Private outq As Integer
Public dclient As Byte
Public dport As Byte

Library "libasound:2"

Const SND_SEQ_OPEN_DUPLEX As Integer = 3
Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576  ' (1<<20)
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
Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254

' int snd_seq_open (snd_seq_t **seqp, const char * name, Int streams, Int mode)
Private Extern snd_seq_open(Pseq As Pointer, name As String, streams As Integer, mode As Integer) As Integer

' int snd_seq_set_client_name (snd_seq_t* seq, const char* name)
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)
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 )
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_alloc_named_queue ( snd_seq_t * seq, CONST char * name )
Private Extern snd_seq_alloc_queue(seq As Pointer, name As String) As Integer


Public Sub alsa_open(myname As String)
 Dim err As Integer

 err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_DUPLEX, 0)
 Print "Opening alsa="; err
 If err < 0 Then error.RAISE("Error opening alsa")
 
 snd_seq_set_client_name(handle, myname)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID="; id
 
 err = snd_seq_create_simple_port(handle, "Seq-Out", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) 'SND_SEQ_PORT_CAP_READ, + SND_SEQ_PORT_CAP_SUBS_READ
 Print "My alsa client port="; err
 If err < 0 Then error.Raise("Error creating alsa port")
 outport = err
 
 err = snd_seq_alloc_queue(handle, "outqueue")         ' per creare una coda di eventi
  printerr("Creating queue", err)
 If err < 0 Then error.Raise("Error creating out queue")
 outq = err
 
End


' int snd_seq_connect_from(seq as pointer, myport as integer, src_client as integer, src_port as integer)
Private Extern snd_seq_connect_from(seq As Pointer, myport As Integer, src_client As Integer, src_port As Integer) As Integer

' int snd_seq_connect_to (snd_seq_t *seq, int my_port, int dest_client, int dest_port)
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, dest_client As Integer, dest_port As Integer) As Integer

Public Sub setdevice(client As Integer, port As Integer)
 Dim err As Integer
 
 dclient = client
 dport = port
 err = snd_seq_connect_to(handle, outport, dclient, dport)
 printerr("Subscribe", err)
 If err < 0 Then error.Raise("Error subscribe output device")
End


' Passeremo alla seguente funzione esterna una Struttura contenente dei valori.
' Nella dichiarazione con "Extern" si inserirà il nome della Struttura modello:
Private Extern snd_seq_event_output(handle As Pointer, vStrutt As MidiStruttura) As Integer
Private Extern snd_seq_drain_output(handle As Pointer) As Integer


' Gestione dell'Errore:

Private Extern snd_strerror(err As Integer) As Pointer
 
Public Sub errmsg(err As Integer) As String
  Return String@(snd_strerror(err))
End

Private Sub printerr(operation As String, err As Integer)
  If err < 0 Then Print operation; ": err ="; err; " ("; errmsg(err); ")"
End


' Eventi Midi:

Public Sub noteon(note As Byte, velocity As Byte)

 Dim err As Integer

' Riempiamo i campi della variabile-Struttura con dei valori:
  ev_noteON.type = 6
  ev_noteON.flag = 0
  ev_noteON.tag = 0
  ev_noteON.queue = outq
  ev_noteON.timestamp1 = 0
  ev_noteON.timestamp2 = 0
  ev_noteON.source_id = id
  ev_noteON.source_porta = outport
  ev_noteON.dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
  ev_noteON.dest_porta = SND_SEQ_ADDRESS_UNKNOWN
  ev_noteON.channel = 0
  ev_noteON.note = note
  ev_noteON.velocity = velocity
 

' Passiamo quindi quella Struttura con i campi, ora "valorizzati", alla funzione esterna...
err = snd_seq_event_output(handle, ev_noteON)
 printerr("Noteon = ", err)
 Print err

' ...affinché passi quei valori ad ALSA:
  flush()

End


Public Sub pgmchange(numStrumento As Byte)

 Dim err As Integer
 
  ev_pgm.type = 11
  ev_pgm.flag = 0
  ev_pgm.tag = 0
  ev_pgm.queue = outq
  ev_pgm.timestamp1 = 0
  ev_pgm.timestamp2 = 0
  ev_pgm.source_id = id
  ev_pgm.source_porta = outport
  ev_pgm.dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS
  ev_pgm.dest_porta = SND_SEQ_ADDRESS_UNKNOWN
  ev_pgm.channel = 0
  ' ev_pgm.note = 0
  ' ev_pgm.velocity = 0
   ' ev_pgm.param = 0
   ev_pgm.value = numStrumento
  

 err = snd_seq_event_output(handle, ev_pgm)
 printerr("Pgmchange = ", err)
 
 flush()

End

Public Sub flush()
 Dim err As Integer

 err = snd_seq_drain_output(handle)    ' drain output buffer to sequencer
 Printerr("Flush", err)

End

Altro esempio generico

In questo altro esempio - più generico - i dati Midi verranno inviati dal programma stesso e non da un dispositivo esterno. In particolare, suoneremo 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"
 
Public Struct snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte
  queue As Byte
  tick_time As Integer
  real_time 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   ' El puerto del Client puede ser "leído" por otro Client externo
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]   ' Las 8 notas Midi a ejecutar
 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_time = 0
   .real_time = 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 del suono della nota musicale:
   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