ALSA e Gambas - Il Tempo della coda

Da Gambas-it.org - Wikipedia.

Introduzione

Come è facilmente immaginabile, un brano potrà eventualmente prevedere una o più variazioni del Tempo metronomico durante la sua esecuzione. Si ha questo caso, quando tali variazioni del Tempo sono previste e stabilite all'interno del File Midi. L'impostazione del valore del nuovo Tempo metronomico avviene mediante la previsione di apposito Meta-evento (FF 51 03 nn nn nn ); laddove i tre dati nn nn nn esprimono il valore del Tempo metronomico in microsecondi.
Inoltre, il cambio del Tempo metronomico può essere effettuato anche manualmente a valontà.

ALSA dispone di varie funzioni per poter gestire il Tempo metronomico di un brano musicale, ossia la velocità di una coda di eventi.
Il Tempo Metronomico pianificato degli Eventi Midi è definito in:

  • Tempo Midi che determina il tempo in microsecondi;
  • Tick (PPQN - pulse per quarter note) che determina il Tempo per tick. [nota 1]

Il Tempo Metronomico di un brano musicale viene gestito in microsecondi [nota 2] per movimenti (beat), ossia per nota da un quarto (semiminima), mediante la funzione di ALSA "snd_seq_queue_tempo_set_tempo()". Solitamente, si preferisce impostare il Tempo in battute per minuto (bpm - beat per minute).

L'impostazione del Tempo in Tick (PPQN) definisce la risoluzione dei tick, ed avviene mediante la funzione "snd_seq_queue_tempo_set_ppq()". Esso è impostato di default a 96 tick per nota da un quarto; ossia per ogni nota da un quarto (corrispondente al valore temporale in musica di una semiminima) vi sono 96 tick.
Inoltre, ALSA imposta di default il valore dei movimenti per minuto (bpm - beat per minute) a 120, che è pari a 500000 microsecondi = (60 * 1000000) / 120.
Da notare che l'impostazione in PPQN non può essere modificata mentre la Coda sta scorrendo, pertanto quel parametro deve essere impostato prima della partenza della Coda.

E' invece possibile modificare il tempo metronomico della Coda degli Eventi Midi ALSA, mentre sta scorrendo, o inviando un Evento Midi del tipo del Tempo Metronomino con il nuovo valore in microsecondi dei BpM, oppure usando la funzione di ALSA "snd_seq_set_queue_tempo()".


La scrittura del codice in Gambas

Si devono innanzitutto distinguere due possibilità di impostazione del Tempo metronomico:

  • quello diretto con l'invio di un Evento Midi "Tempo" di ALSA;
  • quello effettuato mediante apposite funzioni esterne di ALSA.

Impostazione del Tempo metronomico con l'invio di un evento Midi Tempo Alsa

In questo caso il valore del Tempo metronomico avviene attraverso l'invio ad Alsa di uno specifico evento con valore Type uguale a 35, corrispondente alla costante di Alsa:

SND_SEQ_EVENT_TEMPO = 35

