Alsa e Gambas: Invio dei dati senza connessione della porta del programma con il Softsynth

Da Gambas-it.org - Wikipedia.

Per inviare utilmente dati Midi ad altro Client di ALSA, non è necessario connettere i due Client (l'applicativo Gambas ed il Softsynth) mediante la funzione esterna di ALSA "snd_seq_connect_to()", purché si presti attenzione ad alcuni elementi che integrano due casi.

Di seguito vedremo tre casi possibili, che saranno dimostrati con esempi pratici.
Se nel proprio sistema è stato installato il softsynth Fluidsynth, allora i programmi, appresso descritti, dovrebbero connettersi automaticamente a quel softsynth, consentendo così la riproduzione dei suoni. Qualora non avvenga, procedere come segue:
1) da Terminale lanciare questa riga: ":~$ fluidsynth reload 0", o meglio: ":~$ fluidsynth /percorso/del/file/soundfont/.sf2"
2) senza chiudere il Terminale, verificare nell'utilità "Monitor di sistema" che Fluidsynth sia presente fra i processi attivi;
3) se Fluidsynth è presente fra i processi, verificare anche che esso sia presente al num. 128 nel file /proc/asound/seq/clients;
4) in caso affermativo - senza chiudere il Terminale - lanciare il codice sopra esposto.


Casi

1° caso

In tale caso si deve sempre utilizzare la funzione esterna di ALSA: "snd_seq_event_output_direct()" per l'invio diretto di ciascun Evento Midi ALSA.
Inoltre il codice dovrà prevedere che al membro dest_client e a quello dest_port della Struttura relativa agli eventi Midi dovranno essere rispettivamente assegnati i valori dell'Id e della porta del Client (Softsynth), al quale l'applicativo Gambas dovrà inviare i dati Midi.
Il dispositivo destinatario degli Eventi Midi ALSA sarà individuato, dunque, esclusivamente attraverso il suo numero identificativo e il numero della sua porta quale Client di ALSA.

Mostriamo un esempio astratto facendo uso di una Struttura dichiarata ad immagine della citata Struttura di ALSA snd_seq_event_t:

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0


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
 
' 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 Pointer

' 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_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) 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 handle As Pointer
 Dim rit As Integer
 Dim id, porta, que, b As Byte
 Dim ev As New Snd_seq_event_t

 rit = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 id = snd_seq_client_id(handle)
 If id < 0 Then Error.Raise("Errore: " & String@(snd_strerror(rit)))

 porta = snd_seq_create_simple_port(handle, "porta_", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC Or SND_SEQ_PORT_TYPE_APPLICATION)
 If porta < 0 Then Error.Raise("Impossibile creare la porta di uscita dati ALSA !")

 que = snd_seq_alloc_queue(handle)

 With ev
   .type = ...etc...
   .queue = que
   .source_client = id
   .source_port = porta
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   ...etc...
 End With

 snd_seq_event_output_direct(handle, ev)

 snd_seq_close(handle)

End


2° caso

Nel secondo caso si dovrà tenere conto di quanto segue:

  • non sono utilizzate le funzioni esterne "snd_seq_client_id()" e "snd_seq_create_simple_port()", usate invece nel caso precedente, specifiche di ALSA per la creazione della porta del nostro Client applicativo in Gambas;
  • pertanto non si useranno, lasciandoli al valore predefinito di zero, i membri .source_client e ..source_port della Struttura relativa agli eventi Midi;
  • neppure sarà usata la funzione esterna "snd_seq_alloc_queue()" di ALSA;
  • al membro queue della Struttura relativa agli eventi Midi dovrà essere assegnato il valore della Costante ALSA "SND_SEQ_QUEUE_DIRECT" (253);
  • al membro .dest_id e a quello .dest_port della Struttura relativa agli eventi Midi dovranno essere rispettivamente assegnati i valori dell'Id e della porta del Client (Softsynth), al quale l'applicativo Gambas dovrà inviare i dati Midi.

Mostriamo un esempio astratto:

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0


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_QUEUE_DIRECT As Byte = 253
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 Pointer

' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) 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 handle As Pointer
 Dim rit As Integer
 Dim ev As New Snd_seq_event_t

 rit = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 With ev
   .type = ...etc...
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   ...etc...
 End With
     
 snd_seq_event_output_direct(handle, ev)

 snd_seq_close(handle)
 
End


Esempio più essenziale per invio diretto degli Eventi Midi ALSA

Mostriamo ora la modalità più breve ed essenziale per inviare ad altro Client di Alsa alcuni eventi Midi, e nella quale non è prevista la creazione di porte del nostro Client applicativo.
Si utilizzerà ovviamente la funzione esterna di ALSA "snd_seq_event_output_direct()" per l'invio diretto di ciascun Evento Midi ALSA.

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0

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_QUEUE_DIRECT As Byte = 253
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF

' 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 Pointer

' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) 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 seq As Pointer
 Dim evento As New Snd_seq_event_t
 Dim rit As Integer

 snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 With evento
   .type = SND_SEQ_EVENT_NOTEON
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   .channel = 0
   .note = 64
   .velocity = 100
 End With

 snd_seq_event_output_direct(seq, evento)

' 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

 evento.type = SND_SEQ_EVENT_NOTEOFF
 evento.velocity = 0

 snd_seq_event_output_direct(seq, evento)

 snd_seq_close(seq)

End

Quest'altro esempio, simile al precedente, è realizzato in ambiente grafico: sul Form sono posti uno SpinBox e un Button.
Cliccando sul Button si ascolterà il suono della nota Midi espressa dal valore mostrato dallo SpinBox che potrà essere variata manualmente.

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0
Private seq 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_QUEUE_DIRECT As Byte = 253
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF

' 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 Pointer

' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) 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 Form_Open()

 SpinBox1.MaxValue = 127
 SpinBox1.Value = 64

End


Public Sub Button1_MouseDown()

 Dim rit As Integer

 snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 InvioMessaggioMidi(SND_SEQ_EVENT_NOTEON, SpinBox1.Value, 100)

End


Public Sub Button1_MouseUp()

 InvioMessaggioMidi(SND_SEQ_EVENT_NOTEOFF, SpinBox1.Value, 0)

 snd_seq_close(seq)

End


Private Procedure InvioMessaggioMidi(tipo As Byte, nota As Byte, veloc As Byte)

 Dim evento As Snd_seq_event_t

 With evento = New Snd_seq_event_t
   .type = tipo
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = CLIENT_DESTINATARIO
   .dest_port = PORTA_DESTINATARIO
   .channel = 0
   .note = nota
   .velocity = veloc
 End With

 snd_seq_event_output_direct(seq, evento)

End

In questo esempio, per inviare l'evento Midi di ALSA, invece della Struttura snd_seq_event_t sarà utilizzato un vettore di tipo Byte[].

Private Const CLIENT_DESTINATARIO As Byte = 128   ' Solitamente l'identificativo è 128
Private Const PORTA_DESTINATARIO As Byte = 0
Private seq As Pointer


Library "libasound:2.0.0"

Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1
Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF

' 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 Pointer

' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev)
' Output an event directly to the sequencer NOT through output buffer.
Private Extern snd_seq_event_output_direct(handle As Pointer, ev 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 Form_Open()

 SpinBox1.MaxValue = 127
 SpinBox1.Value = 64

End


Public Sub Button1_MouseDown()

 Dim rit As Integer

 snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & String@(snd_strerror(rit)))

 InvioMessaggioMidi(SND_SEQ_EVENT_NOTEON, SpinBox1.Value, 100)

End


Public Sub Button1_MouseUp()

 InvioMessaggioMidi(SND_SEQ_EVENT_NOTEOFF, SpinBox1.Value, 0)

 snd_seq_close(seq)

End


Private Procedure InvioMessaggioMidi(tipo As Byte, nota As Byte, veloc As Byte)

 Dim ev As New Byte[28]

' Vengono assegnati al vettore i valori fondamentali per generare l'evento Midi di ALSA:
 ev[0] = tipo
 ev[3] = SND_SEQ_QUEUE_DIRECT
 ev[14] = CLIENT_DESTINATARIO
 ev[15] = PORTA_DESTINATARIO
 ev[17] = SpinBox1.Value
 ev[18] = 100

 snd_seq_event_output_direct(seq, ev.Data)

End


3° caso

In questo caso si farà uso delle funzioni esterne di ALSA "snd_seq_event_output()" e "snd_seq_drain_output()" per l'invio di ciascun Evento Midi ALSA.

Nell'esempio pratico che segue viene rappresentata sul Form una tastierina Midi che può inviare dati Midi grezzi attraverso ALSA al Client-softsynth (ad esempio QSynth). Premendo un tasto di questa tastiera Midi interna, udiremo il suono della sua nota con il timbro dello strumento musicale prescelto in un ComboBox sul Form.

Private cb As ComboBox
Private Const ALTEZZA_TASTI_BIANCHI As Single = 0.35
Private bb As New Byte[3]
Private instrumenta As String[] = ["Acustic Grand Piano", "Bright Acustic Piano", "Electric Grand Piano", "Honky-tonk",
  "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone",
  "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Hammond Organ", "Percussive Organ", "Rock Organ", "Church Organ",
  "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)",
  "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar(muted)", "Overdriven Guitar", "Distortion Guitar",
  "Guitar Harmonics", "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1",
  "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings",
  "Pizzicato Strings", "Orchestral Harp", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1",
  "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet",
  "French Horn", "Brass Section", "Synth Brass 1", "Synth Brass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax",
  "Oboe", "English Horn", "Basson", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Bottle Blow", "Shakuhachi",
  "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (caliope lead)", "Lead 4 (chiff lead)",
  "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8(brass+lead)", "Pad 1 (new age)", "Pad 2 (warm)",
  "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)",
  "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)",
  "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo",
  "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise",
  "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot"]


