Alsa e Gambas: Invio dei dati senza connessione della porta del programma con il Softsynth
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.
1° caso
Nel primo caso 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 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 .queue = que .source_client = id .source_port = porta .dest_client = CLIENT_DESTINATARIO .dest_port = PORTA_DESTINATARIO .channel = 0 End With ...etc... End
2° caso
Nel secondo caso si dovrà tenere conto di quanto segue:
- non viene utilizzata la funzione esterna (usata invece nel caso precedente) specifica di ALSA per la creazione della porta del Client applicativo;
- 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_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2 Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253 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 Public Sub Main() Dim handle As Pointer Dim rit As Integer Dim id, porta 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 !") With ev .queue = SND_SEQ_QUEUE_DIRECT .source_client = id .source_port = porta .dest_client = CLIENT_DESTINATARIO .dest_port = PORTA_DESTINATARIO .channel = 0 End With ...etc... 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à in questo caso la funzione esterna di ALSA: "snd_seq_event_output_direct()" per invio diretto di ciaascun 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))) With evento = New Snd_seq_event_t .type = SND_SEQ_EVENT_NOTEON .queue = SND_SEQ_QUEUE_DIRECT .dest_client = CLIENT_DESTINATARIO .dest_port = PORTA_DESTINATARIO .channel = 0 .note = SpinBox1.Value .velocity = 100 End With End Public Sub Button1_MouseUp() evento.type = SND_SEQ_EVENT_NOTEOFF evento.velocity = 0 snd_seq_event_output_direct(seq, evento) snd_seq_close(seq) End
In quest'altro esempio pratico viene rappresentata sul Form una tastierina Midi che può inviare dati Midi grezzi attraverso ALSA al Client-softsynth (ad esempio QSynth).
Sul Form è rappresentata una tastiera musicale. 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 .Index = 0 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 = Null 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_EVENT_PGMCHANGE As Byte = 11 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 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 A 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
Se nel proprio sistema è stato installato il softsynth Fluidsynth, allora il programma, qui sopra descritto, dovrebbe 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";
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.