Successivamente sulla base del suo Tempo Delta assoluto (ossia posto correttamente nell'ordine crescente della coda di eventi Midi da inviare), invieremo ad ALSA l'Evento Tempo metronomico come un qualsiasi Evento Midi di ALSA, avendo l'accortezza, però, di attribuirgli e di accompagnarlo con i seguenti valori:

* type = SND_SEQ_EVENT_TEMPO (questo valore è una Costante di Alsa uguale a 35);
* flags = 0 (temporizzazione in Tick Midi) oppure 1 (temporizzazione oraria);
* tag  = 0
* queue = il numero della coda ottenuto nel modo consueto;
* tick_o_tv_sec = il "Timestamp" di questo Evento Midi in "tick Midi" oppure in "secondi";
* tv_nsec = il "Timestamp" di questo Evento Midi in "nanosecondi" se flags = 1;
* source_client = identificato nel modo consueto;
* source_port = identificata nel modo consueto;
* dest_client = SND_SEQ_CLIENT_SYSTEM (questo valore è una Costante di ALSA uguale a 0);
* dest_port = SND_SEQ_PORT_SYSTEM_TIMER (questo valore è una Costante di Alsa uguale a 0);
* channel = 0;
* note = 0;
* velocity = 0;
* off_velocity = 0;
* param = il valore del Tempo metronomico qui è sempre e solo espresso in microsecondi;
* value = 0.

Calcolo per ottenere il valore da passare all'Evento Midi ALSA del Tempo Metronomico

Per il calcolo del valore da passare al campo param della Struttura dell'Evento Midi di ALSA bisogna distinguere:

se l'impostazione del membro flags è in tick Midi, allora il valore da passare all'Evento Midi ALSA del Tempo Metronomico sarà ottenuto con una delle seguenti modalità:

tempo_definitivo = Round(240000000 / (bmp * (96 / 24)))

oppure:

tempo_definitivo = CInt(6000000000 / (bmp * 96))

oppure:

tempo_definitivo = CInt((6e7 / (bmp * 96)) * 96) = CInt(6e7 / bmp)

Se l'impostazione del membro flags è invece in real time (tempo orario), allora non è possibile cambiare il Tempo metronomico della Coda di ALSA, restando necessario così agire sulla temporizzazione dei singoli Eventi Midi.

Impostazione del Tempo metronomico mediante apposite funzioni esterne di ALSA

In quest'altro caso l'impostazione del Tempo metronomico avviene mediante una serie di apposite funzioni esterne di Alsa.

Come si è detto sopra, la risoluzione del Tempo Delta di ALSA è predefinita sul valore di 96 tick per quarto (semiminima) di battura (Misura).
E' possibile modificare questo valore mediante la funzione esterna "snd_seq_queue_tempo_set_ppq()". Ciò è richiesto in particolare nel caso di elaborazione di dati provenienti da file Midi, i quali contengono alla fine del proprio "Blocco d'intestazione" (MThd) due byte per l'impostazione appunto della risoluzione del Tempo Delta.

Qualora invece non si debba apportare una modifica del valore della risoluzione del Tempo Delta, ovviamente si lascerà il valore predefinito di ALSA.

Calcolo per ottenere il valore da passare all'Evento Midi ALSA del Tempo Metronomico

Se l'impostazione del membro flags è in tick Midi, allora il valore da passare all'Evento Midi ALSA del Tempo Metronomico dovrà essere ottenuto attraverso la trasformazione del Tempo da BpM in "microsecondi" (ossia quanti microsecondi dura una nota da un quarto), adottando una delle seguenti modalità:

 tempo_definitivo = Round(240000000 / (bmp * (risoluzione_TD / 24)))

oppure:

tempo_definitivo = CInt(6000000000 / (bmp * risoluzione_TD))

oppure:

tempo_definitivo = CInt((6e7 / (bmp * risoluzione_TD)) * risoluzione_TD) = CInt(6e7 / bmp)

Da sottolineare che la risoluzione del Tempo Delta (TΔ) è importante in modo particolare nel caso di elaborazione ed esecuzione dei dati derivanti da uno file Midi standard (smf), considerato che il Tempo Delta condiziona il rapporto temporale e che può variare da file a file (è sempre indicato come valore a 16-bit in ordine Little-Endiannel blocco d'intestazione del file Midi nei byte d'indice 12 e 13).

Un esempio astratto

' int snd_seq_queue_tempo_malloc (snd_seq_queue_tempo_t ** ptr)
Private Extern snd_seq_queue_tempo_malloc(ptr As Pointer) As Integer
' void snd_seq_queue_tempo_set_ppq (snd_seq_queue_tempo_t * info, int ppq)
Private Extern snd_seq_queue_tempo_set_ppq(info As Pointer, ppq As Integer) As Integer
' void snd_seq_queue_tempo_set_tempo (snd_seq_queue_tempo_t * info, unsigned int tempo)
Private Extern snd_seq_queue_tempo_set_tempo(info As Pointer, tempo As Integer) As Integer
' int snd_seq_set_queue_tempo (snd_seq_t * seq, int q, snd_seq_queue_tempo_t * tempo)
Private Extern snd_seq_set_queue_tempo(seq As Pointer, q As Integer, tempo As Pointer) As Integer
' void snd_seq_queue_tempo_free (snd_seq_queue_tempo_t * obj)
Private Extern snd_seq_queue_tempo_free(obj As Pointer) As Integer

Public Sub ImpostaTempoMetronomico(bpm As Integer)
 
 Dim qtempo As Pointer
 Dim tempo_definitivo As Integer

 snd_seq_queue_tempo_malloc(varPtr(qtempo))

 tempo_definitivo = CInt((6e7 / (bmp * risoluzione_TD)) * risoluzione_TD)
 
 snd_seq_queue_tempo_set_tempo(qtempo, tempo_definitivo)
 
 snd_seq_queue_tempo_set_ppq(qTempo, risoluzione_TD)

 snd_seq_set_queue_tempo(handle, queue, qtempo)

 snd_seq_queue_tempo_free(qtempo)
 
End

La funzione di ALSA "snd_seq_queue_tempo_malloc(snd_seq_queue_tempo_t ** ptr)" riserva della memoria per la gestione del tempo da parte delle funzioni di ALSA, e ritorna un puntatore. In questo caso ALSA riserva la memoria necessaria per la funzione, e ritorna un puntatore (segnato dal secondo dei due asterischi). Il primo asterisco è necessario perché ALSA deve sapere dove scrivere il suo puntatore per passarlo poi all'applicativo. Quindi avremo un puntatore ad un puntatore. Infatti, ogni volta che una funzione C scrive qualcosa, è necessario passargli un puntatore. Se ALSA deve riempire un puntatore, bisognerà passarle un puntatore a questo puntatore. Per passare, dunque, un indirizzo del puntatore (cioè un puntatore ad un puntatore) ad ALSA, useremo la funzione di Gambas "VarPtr() ".

La funzione "snd_seq_queue_tempo_set_ppq()" imposta il valore della risoluzione del Tempo Delta alla Coda degli Eventi Midi Alsa.

La funzione "snd_seq_set_queue_tempo(snd_seq_t * seq, int q, snd_seq_queue_tempo_t * tempo)" imposta il tempo di una coda.
Laddove:

  • seq è l'handle del sequencer ALSA;
  • q è è il numero identificativo della Coda, di cui si deve cambiare il Tempo Metronomico;
  • tempo è un puntatore ed un'informazione relativa al Tempo Metronomico.

La funzione "snd_seq_queue_tempo_free()" libera l'area di memoria precedentemente allocata.


Un esempio pratico

Mostriamo di seguito un esempio pratico in ambiente grafico, nel quale:
- vi sono due Classi, una principale e un'altra, chiamata CAlsa per la gestione delle risorse esterne di ALSA;
- gli Eventi Midi ALSA sono impostati con temporizzazione in modalità tick Midi;
- gli Eventi Midi sono inviati in un ciclo continuo grazie all'uso di un Evento Midi finale del tipo "Echo";
- all'inizio della Coda degli Eventi Midi viene inviato anche un Evento Midi del tipo del "Tempo" metronomico;
- mediante uno Slider, posto sul Form, è possibile cambiare il valore metronomico del Tempo.

Nella Classe principale v'è il seguente codice:

Private strum 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"]

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 _new()

' Imposta il valore metronomico massimo e quello minimo rappresentabili dallo "Slider":
 With Slider1
   .MaxValue = 340
   .MinValue = 30
 End With

End


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
   alsa.Descrittori()
' Assegna un valore iniziale del Tempo Metronomico allo "Slider":
   Slider1.Value = 180
' Assegna un valore iniziale del Tempo Metronomico alla "Coda" degli Eventi Midi:
   alsa.TempoMetronomico(0, 0, 0, (60 * 1000000) / Slider1.Value, 0)
   InviaEventiMidi()
 Else
   With alsa
' Invia un messaggio "Control Change" di "All notes off":
     .ControlChange(0, 0, 0, 123, 0)
     .Flush()
     .StopCoda()
   End With
   Me.Close
 Endif

End


Private Procedure InviaEventiMidi()

 With alsa
' Avvia la "Coda" degli Eventi Midi ALSA:
   .AvvioCoda()
   .ControlChange(0, 0, 0, 7, 50)
   .ProgramChange(0, 0, 0, 0, strum.Find("Violin"))
   .NoteON(0, 0, 0, 64, 100)
   .NoteOFF(0, 96, 0, 64, 0)

   .ProgramChange(0, 0, 0, 0, strum.Find("Acoustic Guitar (nylon)"))
   .NoteON(0, 96, 0, 66, 100)
   .NoteOFF(0, 192, 0, 66, 0)
  
   .ProgramChange(0, 182, 0, 0, strum.Find("Flute"))
   .NoteON(0, 192, 0, 68, 100)
   .NoteOFF(0, 288, 0, 68, 0)
  
   .ProgramChange(0, 288, 0, 0, strum.Find("Acoustic Guitar (nylon)"))
   .NoteON(0, 288, 0, 66, 100)
   .NoteOFF(0, 384, 0, 66, 0)
' Invia l'Evento Midi Echo:
   .Echo(0, 384)
' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda":
   .Flush()
 End With

End


Public Sub Alsa_EventoEcho()

 InviaEventiMidi()

End


Public Sub Slider1_Change()

 alsa.ImpostaTempo(Slider1.Value)
 Me.Title = "Tempo = " & CStr(Slider1.Value)

End

La Classe secondaria, che chiameremo "CAlsa", sarà la seguente:

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
Private in_port As Integer
Private p As Pointer
Private st As Stream
Private fl As File
Private Const POLLIN As Short = 1
Event EventoEcho


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_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_CAP_WRITE As Integer = 2
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_CLIENT_SYSTEM As Integer = 0
Private Const SND_SEQ_PORT_SYSTEM_TIMER As Integer = 0
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,
             SND_SEQ_EVENT_TEMPO = 35, SND_SEQ_EVENT_ECHO = 50

' 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_poll_descriptors_count (snd_seq_t * seq, short events)
' Returns the number of poll descriptors.
Private Extern snd_seq_poll_descriptors_count(seq As Pointer, events As Short) As Integer

' int snd_seq_poll_descriptors (snd_seq_t * seq, struct pollfd * pfds, unsigned int space, short events)
' Get poll descriptors.
Private Extern snd_seq_poll_descriptors(seq As Pointer, pfds As Pointer, space As Integer, events As Short) As Integer

' int snd_seq_event_input (snd_seq_t * seq, snd_seq_event_t ** ev)
' Retrieve an event from sequencer.
Private Extern snd_seq_event_input(seq As Pointer, ev As Pointer) As Integer

' int snd_seq_queue_tempo_malloc (snd_seq_queue_tempo_t ** ptr)
' Allocate an empty snd_seq_queue_tempo_t using standard malloc.
Private Extern snd_seq_queue_tempo_malloc(ptr As Pointer) as Integer

' void snd_seq_queue_tempo_set_ppq (snd_seq_queue_tempo_t * info, int ppq)
' Set the ppq of a queue_status container.
Private Extern snd_seq_queue_tempo_set_ppq(info As Pointer, ppq As Integer)

' void snd_seq_queue_tempo_set_tempo (snd_seq_queue_tempo_t * info, unsigned int tempo)
' Set the tempo of a queue_status container.
Private Extern snd_seq_queue_tempo_set_tempo(info As Pointer, tempo As Integer)

' int snd_seq_set_queue_tempo (snd_seq_t * seq, int q, snd_seq_queue_tempo_t * tempo)
' Set the tempo of the queue.
Private Extern snd_seq_set_queue_tempo(seq As Pointer, q As Integer, tempo As Pointer) As Integer

' void snd_seq_queue_tempo_free (snd_seq_queue_tempo_t * obj)
' Frees a previously allocated snd_seq_queue_tempo_t.
Private Extern snd_seq_queue_tempo_free(obj As Pointer)

' 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_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, nome)
 id = snd_seq_client_id(handle)
 Print "Alsa ClientID = "; id

' Crea la Porta di Uscita dei dati Midi del nostro Client ALSA:
 s_port = snd_seq_create_simple_port(handle, "Uscita dati", 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))