Public Sub Form_Open()

 With Me
   .W = Screen.AvailableWidth * 0.5
   .H = Screen.AvailableHeight * 0.2
   .Center
 End With
 CreaClient()
 With cb = New ComboBox(Me) As "Combo"
   .W = 160
   .H = 25
   .X = (Me.W * 0.94) - .W
   .Y = 0
   .List = instrumenta
   .Placeholder = "Strumenti musicali GM"
 End With

 CreaTastiera()

End

Private Procedure CreaTastiera()

 Dim pn As Panel
 Dim neri As New Byte[40]
 Dim tasti As Button[]
 Dim b, c, n As Byte
 
 With pn = New Panel(Me)
   .W = Me.W * 0.88
   .H = Me.H * 0.2
   .X = (Me.W / 2) - (pn.W / 2)
   .Y = Me.H * 0.2
   .Border = Border.Sunken
   .Background = &8b4513
 End With

 Repeat 
   neri[b] = 25 + (12 * b / 5)
   neri[b + 1] = 27 + (12 * b / 5)
   neri[b + 2] = 30 + (12 * b / 5)
   neri[b + 3] = 32 + (12 * b / 5)
   neri[b + 4] = 34 + (12 * b / 5)
   b += 5
 Until b == neri.Count

 tasti = New Button[109]

 For t As Short = 0 To tasti.Max
   With tasti[t] = New Button(Me) As "Tasti"
     .W = 0
     If t > 23 Then
       If neri.Exist(t) Then  ' Imposta i tasti neri
         .W = Me.W * 0.013
         .H = ((Me.H * ALTEZZA_TASTI_BIANCHI) * 66.66) / 100
         Select Case t 
           Case 25 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 27 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 30 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 32 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
           Case 34 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.025) / 2))
             Inc n
         End Select
         .Y = Me.H * 0.375
         .Background = Color.Black
         .Tag = t
       Else
' Imposta i tasti bianchi:
         .W = Me.W * 0.018
         .H = Me.H * ALTEZZA_TASTI_BIANCHI
         .X = (.W * c) + (Me.W / 16)
         .Y = Me.H * 0.37
         .Background = Color.White
         .Tag = t
         .Lower
         Inc c
       Endif
     Endif 
   End With
 Next

End


Public Sub Tasti_MouseDown()

 bb[0] = 0
 bb[1] = Last.Tag
 bb[2] = &64

 InvioMIDI(SND_SEQ_EVENT_NOTEON, bb)

 If Last.Background = Color.Black Then Last.Background = Color.DarkGray
 Me.Title = "Nota Midi:  " & Last.Tag

End

Public Sub Tasti_MouseUp()

 bb[0] = 0
 bb[1] = Last.Tag
 bb[2] = 0

 InvioMIDI(SND_SEQ_EVENT_NOTEOFF, bb)

 If Last.Background = Color.DarkGray Then Last.Background = Color.Black
 Me.Title = " "

End

'''''''''''''''''''''''''''''''''''''''''''''''

Private seq As Pointer


Library "libasound:2"

Public Struct snd_seq_event_t   ' Struttura dell'Evento Midi di ALSA
  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_QUEUE_DIRECT As Byte = 253
Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_PGMCHANGE = 11

' 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_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


Private Procedure CreaClient()

 Dim rit As Integer

 rit = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0)
 If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & snd_strerror(rit))

End


Public Sub Combo_Change()  ' Imposta lo strumento musicale

 Dim evento As New Snd_seq_event_t

 With evento
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = 128
   .dest_port = 0
   .channel = 0
 End With

' Imposta il tipo di strumento musicale mediante il Messaggio Midi "Program-Change":
 Messaggio(evento, SND_SEQ_EVENT_PGMCHANGE, [0, 0, 0], cb.Index)

End


Private Procedure InvioMIDI(tipo As Byte, mid As Byte[])

 Dim evento As New Snd_seq_event_t

 With evento
   .queue = SND_SEQ_QUEUE_DIRECT
   .dest_client = 128
   .dest_port = 0
   .channel = mid[0]
 End With

' Imposta il Messaggio Midi:
 Messaggio(evento, tipo, mid, 0)

End


Private Procedure Messaggio(ev As Snd_seq_event_t, tipo As Byte, nota As Byte[], strum As Integer)

 With ev
   .type = tipo
   .channel = nota[0]
   .note = nota[1]
   .velocity = nota[2]
   .value = strum
 End With

' Inserisce l'Evento di ALSA nel buffer:
 snd_seq_event_output(seq, ev)

' Invia l'Evento:
 snd_seq_drain_output(seq)

End


Public Sub Form_Close()

 snd_seq_close(seq)

End