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 interno

Mostriamo un semplice esempio, in ambiente grafico, per ricevere dati Midi grezzi, inviati dal codice interno dell'esempio medesimo, che saranno inviati subito 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.
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 cb As ComboBox
Private bb As New Byte[3]
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()

 With Me
   .W = Screen.AvailableWidth * 0.6
   .H = Screen.AvailableHeight * 0.3
   .Center
 End With
 CreaClient()
 With cb = New ComboBox(Me) As "Combo"
   .W = 160
   .H = 25
   .X = (Me.W * 0.93) - .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.86
   .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.0122
         .H = Me.H * 0.16
         Select Case t 
           Case 25 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.0245) / 2))
           Case 27 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.0245) / 2))
           Case 30 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.0245) / 2))
           Case 32 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.0245) / 2))
           Case 34 + (12 * n)
             .X = tasti[t - 1].X + (((Me.W * 0.0245) / 2))
             Inc n
         End Select
         .Y = Me.H * 0.38
         .Background = Color.Black
         .Tag = t
       Else
' Imposta i tasti bianchi:
         .W = Me.W * 0.018
         .H = Me.H * 0.25
         .X = (.W * c) + (Me.W / 14) + 1
         .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(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(bb)

 If Last.Background = Color.DarkGray Then Last.Background = Color.Black
 Me.Title = Null

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

 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(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[], 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


Ricevere dati Midi grezzi inviati da un dispositivo Midi esterno

Il seguente semplice codice è simile a quello precedente, ma con la differenza che in questo caso i dati Midi grezzi saranno ricevuti da un dispositivo Midi esterno (ad esempio una tastiera Midi).

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-OFF" e "Note-ON":
 If b > 159 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