' Crea la Porta di Entrata dei dati Midi del nostro Client ALSA:
 in_port = snd_seq_create_simple_port(handle, "Entrata dati", SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)
 If err < 0 Then error.Raise("Errore: " & snd_strerror(in_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 TempoMetronomico(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_TEMPO
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = SND_SEQ_CLIENT_SYSTEM
   .dest_port = SND_SEQ_PORT_SYSTEM_TIMER
   .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 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 Echo(flg As Byte, tick As Integer)

 Dim ev_midi As New Snd_seq_event_t
 Dim err As Integer

 With ev_midi
   .type = SND_SEQ_EVENT_ECHO
   .flags = flg
   .tick_o_tv_sec = tick
   .queue = que
   .source_client = id
   .source_port = s_port
   .dest_client = id
   .dest_port = in_port
 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))

 st.Close
 fl.Close
 Free(p)    ' Dealloca la memoria precedentemente riservata
 p = 0      ' Assicura che comunque il Puntatore non punti ad un indirizzo di memoria rilevante
 snd_seq_close(handle)

End


Public Procedure Descrittori()

 Dim pdc, i, fd As Integer

 pdc = snd_seq_poll_descriptors_count(handle, POLLIN)
 p = Alloc(SizeOf(gb.Pointer), pdc)

 snd_seq_poll_descriptors(handle, p, pdc, POLLIN)
 st = Memory p For Read
 For i = 0 To pdc - 1
   Seek #st, i * SizeOf(gb.Pointer)
   Read #st, fd
   fl = Open "." & CStr(fd) For Read Watch
 Next

End


Public Sub File_Read()

 Dim ev As Pointer

' Funzione di ALSA che restiruisce i veri dati utili degli "Eventi Midi":
 snd_seq_event_input(handle, VarPtr(ev))

 If Byte@(ev) == SND_SEQ_EVENT_ECHO Then Raise EventoEcho

End


Public Sub ImpostaTempo(modificaTempo As Integer)

 Dim qtempo As Pointer
 Dim tempoDefinitivo As Integer

 snd_seq_queue_tempo_malloc(VarPtr(qtempo))

' Trasforma il Tempo metronomico da BpM in Microsecondi (ossia quanti microsecondi dura una nota da un quarto):
 tempoDefinitivo = 6e7 / modificaTempo

 snd_seq_queue_tempo_set_tempo(qtempo, tempoDefinitivo)
 snd_seq_queue_tempo_set_ppq(qTempo, 96)
 snd_seq_set_queue_tempo(handle, que, qtempo)
 snd_seq_queue_tempo_free(qtempo)

End


Note

[1] Il Tick è l'unità di misura più piccola nella risoluzione del sequencer.

[2] Il microsecondo rappresenta un milionesimo di secondo.