Esempio di semplice Client Midi di Alsa in ricezione ed invio dati
Mostreremo di seguito il codice di un semplice Client Midi di Alsa capace di ricevere dati Midi (a titolo esemplificativo sono stati individuati i quattro tipi più importanti e frequenti) da un altro Client di Alsa (ad esempio da una tastiera Midi esterna) e di inviarli immediatamente ad un altro Client (ad esempio ad un softsynth per l'ascolto sonoro).
Al terzo parametro della funzione snd_seq_open(), necessaria per l'apertura del subsistema Seq di Alsa, va inserita la costante SND_SEQ_OPEN_DUPLEX per consentire il flusso di dati sia in Entrata che in Uscita.
I dati Midi ricevuti sono assegnati ad una variabile di tipo Struttura organizzata secondo lo schema tipico di un "Evento" Midi Alsa.
Dopo l'avvio del softsynth e del seguente applicativo, sarà necessario connettere via Alsa la tastiera Midi ed softsynth al presente applicativo Midi rispettivamente in entrata ed in uscita.
Public Struct snd_seq_event_t type As Byte ' byte n° 0 flag As Byte tag As Byte queue As Byte timestamp1 As Integer timestamp2 As Integer source_id As Byte source_porta As Byte dest_id As Byte dest_porta As Byte channel As Byte ' byte n° 16 note As Byte velocity As Byte off_velocity As Byte parametro As Integer ' byte n° 20 valore As Integer ' byte n° 24 End Struct Library "libasound:2.0.0" Private Const SND_SEQ_OPEN_DUPLEX As Integer = 3 Private Const SND_SEQ_PORT_CAP_WRITE As Integer = 2 Private Const SND_SEQ_PORT_CAP_SUBS_WRITE As Integer = 64 Private Const SND_SEQ_PORT_CAP_READ As Integer = 1 Private Const SND_SEQ_PORT_CAP_SUBS_READ As Integer = 32 Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576 Private Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253 Private Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254 Private Const SND_SEQ_EVENT_NOTEON As Byte = 6 Private Const SND_SEQ_EVENT_NOTEOFF As Byte = 7 Private Const SND_SEQ_EVENT_CONTROLLER As Byte = 10 Private Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11 ' 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_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 ' int snd_seq_set_client_name(snd_seq_t* seq, const char* name) ' Set client name. Private Extern snd_seq_set_client_name(handle 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(handle 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(handle As Pointer, name As String, caps As Integer, type As Integer) As Integer ' int snd_seq_event_input(snd_seq_t *handle, snd_seq_event_t **ev) ' Retrieve an event from sequencer. Private Extern snd_seq_event_input(handle As Pointer, ev As Snd_seq_event_t) As Integer ' int snd_seq_event_output_direct (snd_seq_t *handle, snd_seq_event_t *ev) ' Output an event directly to the sequencer NOT through output buffer. Private Extern snd_seq_event_output_direct(handle As Pointer, ev As Snd_seq_event_t) As Integer Public Sub Main() Dim seq As Pointer Dim idportaE, idportaU, err, id As Integer err = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_DUPLEX, 0) If err < 0 Then Error.Raise("Impossibile inizializzare il subsistema 'seq' di ALSA: " & snd_strerror(err)) Print "Apertura del dispositivo 'seq' di Alsa: regolare\nNome del dispositivo 'seq': "; "'default'" err = snd_seq_set_client_name(seq, "Sequencer dimostrativo di ALSA") If err < 0 Then Error.Raise("Impossibile assegnare un nome al Client applicativo di ALSA: " & snd_strerror(err)) id = snd_seq_client_id(seq) Print "Numero identificativo del sequencer dimostrativo: "; id idportaE = snd_seq_create_simple_port(seq, "Porta di Entrata del sequencer dimostrativo di ALSA", SND_SEQ_PORT_CAP_WRITE Or SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION) If idportaE < 0 Then Error.Raise("Impossibile creare una porta del Client applicativo di ALSA: " & snd_strerror(err)) Print "Numero identificativo della porta di Entrata: "; idportaE idportaU = snd_seq_create_simple_port(seq, "Porta di Uscita del sequencer dimostrativo di ALSA", SND_SEQ_PORT_CAP_READ Or SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION) If idportaU < 0 Then Error.Raise("Impossibile creare una porta del Client applicativo di ALSA: " & snd_strerror(err)) Print "Numero identificativo della porta di Uscita: "; idportaU Print intercettaMessaggiMidi(seq, id, idportaU) End Public Procedure intercettaMessaggiMidi(seqiM As Pointer, idC As Integer, portEx As Integer) Dim ev As Pointer Dim midi@ As Snd_seq_event_t While True snd_seq_event_input(seqiM, VarPtr(ev)) midi@ = ev With midi@ Select Case midi@.type Case SND_SEQ_EVENT_NOTEON Print "Evento 'NoteOn' sul canale: "; .channel, .note, .velocity Case SND_SEQ_EVENT_NOTEOFF Print "Evento 'NoteOff' sul canale: "; .channel, .note, .velocity Case SND_SEQ_EVENT_CONTROLLER Print "Evento 'Control' sul canale: "; .channel, .parametro, .valore Case SND_SEQ_EVENT_PGMCHANGE Print "Evento 'Program Change' sul canale: "; .channel, .valore End Select .source_id = idC .source_porta = portEx .dest_id = SND_SEQ_ADDRESS_SUBSCRIBERS .dest_porta = SND_SEQ_ADDRESS_UNKNOWN End With snd_seq_event_output_direct(seqiM, midi@) Wend End
Un altro esempio analogo al precedente.
Funzionamento:
1) connettere una tastiera Midi al computer;
2) lanciare il softhsynth (p.e. Qsynth);
3) quindi lanciare il seguente codice;
4) scegliere uno strumento musicale dalla lista "General Midi" contenuta dal "ComboBox";
5) ...suonare la tastiera Midi esterna.
Private fl As File Private cb As ComboBox Private bb As Byte[] Private seq As Pointer 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() Dim dev As String dev = Dir("/dev", "*midi*", gb.Device)[0] With Me .W = 400 .H = 200 .Center End With With cb = New ComboBox(Me) As "Combo" .W = 180 .H = 30 .X = (Me.W / 2) - (.W / 2) .Y = (Me.H / 2.5) - .H .Text = "Strumenti" .List = instrumenta End With bb = New Byte[] CreaClient() ' Apre il file-device Midi in "Lettura" e lo pone in "Osservazione": fl = Open "/dev" &/ dev For Read Watch End Public Sub File_Read() Dim b As Byte ' Se c'è qualcosa da leggere dal file-device, lo legge": Read #fl, b ' Prende in considerazione soltanto Messaggi-Midi "Note-ON" con canale 0: If b > 144 Then Return bb.Push(b) If bb.Count == 3 Then InvioMIDI(bb) bb = New Byte[] Endif End ''''''''''''''''''''''''''''''''''''''''''''' Library "libasound:2" Public Struct snd_seq_event_t ' Struttura dell'Evento Midi di ALSA type As Byte flags As Byte tag As Byte queue As Byte tick_time As Integer real_time As Integer source_client As Byte source_port As Byte dest_client As Byte dest_port As Byte channel As Byte note As Byte velocity As Byte off_velocity As Byte param As Integer value As Integer End Struct Private Const SND_SEQ_OPEN_OUTPUT As Integer = 1 Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253 Private Const SND_SEQ_EVENT_NOTEON As Byte = 6 Private Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11 ' int snd_seq_open (snd_seq_t **handle, const char * name, int streams, int mode) ' Open the ALSA sequencer. Private Extern snd_seq_open(handle As Pointer, name As String, streams As Integer, mode As Integer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an error code. Private Extern snd_strerror(err As Integer) As String ' int snd_seq_event_output (snd_seq_t *handle, snd_seq_event_t *ev) ' Output an event. Private Extern snd_seq_event_output(handle As Pointer, ev As Snd_seq_event_t) ' int snd_seq_drain_output (snd_seq_t * seq) ' Drain output buffer to sequencer. Private Extern snd_seq_drain_output(seq As Pointer) As Integer ' int snd_seq_close (snd_seq_t *handle) ' Close the sequencer. Private Extern snd_seq_close(handle As Pointer) As Integer Private Procedure CreaClient() Dim rit As Integer rit = snd_seq_open(VarPtr(seq), "default", SND_SEQ_OPEN_OUTPUT, 0) If rit < 0 Then Error.Raise("Impossibile aprire il subsistema 'seq' di ALSA: " & snd_strerror(rit)) End Public Sub Combo_Change() ' Imposta lo strumento musicale If cb.Index = -1 Then Return 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, [144, 0, 0], cb.Index) End Private Procedure InvioMIDI(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 "Note-ON": Messaggio(evento, SND_SEQ_EVENT_NOTEON, mid, 0) End Private Procedure Messaggio(ev As Snd_seq_event_t, tipo As Byte, nota As Byte[], instr As Integer) With ev .type = tipo .channel = nota[0] .note = nota[1] .velocity = nota[2] .value = instr 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) fl.Close End