Alsa e Gambas: Invio dati con l'uso di una Classe specifica
Indice
Introduzione
Per definire e gestire gli Eventi Midi di ALSA si può usare una Classe specifica, fornita di Proprietà in "lettura" e in "scrittura" che potranno essere usate per memorizzare i valori necessari dell'Evento Midi, secondo la Struttura snd_seq_event_t prevista dal protocollo di ALSA.
Dichiarando poi una variabile del tipo di quella Classe per ciascun Evento Midi ALSA, si avrà un'area di memoria utilizzabile.
L'uso di tale Classe specifica sostituisce egregiamente e più dinamicamente la Struttura. [nota 1]
Scrittura dei dati dei messaggi Midi nella Classe specifica dei dati Midi
Impostando e usando le Proprietà della Classe specifica
La Classe modello specifica per la gestione delle risorse di ALSA, che chiameremo "CAlsa", va creata con tutte le Proprietà necessarie per la definizione di qualunque Evento Midi di ALSA e sarà così organizzata:
' Definisce le Proprietà della Classe specifica: Property Type As Byte Property Flags As Byte Property Tag As Byte Property Queue As Byte Property Tick_o_Tv_sec As Integer Property Tv_nsec As Integer Property Source_client As Byte Property Source_port As Byte Property Dest_client As Byte Property Dest_port As Byte Property Channel As Byte Property Note As Byte Property Velocity As Byte Property Off_velocity As Byte Property Param As Integer Property Value As Integer ' Definisce i simboli associati alle suddette Proprietà: Private $Type As Byte Private $Flags As Byte Private $Tag As Byte Private $Queue As Byte Private $Tick_o_Tv_sec As Integer Private $Tv_nsec As Integer Private $Source_client As Byte Private $Source_port As Byte Private $Dest_client As Byte Private $Dest_port As Byte Private $Channel As Byte Private $Note As Byte Private $Velocity As Byte Private $Off_velocity As Byte Private $Param As Integer Private $Value As Integer Private Function Type_Read() As Byte Return $Type End Private Sub Type_Write(Value As Byte) $Type = Value End Private Function Flags_Read() As Byte Return $Flags End Private Sub Flags_Write(Value As Byte) $Flags = Value End Private Function Tag_Read() As Byte Return $Tag End Private Sub Tag_Write(Value As Byte) $Tag = Value End Private Function Queue_Read() As Byte Return $Queue End Private Sub Queue_Write(Value As Byte) $Queue = Value End Private Function Tick_o_Tv_sec_Read() As Integer Return $Tick_o_Tv_sec End Private Sub Tick_o_Tv_sec_Write(Value As Integer) $Tick_o_Tv_sec = Value End Private Function Tv_nsec_Read() As Integer Return $Tv_nsec End Private Sub Tv_nsec_Write(Value As Integer) $Tv_nsec = Value End Private Function Source_client_Read() As Byte Return $Source_client End Private Sub Source_client_Write(Value As Byte) $Source_client = Value End Private Function Source_port_Read() As Byte Return $Source_port End Private Sub Source_port_Write(Value As Byte) $Source_port = Value End Private Function Dest_client_Read() As Byte Return $Dest_client End Private Sub Dest_client_Write(Value As Byte) $Dest_client = Value End Private Function Dest_port_Read() As Byte Return $Dest_port End Private Sub Dest_port_Write(Value As Byte) $Dest_port = Value End Private Function Channel_Read() As Byte Return $Channel End Private Sub Channel_Write(Value As Byte) $Channel = Value End Private Function Note_Read() As Byte Return $Note End Private Sub Note_Write(Value As Byte) $Note = Value End Private Function Velocity_Read() As Byte Return $Velocity End Private Sub Velocity_Write(Value As Byte) $Velocity = Value End Private Function Off_velocity_Read() As Byte Return $Off_velocity End Private Sub Off_velocity_Write(Value As Byte) $Off_velocity = Value End Private Function Param_Read() As Integer Return $Param End Private Sub Param_Write(Value As Integer) $Param = Value End Private Function Value_Read() As Integer Return $Value End Private Sub Value_Write(Value As Integer) $Value = Value End
Esempio di codice
Mostriamo un codice in ambiente garfico che prevede tre Classi:
- la prima è una Classe secondaria, che chiameremo EventoMidi, ed è la Classe specifica per la gestione degli Eventi Midi di ALSA e sarà identica al modello di Classe specifica di un Evento Midi ALSA sopra descritto.
- la seconda è un'altra Classe secondaria, che chiameremo CAlsa, ed è la Classe per la gestione delle risorse di ALSA. In questa Classe saranno attribuiti alla variabile del tipo della Classe specifica della gestione degli Eventi Midi alcuni valori.
- La terza Classe è quella principale e in essa saranno attribuiti alla variabile del tipo della Classe specifica della gestione degli Eventi Midi la maggior parte dei valori per il particolare Evento Midi in quel momento gestito.
Il suo codice, che sarà il seguente, prevede che la temporizzazione degli Eventi Midi, ivi gestiti, sia impostata in modalità tick time Midi:
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 Private Const SND_SEQ_TIME_STAMP_TICK As Integer = 0 Public alsa As CAlsa ' Classe che incapsula le funzioni ALSA Public evmid As New Evento_Midi ' Classe delle Proprietà per gestire uno spcifico "Evento Midi" di 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 ' Avvia la "Coda" degli Eventi Midi ALSA: alsa.AvvioCoda() ' Imposta i valori specifici dell'Evento Midi "Control Change": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 0 .Channel = 0 .Param = 7 .Value = 50 End With alsa.ControlChange(evmid) ' Imposta i valori specifici dell'Evento Midi "Program Change": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 0 .Channel = 0 .Value = strum.Find("Violin") End With alsa.ProgramChange(evmid) ' Imposta i valori specifici dell'Evento Midi "Midi-ON": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 0 .Channel = 0 .Note = 64 .Velocity = 100 End With alsa.NoteON(evmid) ' Imposta i valori specifici dell'Evento Midi "Midi-OFF": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 96 .Channel = 0 .Note = 64 .Velocity = 100 End With alsa.NoteOFF(evmid) ' Imposta il suono di un altro strumento musicale dopo 150 tick dall'inizio della "Coda" (quindi crea una pausa): With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 150 .Channel = 0 .Value = strum.Find("Reed Organ") End With alsa.ProgramChange(evmid) ' Imposta la nota Midi da eseguire dopo 182 tick dall'inizio della "Coda": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 182 .Channel = 0 .Note = 66 .Velocity = 100 End With alsa.NoteON(evmid) ' Imposta la nota Midi da silenziare dopo 300 tick dall'inizio della "Coda": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 300 .Channel = 0 .Note = 66 .Velocity = 0 End With alsa.NoteOFF(evmid) ' Imposta il suono di un altro strumento musicale dopo 300 tick dall'inizio della "Coda": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 300 .Channel = 0 .Value = strum.Find("Flute") End With alsa.ProgramChange(evmid) ' Imposta la nota Midi da eseguire dopo 300 tick dall'inizio della "Coda": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 300 .Channel = 0 .Note = 68 .Velocity = 100 End With alsa.NoteON(evmid) ' Imposta la nota Midi da silenziare dopo 500 tick dall'inizio della "Coda": With evmid .Flags = SND_SEQ_TIME_STAMP_TICK .Tick_o_Tv_sec = 500 .Channel = 0 .Note = 68 .Velocity = 0 End With alsa.NoteOFF(evmid) ' Dispone infine l'invio di tutti gli Eventi Midi bufferizzati nella "Coda": alsa.Flush() Else alsa.StopCoda() Me.Close Endif End
La Classe secondaria CAlsa avrà 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" 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 EventoMidi) 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", 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)) 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(mid As Evento_Midi) Dim err As Integer With mid .Type = SND_SEQ_EVENT_CONTROLLER .Queue = que .Source_client = id .Source_port = s_port .Dest_client = dclient .Dest_port = dport End With err = snd_seq_event_output_buffer(handle, mid) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure ProgramChange(mid As Evento_Midi) Dim err As Integer With mid .Type = SND_SEQ_EVENT_PGMCHANGE .Queue = que .Source_client = id .Source_port = s_port .Dest_client = dclient .Dest_port = dport End With err = snd_seq_event_output_buffer(handle, mid) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteON(mid As Evento_Midi) Dim err As Integer With mid .Type = SND_SEQ_EVENT_NOTEON .Queue = que .Source_client = id .Source_port = s_port .Dest_client = dclient .Dest_port = dport End With err = snd_seq_event_output_buffer(handle, mid) If err < 0 Then Error.Raise("Errore: " & snd_strerror(err)) End Public Procedure NoteOFF(mid As Evento_Midi) Dim err As Integer With mid .Type = SND_SEQ_EVENT_NOTEOFF .Queue = que .Source_client = id .Source_port = s_port .Dest_client = dclient .Dest_port = dport End With err = snd_seq_event_output_buffer(handle, mid) 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
Impostando e usando solo variabili Globali per ciascun dato della Classe specifica
La Classe specifica della gestione degli Eventi Midi di ALSA può anche solo contenere singole variabili di tipo Globale, ciascuna delle quali rappresenta un membro:
Public type As Byte Public flag As Byte Public tag As Byte Public queue As Byte Public tick_o_tv_sec As Integer Public tv_nsec As Integer Public source_client As Byte Public source_port As Byte Public dest_client As Byte Public dest_port As Byte Public channel As Byte Public note As Byte Public velocity As Byte Public off_velocity As Byte Public param As Integer Public value As Integer
Ovviamente nel codice pricipale di Gambas si creerà un Oggetto (istanza) della Classe specifica degli Eventi Midi ALSA, così costituita, e le predette variabili globali saranno richiamate nel modo consueto attraverso la variabile identificatrice della Classe specifica.
Note
[1] In Gambas una Struttura può anche essere sostituita con una Classe avente solo Proprietà (dunque senza Metodi né Eventi).