Alsa e Gambas: Invio dati con l'uso delle Strutture
Indice
Preambolo
In questa pagina verrà preso in considerazione l'uso delle Strutture per l'invio degli Eventi Midi ad ALSA.
Le Strutture, introdotte in Gambas con la versione 3.0, possono essere utilizzate per scrivere e gestire i messaggi Midi. Infatti dichiarando una Struttura (Struct) ci si riserva una quantità di memoria, definita dal tipo (byte, short, integer, etc.) dei valori dichiarati in ciascun membro della Struttura.
Dichiarando poi una variabile del tipo di quella Struttura, si ha automaticamente una zona di memoria utilizzabile.
Scrittura dei dati dei messaggi Midi nelle Strutture
Inseriremo una Struttura modello all'inizio della classe secondaria CAlsa.class. Porremo quindi, quali campi della struttura-tipo, i dati necessari per la definizione degli eventi Midi, come richiesti da ALSA, attribuendo a ciascun campo il tipo di dato desiderato da ALSA nel rispetto dell'ordine previsto dei dati, nonché del numero del byte al quale ciascun dato dovrà essere assegnato. Inseriremo non solo i dati comuni per tutti i messaggi Midi, ma anche quelli previsti per gli specifici messaggi:
In rosso sono rappresentati i membri che contengono dati identici per qualunque Evento Midi ALSA; In verde sono rappresentati i membri che contengono dati relativi allo specifico Evento Midi ALSA. struct snd_seq_event_t { Public Struct Snd_seq_event_t snd_seq_event_type_t type type As Byte byte di indice 0 unsigned char flags flags As Byte unsigned char tag tag As Byte unsigned char queue queue As Byte snd_seq_timestamp_t time ↳ snd_seq_tick_time_t / snd_seq_real_time_t ↳ tick ↳ tv_sec tick_o_tv_sec As Integer ↳ tv_nsec tv_nsec As Integer snd_seq_addr_t source ↳ unsigned char client source_client As Byte ↳ unsigned char port source_port As Byte snd_seq_addr_t dest ↳ unsigned char client dest_client As Byte ↳ unsigned char port dest_port As Byte snd_seq_ev_note_t note ↳ unsigned char channel channel As Byte byte di indice 16 ↳ unsigned char note note As Byte byte di indice 17 ↳ unsigned char velocity velocity As Byte byte di indice 18 ↳ unsigned char off_velocity off_velocity As Byte byte di indice 19 unsigned int param param As Integer byte di indice 20 signed int value value As Integer byte di indice 24 } End Struct
I dati appartenenti agli specifici Messaggi Midi non vengono utilizzati tutti insieme da ciascun Evento Midi ALSA, ma solo alcuni di essi a seconda dello specifico Messaggio. Ovviamente nella Struttura-tipo essi saranno comunque posti e dichiarati, al fine di consentirne l'uso.
In Gambas si istanzierà una variabile del tipo della Struttura snd_seq_event_t, sopra descritta, con la quale gestire gli specifici Eventi Midi di ALSA, assegnando a ciascun membro (sia quelli comuni, sia quelli specifici di ogni tipo di Evento Midi ALSA) il relativo valore. Ovviamente si potrà evitare di richiamare i membri che eventualmente non devono essere lasciati con valore zero.
Riassunto delle fasi di creazione, allocamento e invio degli Eventi Midi ad ALSA
Prima di mostrare dei codici esemplificativi sulle varie modalità di invio degli'Eventi Midi al sistema ALSA, è opportuno ricordare quanto descritto nela capitolo precedente "ALSA e Gambas - Gestione dei Messaggi Midi standard".
Dunque possiamo elencare per sommicapi le seguenti fasi da seguire:
1) comporre l'Evento Midi ALSA nei membri essenziali della sua Struttura snd_seq_event_t, ed in particolare attribuire la temporizzazione mediante il "Timestamp" in "tick Midi" o in "real time";
2) allocare la Coda degli Eventi Midi ALSA con la funzione esterna snd_seq_alloc_queue();
3) avviare il controllo della Coda degli Eventi Midi ALSA con la funzione esterna snd_seq_control_queue(seq, q, type, value, 0, ev), passandole come 3° argomento la Costante di ALSA "SND_SEQ_EVENT_START";
4) accodare gli Eventi Midi nel buffer mediante la funzione esterna snd_seq_event_output_buffer();
5) inviare al sistema ALSA mediante la funzione esterna snd_seq_drain_output() l'intera Coda degli Eventi Midi, memorizzati nel buffer, affinche siano processati;
6) arrestare la Coda degli Eventi Midi ALSA richiamando la funzione esterna snd_seq_control_queue(seq, q, type, value, 0, ev), passandole però questa volta come suo 3° argomento la Costante di ALSA "SND_SEQ_EVENT_STOP".
Esempi di codice
1° esempio pratico
Nel seguente esempio in ambiente grafico avremo la Classe principale "FMain.Class" e una Classe secondaria, che incapsula le risorse di ALSA da utilizzare.
Dichiareremo una Struttura, chiamata snd_seq_event_t, che farà da modello alla particolare variabile di quel tipo di Struttura da usare per la organizzazione di ciascun Evento Midi ALSA.
In questo esempio sarà effettuato un invio diretto e immediato di un singolo Evento Midi per volta: cliccando su un qualunque tasto della tastiera del computer, si invierà un Evento Midi "NoteON"; rilasciando il tasto si invierà un Evento Midi "NoteOFF", per far cesssare l'esecuzione della nota Midi prima avviata.
Saranno gestiti comunque in apertura del programma anche gli Eventi Midi "Control Change" e "Program Change".
Riguardo al Timestamp di ogni Evento Midi ALSA inviato, trattandosi di invio singolo e diretto con esecuzione immediata è indifferente la modalità scelta: il membro time della predetta Struttura snd_seq_event_t relativo al Timestamp del singolo Evento Midi sarà posto a zero.
Dunque avremo la Classe principale "FMain.Class":
Private Const id_dev As Integer = 128 ' 14 = midi out oppure 128 (solitamente) = softsynth Private Const p_dev As Integer = 0 ' porta del Client destinatario dei dati Midi: solitamente 0 Public alsa As CAlsa ' Classe che incapsula le funzioni ALSA Private bo As Boolean Public Sub Form_Open() ' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare: With alsa = New CAlsa As "Alsa" ' Apre ALSA e assegna un nome al Client: Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova") ' Sceglie la periferica (Softsynth) su cui suonare: .ImpostaDispositivo(id_dev, p_dev) End With ' Imposta il Volume gestendo l'Evento Midi "Control Change": alsa.ControlChange(0, 7, 64) ' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change": alsa.ProgramChange(0, 0, 44) End Public Sub Form_KeyPress() If bo Then Return ' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-ON": alsa.NoteON(0, 64, 100) alsa.Flush() bo = True End Public Sub Form_KeyRelease() ' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-OFF": alsa.NoteOFF(0, 64, 0) alsa.Flush() bo = False End Public Sub Form_Close() alsa.Chiude() End
Nella Classe secondaria, chiamata "CAlsa", avremo il seguente codice:
Private handle As Pointer Private id As Integer Private s_port As Integer Private que As Integer Private dclient As Byte Private dport As Byte Library "libasound:2.0.0" Public Struct snd_seq_event_t type As Byte flags As Byte tag As Byte queue As Byte tick_o_tv_sec As Integer tv_nsec As Integer source_client As Byte source_port As Byte dest_client As Byte dest_port As Byte channel As Byte note As Byte velocity As Byte off_velocity As Byte param As Integer value As Integer End Struct Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1 Private Const SND_SEQ_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2 Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576 Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND ' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode) ' Open the ALSA sequencer. Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer ' int snd_seq_set_client_name (snd_seq_t* seq, const char* name) ' Set client name. Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer ' int snd_seq_client_id (snd_seq_t * seq) ' Get the client id. Private Extern snd_seq_client_id(seq As Pointer) As Integer ' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type) ' Creates a port with the given capability and type bits. Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer ' int snd_seq_alloc_queue (snd_seq_t * seq) ' Allocate a queue. Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer ' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev) ' Output an event. Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer ' int snd_seq_drain_output (snd_seq_t * seq) ' Drain output buffer to sequencer. Private Extern snd_seq_drain_output(seq As Pointer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_seq_close (snd_seq_t * seq) ' Close the sequencer. Private Extern snd_seq_close(seq As Pointer) As Integer Public Function AlsaIni(nome As String) As String Dim err As Integer err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0) Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !") If err < 0 Then error.RAISE("Errore: " & snd_strerror(err)) snd_seq_set_client_name(handle, nome) id = snd_seq_client_id(handle) Print "Alsa ClientID = "; id s_port = snd_seq_create_simple_port(handle, "Uscita", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) Print "Porta del programma = "; s_port If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port)) que = snd_seq_alloc_queue(handle) If que < 0 Then error.Raise("Errore: " & snd_strerror(que)) Return CStr(id) & ":" & CStr(s_port) End Public Procedure ImpostaDispositivo(client As Integer, port As Integer) dclient = client dport = port End Public Procedure ControlChange(canale As Byte, prm As Integer, value As Integer) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_CONTROLLER .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .param = prm .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ProgramChange(canale As Byte, non_usato As Byte, value As Integer) Dim err As Integer Dim ev_midi As New Snd_seq_event_t With ev_midi .type = SND_SEQ_EVENT_PGMCHANGE .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteON(canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEON .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteOFF(canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEOFF .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Flush() Dim err As Integer err = snd_seq_drain_output(handle) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Chiude() snd_seq_close(handle) End
2° esempio pratico
Di seguito un programma simile al precedente, nel quale si farà uso di uno Slider per agire sul Messaggio Midi Pitch Bend.
Nella Classe principale avremo il seguente codice:
Private Const id_dev As Integer = 128 ' 14 = midi out oppure 128 (solitamente) = softsynth Private Const p_dev As Integer = 0 ' porta del Client destinatario dei dati Midi: solitamente 0 Public alsa As CAlsa ' Classe che incapsula le funzioni ALSA Private bo As Boolean Public Sub Form_Open() With Slider1 .MinValue = -8192 .MaxValue = 8192 End With ' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare: With alsa = New CAlsa As "Alsa" ' Apre ALSA e assegna un nome al Client: Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova") ' Sceglie la periferica (Softsynth) su cui suonare: .ImpostaDispositivo(id_dev, p_dev) End With ' Imposta il Volume gestendo l'Evento Midi "Control Change": alsa.ControlChange(0, 7, 64) ' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change": alsa.ProgramChange(0, 0, 44) End Public Sub Form_KeyPress() If bo Then Return ' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-ON": alsa.NoteON(0, 64, 100) alsa.Flush() bo = True End Public Sub Form_KeyRelease() ' Imposta la nota midi da eseguire gestendo l'Evento Midi "Note-OFF": alsa.NoteOFF(0, 64, 0) alsa.Flush() bo = False End Public Sub Slider1_Change() alsa.PitchBend(0, 0, Slider1.Value) alsa.Flush() End Public Sub Form_Close() alsa.Chiude() End
Nella Classe secondaria, chiamata "CAlsa", avremo il seguente codice:
Private handle As Pointer Private id As Integer Private s_port As Integer Private que As Integer Private dclient As Byte Private dport As Byte Library "libasound:2.0.0" Public Struct snd_seq_event_t type As Byte flags As Byte tag As Byte queue As Byte tick_o_tv_sec As Integer tv_nsec As Integer source_client As Byte source_port As Byte dest_client As Byte dest_port As Byte channel As Byte note As Byte velocity As Byte off_velocity As Byte param As Integer value As Integer End Struct Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1 Private Const SND_SEQ_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2 Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576 Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND ' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode) ' Open the ALSA sequencer. Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer ' int snd_seq_set_client_name (snd_seq_t* seq, const char* name) ' Set client name. Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer ' int snd_seq_client_id (snd_seq_t * seq) ' Get the client id. Private Extern snd_seq_client_id(seq As Pointer) As Integer ' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type) ' Creates a port with the given capability and type bits. Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer ' int snd_seq_alloc_queue (snd_seq_t * seq) ' Allocate a queue. Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer ' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev) ' Output an event. Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer ' int snd_seq_drain_output (snd_seq_t * seq) ' Drain output buffer to sequencer. Private Extern snd_seq_drain_output(seq As Pointer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_seq_close (snd_seq_t * seq) ' Close the sequencer. Private Extern snd_seq_close(seq As Pointer) As Integer Public Function AlsaIni(nome As String) As String Dim err As Integer err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0) Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !") If err < 0 Then error.RAISE("Errore: " & snd_strerror(err)) snd_seq_set_client_name(handle, nome) id = snd_seq_client_id(handle) Print "Alsa ClientID = "; id s_port = snd_seq_create_simple_port(handle, "Uscita", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) Print "Porta del programma = "; s_port If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port)) que = snd_seq_alloc_queue(handle) If que < 0 Then error.Raise("Errore: " & snd_strerror(que)) Return CStr(id) & ":" & CStr(s_port) End Public Procedure ImpostaDispositivo(client As Integer, port As Integer) dclient = client dport = port End Public Procedure ControlChange(canale As Byte, prm As Integer, value As Integer) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_CONTROLLER .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .param = prm .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ProgramChange(canale As Byte, non_usato As Byte, value As Integer) Dim err As Integer Dim ev_midi As New Snd_seq_event_t With ev_midi .type = SND_SEQ_EVENT_PGMCHANGE .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteON(canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEON .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteOFF(canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEOFF .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Flush() Dim err As Integer err = snd_seq_drain_output(handle) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure PitchBend(canale As Byte, prm As Integer, val As Integer) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_PITCHBEND .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .param = prm .value = val End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Stop() snd_seq_close(handle) End
Esempi con invio di più Eventi Midi ALSA temporizzati in sequenza
Vedremo di seguito due semplici esempi sull'uso del membro flags della Struttura snd_seq_event_t e della temporizzazione in "tick time" e in "real time".
Invio Eventi Midi con temporizzazione in "tick time"
Ricordiamo che la temporizzazione in ALSA nella modalità in "tick" Midi è attivata assegnando al membro flags della snd_seq_event_t la Costante "SND_SEQ_TIME_STAMP_TICK" di ALSA, mentre i valori temporali sono espressi appunto in "tick" Midi al membro tick del membro snd_seq_timestamp_t time della predetta Struttura.
Mostriamo un esempio pratico in ambiente grafico, nel quale cliccando su un "ToggleButton" si avvierà l'invio ad ALSA degli Eventi Midi e il loro processamento. Essi saranno eseguiti secondo la propria temporizzazione impostata nella Struttura snd_seq_event_t costitutiva.
Dunque avremo la Classe principale "FMain.Class":
Private Const id_dev As Integer = 128 ' 14 = midi out oppure 128 (solitamente) = softsynth Private Const p_dev As Integer = 0 ' porta del Client destinatario dei dati Midi: solitamente 0 Public alsa As CAlsa ' Classe che incapsula le funzioni ALSA Public Sub Form_Open() ' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare: With alsa = New CAlsa As "Alsa" ' Apre ALSA e assegna un nome al Client: Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova") ' Sceglie la periferica (Softsynth) su cui suonare: .ImpostaDispositivo(id_dev, p_dev) End With End Public Sub ToggleButton1_Click() If ToggleButton1.Value Then With alsa ' Avvia la "Coda" degli Eventi Midi ALSA: .AvvioCoda() ' Imposta il Volume gestendo l'Evento Midi "Control Change": .ControlChange(0, 0, 0, 7, 50) ' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change": .ProgramChange(0, 0, 0, 0, 44) ' Imposta la prima nota Midi da eseguire: .NoteON(0, 0, 0, 64, 100) ' Imposta la nota Midi da silenziare dopo 96 tick dall'inizio della "Coda": .NoteOFF(0, 96, 0, 64, 0) ' Imposta il suono di un altro strumento musicale dopo 150 tick dall'inizio della "Coda" (quindi crea una pausa): .ProgramChange(0, 182, 0, 0, 18) ' Imposta la nota Midi da eseguire dopo 182 tick dall'inizio della "Coda": .NoteON(0, 182, 0, 66, 100) ' Imposta la nota Midi da silenziare dopo 300 tick dall'inizio della "Coda": .NoteOFF(0, 300, 0, 66, 0) ' Imposta il suono di un altro strumento musicale dopo 300 tick dall'inizio della "Coda": .ProgramChange(0, 300, 0, 0, 18) ' Imposta la nota Midi da eseguire dopo 300 tick dall'inizio della "Coda": .NoteON(0, 300, 0, 68, 100) ' Imposta la nota Midi da silenziare dopo 500 tick dall'inizio della "Coda": .NoteOFF(0, 500, 0, 68, 0) ' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda": .Flush() End With Else alsa.StopCoda() Me.Close Endif End
Nella Classe secondaria, chiamata "CAlsa", vi sarà invece il seguente codice:
Private handle As Pointer Private id As Integer Private s_port As Integer Private que As Integer Private dclient As Byte Private dport As Byte Library "libasound:2.0.0" Public Struct snd_seq_event_t type As Byte flags As Byte tag As Byte queue As Byte tick_o_tv_sec As Integer tv_nsec As Integer source_client As Byte source_port As Byte dest_client As Byte dest_port As Byte channel As Byte note As Byte velocity As Byte off_velocity As Byte param As Integer value As Integer End Struct Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1 Private Const SND_SEQ_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2 Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576 Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_STOP = 32 ' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode) ' Open the ALSA sequencer. Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer ' int snd_seq_set_client_name (snd_seq_t* seq, const char* name) ' Set client name. Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer ' int snd_seq_client_id (snd_seq_t * seq) ' Get the client id. Private Extern snd_seq_client_id(seq As Pointer) As Integer ' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type) ' Creates a port with the given capability and type bits. Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer ' int snd_seq_alloc_queue (snd_seq_t * seq) ' Allocate a queue. Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer ' int snd_seq_control_queue(snd_seq_t * seq, int q, int type, int value, snd_seq_event_t * ev) ' Queue controls - start/stop/continue. Private Extern snd_seq_control_queue(seq As Pointer, q As Integer, type As Integer, value As Integer, ev As Pointer) As Integer ' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev) ' Output an event. Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer ' int snd_seq_drain_output (snd_seq_t * seq) ' Drain output buffer to sequencer. Private Extern snd_seq_drain_output(seq As Pointer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_seq_close (snd_seq_t * seq) ' Close the sequencer. Private Extern snd_seq_close(seq As Pointer) As Integer Public Function AlsaIni(nome As String) As String Dim err As Integer err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0) Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !") If err < 0 Then error.RAISE("Errore: " & snd_strerror(err)) snd_seq_set_client_name(handle, nome) id = snd_seq_client_id(handle) Print "Alsa ClientID = "; id s_port = snd_seq_create_simple_port(handle, "Uscita", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) Print "Porta del programma = "; s_port If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port)) que = snd_seq_alloc_queue(handle) If que < 0 Then error.Raise("Errore: " & snd_strerror(que)) Return CStr(id) & ":" & CStr(s_port) End Public Procedure ImpostaDispositivo(client As Integer, port As Integer) dclient = client dport = port End Public Procedure AvvioCoda() Dim err As Integer err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_START, 0, 0) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ControlChange(flg As Byte, tick As Integer, canale As Byte, prm As Integer, value As Integer) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_CONTROLLER .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .param = prm .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ProgramChange(flg As Byte, tick As Integer, canale As Byte, non_usato As Byte, value As Integer) Dim err As Integer Dim ev_midi As New Snd_seq_event_t With ev_midi .type = SND_SEQ_EVENT_PGMCHANGE .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteON(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEON .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteOFF(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEOFF .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Flush() Dim err As Integer err = snd_seq_drain_output(handle) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure StopCoda() Dim err As Integer err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_STOP, 0, 0) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) snd_seq_close(handle) End
Invio Eventi Midi con temporizzazione in "real time"
La temporizzazione in ALSA nella modalità in "real time" è attivata assegnando al membro flags della snd_seq_event_t la Costante "SND_SEQ_TIME_STAMP_REAL" di ALSA.
Ricordiamo che questa modalità di temporizzazione degli Eventi Midi di ALSA adotta il sistema di misura dell'orario standard (ore, minuti, secondi), assegnando il valore in "secondi" al membro tv_sec e in "nanosecondi" al membro tv_nsec della sotto-Struttura snd_seq_real_time_t del membro time della Struttura principale snd_seq_event_t costitutiva.
Mostriamo un esempio identico al precedente, ma con impostazione della temporizzazione in Clock Midi in secondi:
Dunque avremo la Classe principale "FMain.Class":
Private Const id_dev As Integer = 128 ' 14 = midi out oppure 128 (solitamente) = softsynth Private Const p_dev As Integer = 0 ' porta del Client destinatario dei dati Midi: solitamente 0 Public alsa As CAlsa ' Classe che incapsula le funzioni ALSA Public Sub Form_Open() ' Crea ("istanzia") un Oggetto della Classe "CAlsa" per poterla usare: With alsa = New CAlsa As "Alsa" ' Apre ALSA e assegna un nome al Client: Me.Title = "|| Client ALSA " & .AlsaIni("Programma di prova") ' Sceglie la periferica (Softsynth) su cui suonare: .ImpostaDispositivo(id_dev, p_dev) End With End Public Sub ToggleButton1_Click() If ToggleButton1.Value Then With alsa ' Avvia la "Coda" degli Eventi Midi ALSA: .AvvioCoda() ' Imposta il Volume gestendo l'Evento Midi "Control Change": .ControlChange(1, 0, 0, 7, 50) ' Imposta il suono dello strumento musicale gestendo l'Evento Midi "Program Change": .ProgramChange(1, 0, 0, 0, 44) ' Imposta la prima nota Midi da eseguire: .NoteON(1, 0, 0, 64, 100) ' Imposta la nota Midi da silenziare dopo 1 secondo dall'inizio della "Coda": .NoteOFF(1, 1, 0, 64, 0) ' Imposta il suono di un altro strumento musicale dopo 2 secondi dall'inizio della "Coda" (quindi crea una pausa): .ProgramChange(1, 2, 0, 0, 18) ' Imposta la nota Midi da eseguire dopo 2 secondi dall'inizio della "Coda": .NoteON(1, 2, 0, 66, 100) ' Imposta la nota Midi da silenziare dopo 3 secondi dall'inizio della "Coda": .NoteOFF(1, 3, 0, 66, 0) ' Imposta il suono di un altro strumento musicale dopo 3 secondi dall'inizio della "Coda": .ProgramChange(1, 3, 0, 0, 18) ' Imposta la nota Midi da eseguire dopo 300 secondi dall'inizio della "Coda": .NoteON(1, 3, 0, 68, 100) ' Imposta la nota Midi da silenziare dopo 5 secondi dall'inizio della "Coda": .NoteOFF(1, 5, 0, 68, 0) ' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda": .Flush() End With Else alsa.StopCoda() Me.Close Endif End
Nella Classe secondaria, chiamata "CAlsa", vi sarà invece il seguente codice:
Private handle As Pointer Private id As Integer Private s_port As Integer Private que As Integer Private dclient As Byte Private dport As Byte Library "libasound:2.0.0" Public Struct snd_seq_event_t type As Byte flags As Byte tag As Byte queue As Byte tick_o_tv_sec As Integer tv_nsec As Integer source_client As Byte source_port As Byte dest_client As Byte dest_port As Byte channel As Byte note As Byte velocity As Byte off_velocity As Byte param As Integer value As Integer End Struct Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1 Private Const SND_SEQ_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2 Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576 Private Enum SND_SEQ_EVENT_NOTEON = 6, SND_SEQ_EVENT_NOTEOFF, SND_SEQ_EVENT_KEYPRESS, SND_SEQ_EVENT_CONTROLLER = 10, SND_SEQ_EVENT_PGMCHANGE, SND_SEQ_EVENT_CHANPRESS, SND_SEQ_EVENT_PITCHBEND, SND_SEQ_EVENT_START = 30, SND_SEQ_EVENT_STOP = 32 ' int snd_seq_open (snd_seq_t **seqp, const char * name, int streams, int mode) ' Open the ALSA sequencer. Private Extern snd_seq_open(seqp As Pointer, name As String, streams As Integer, mode As Integer) As Integer ' int snd_seq_set_client_name (snd_seq_t* seq, const char* name) ' Set client name. Private Extern snd_seq_set_client_name(seq As Pointer, name As String) As Integer ' int snd_seq_client_id (snd_seq_t * seq) ' Get the client id. Private Extern snd_seq_client_id(seq As Pointer) As Integer ' int snd_seq_create_simple_port (snd_seq_t* seq, const char* name, unsigned int caps, unsigned int type) ' Creates a port with the given capability and type bits. Private Extern snd_seq_create_simple_port(seq As Pointer, name As String, caps As Integer, type As Integer) As Integer ' int snd_seq_alloc_queue (snd_seq_t * seq) ' Allocate a queue. Private Extern snd_seq_alloc_queue(seq As Pointer) As Integer ' int snd_seq_control_queue(snd_seq_t * seq, int q, int type, int value, snd_seq_event_t * ev) ' Queue controls - start/stop/continue. Private Extern snd_seq_control_queue(seq As Pointer, q As Integer, type As Integer, value As Integer, ev As Pointer) As Integer ' int snd_seq_event_output_buffer (snd_seq_t * seq, snd_seq_event_t *ev) ' Output an event. Private Extern snd_seq_event_output_buffer(seq As Pointer, ev As Snd_seq_event_t) As Integer ' int snd_seq_drain_output (snd_seq_t * seq) ' Drain output buffer to sequencer. Private Extern snd_seq_drain_output(seq As Pointer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_seq_close (snd_seq_t * seq) ' Close the sequencer. Private Extern snd_seq_close(seq As Pointer) As Integer Public Function AlsaIni(nome As String) As String Dim err As Integer err = snd_seq_open(VarPtr(handle), "default", SND_SEQ_OPEN_OUTPUT, 0) Print "Apertura del subsistema 'seq' di ALSA = "; IIf(err == 0, "corretta !", "errata !") If err < 0 Then error.RAISE("Errore: " & snd_strerror(err)) snd_seq_set_client_name(handle, nome) id = snd_seq_client_id(handle) Print "Alsa ClientID = "; id s_port = snd_seq_create_simple_port(handle, "Uscita", 0, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION) Print "Porta del programma = "; s_port If s_port < 0 Then error.Raise("Errore: " & snd_strerror(s_port)) que = snd_seq_alloc_queue(handle) If que < 0 Then error.Raise("Errore: " & snd_strerror(que)) Return CStr(id) & ":" & CStr(s_port) End Public Procedure ImpostaDispositivo(client As Integer, port As Integer) dclient = client dport = port End Public Procedure AvvioCoda() Dim err As Integer err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_START, 0, 0) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ControlChange(flg As Byte, tick As Integer, canale As Byte, prm As Integer, value As Integer) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_CONTROLLER .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .param = prm .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ProgramChange(flg As Byte, tick As Integer, canale As Byte, non_usato As Byte, value As Integer) Dim err As Integer Dim ev_midi As New Snd_seq_event_t With ev_midi .type = SND_SEQ_EVENT_PGMCHANGE .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .value = value End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteON(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEON .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteOFF(flg As Byte, tick As Integer, canale As Byte, nota As Byte, vel As Byte) Dim ev_midi As New Snd_seq_event_t Dim err As Integer With ev_midi .type = SND_SEQ_EVENT_NOTEOFF .flags = flg .tick_o_tv_sec = tick .queue = que .source_client = id .source_port = s_port .dest_client = dclient .dest_port = dport .channel = canale .note = nota .velocity = vel End With err = snd_seq_event_output_buffer(handle, ev_midi) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure Flush() Dim err As Integer err = snd_seq_drain_output(handle) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure StopCoda() Dim err As Integer err = snd_seq_control_queue(handle, que, SND_SEQ_EVENT_STOP, 0, 0) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) snd_seq_close(handle) End