Ricevere dati Midi senza funzioni di ALSA e inviarli mediante le funzioni di Alsa

Da Gambas-it.org - Wikipedia.

Non è necessario in modo assoluto utilizzare le risorse esterne di ALSA per ricevere dati Midi inviati da un dispositivo esterno (ad esempio da una tastiera Midi).
I dati Midi, inviati da tale dispositivo, possono essere intercettati aprendo e leggendo il file-device specifico, creato all'atto della connessione di quel dispositivo esterno al computer.
In questo caso si intercetteranno dal dispositivo esterno i dati grezzi inviati.

In questa pagina vedremo come ricevere dati grezzi, dunque, da un dispositivo esterno e come inviarli subito attraverso ALSA ad un softsynth (ad esempio QSynth), affinché siano riprodotti i suoni delle note e dello strumento prescelti.
In particolare, il programma intercetterà i dati Midi grezzi, inviati dal dispositivo Midi esterno, aprendo in lettura e ponendo sotto "osservazione" il file-device creato appositamente dal sistema, senza l'uso di risorse esterne di ALSA. Opportunamente elaborati secondo il protocollo di ALSA, tali dati ricevuti saranno inviati al softhsynth mediante le funzioni esterne di ALSA per la riproduzione sonora.

A tal proposito, come sappiamo, ALSA non accetta semplici dati Midi grezzi, come quelli che arrivano al file-device della tastiera Midi esterna connessa ed inviati da appunto questo dispositivo.
ALSA accetta solo i dati Midi inviati ad essa rigorosamente nella forma dei suoi "Eventi Midi", come stabiliti e descritti dalle specificazioni di ALSA.
Così, ogni "Messaggio", previsto dal protocollo Midi, deve essere organizzato in una porzione di memoria assegnata, introducendo diversi specifici valori, e solo dopo possono essere inviati ad ALSA.
Sappiamo anche che la prima parte (16 byte) di questa porzione di memoria sarà occupata da valori comuni a tutti gli "Eventi Midi" di ALSA; mentre la parte restante e ultima sarà occupata dai valori inerenti agli specifici "Messaggi" previsti dal protocollo Midi.
Dunque, per organizzare un "Evento Midi " di ALSA, terremo conto della Struttura snd_seq_event_t prevista dall'API di ALSA, e in Gambas potremo utilizzare l'Oggetto Struttura come astrattamente segue:

Public Struct Snd_seq_event_t
  type As Byte
  flags As Byte
  tag As Byte           
  queue As Byte                 Dati comuni
  tick_time As Integer          a tutti
  real_time As Integer          gli Eventi Midi di ALSA
  source_client As Byte
  source_port As Byte
  dest_client As Byte
  dest_port As Byte
    channel As Byte
    note As Byte                   Dati appartenenti
    velocity As Byte               agli specifici "Messaggi"
    off_velocity As Byte           del protocollo Midi
    param As Integer
    value As Integer
End Struct


Esempi pratici

Ricevere dati Midi grezzi inviati da un dispositivo Midi esterno

Mostriamo un semplice esempio, in ambiente grafico, per ricevere dati Midi grezzi, inviati da un dispositivo Midi esterno (ad esempio una tastiera Midi) e che saranno inviati subito attraverso ALSA al Client-softsynth (ad esempio QSynth).
Come si potrà notare, utilizzeremo uno speciale codice per la gestione delle risorse di ALSA, nel quale non si creerà arbitrariamente una Porta del nostro Client Midi mediante la specifica funzione esterna di ALSA. Conseguentemente, dovremo assegnare al membro "queue " della Struttura degli "Eventi Midi" di ALSA, sopra illustrata, il valore della Costante "SND_SEQ_QUEUE_DIRECT" delle risorse di ALSA che ha il valore 253.

Private fl As File
Private cb As ComboBox
Private bb As Byte[]
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

' Individua il file-device Midi:
 dev = Dir("/dev", "*midi*", gb.Device)[0]

 With Me
   .W = 300
   .H = 150
   .Center
 End With

 CreaClient()

 With cb = New ComboBox(Me) As "Combo"
   .W = 180
   .H = 30
   .X = (Me.W / 2) - (.W / 2)
   .Y = (Me.H / 2.5) - .H
   .List = instrumenta
   .Index = 0
 End With

 bb = New Byte[]

' 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-OFF" e "Note-ON":
 If b > 159 Then Return
 bb.Push(b)
 If bb.Count == 3 Then
   InvioMIDI(bb)
   bb = New Byte[]
 Endif

End

'''''''''''''''''''''''''''''''''''''''''''''

Private seq As Pointer
Private id As Integer
Private porta As Integer


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_PORT_CAP_READ As Integer = 1
Private Const SND_SEQ_PORT_TYPE_MIDI_GENERIC As Integer = 2
Private Const SND_SEQ_EVENT_NOTEON As Byte = 6
Private Const SND_SEQ_EVENT_PGMCHANGE As Byte = 11
Private Const SND_SEQ_QUEUE_DIRECT As Byte = 253
Private Const SND_SEQ_ADDRESS_UNKNOWN As Byte = 253
Private Const SND_SEQ_ADDRESS_SUBSCRIBERS As Byte = 254
Private Const SND_SEQ_PORT_TYPE_APPLICATION As Integer = 1048576

' 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_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_client_id(snd_seq_t * seq)
' Get the client id.
Private Extern snd_seq_client_id(seq As Pointer) As Integer

' int snd_seq_connect_to ( snd_seq_t * seq, int myport, int dest_client, int dest_port )
' Connect from the given receiver port in the current client to the given destination client:port.
Private Extern snd_seq_connect_to(seq As Pointer, myport As Integer, dest_client As Integer, dest_port As Integer) As Integer

' 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))

 id = snd_seq_client_id(seq)

 porta = snd_seq_create_simple_port(seq, "Uscita", SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC + SND_SEQ_PORT_TYPE_APPLICATION)

' Si connette al Client di ALSA avente numero ID 128:
 rit = snd_seq_connect_to(seq, porta, 128, 0)
 If rit < 0 Then Error.Raise("Impossibile connettersi al Client 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
   .source_client = id
   .source_port = porta
   .dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
   .dest_port = SND_SEQ_ADDRESS_UNKNOWN
   .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(mid As Byte[])

 Dim evento As New Snd_seq_event_t

 With evento
   .queue = SND_SEQ_QUEUE_DIRECT
   .source_client = id
   .source_port = porta
   .dest_client = SND_SEQ_ADDRESS_SUBSCRIBERS
   .dest_port = SND_SEQ_ADDRESS_UNKNOWN
   .channel = mid[0] And &0F
 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[], strum As Integer)

 With ev
   .type = tipo
   .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)
 fl.Close

End