Esempio di semplice Client Midi di Alsa in ricezione ed invio dati

Da Gambas-it.org - Wikipedia.

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