Differenze tra le versioni di "La gestione dei dati Midi senza l'uso delle funzioni di Alsa"

Da Gambas-it.org - Wikipedia.
Riga 2: Riga 2:
 
Per mera curiosità presentiamo qui una soluzioni alternativa per ricevere da un dispositivo esterno Midi (ad esempio una tastiera Midi) i messaggi MIDI <SPAN style="text-decoration:underline">senza</span> l'utilizzo delle funzioni esterne di ALSA, anche se, però, si andrà a leggere il flusso di dati grezzi Midi direttamente da un ''file-device'' specifico <SPAN style="text-decoration:underline">comunque generato da Alsa</span>.
 
Per mera curiosità presentiamo qui una soluzioni alternativa per ricevere da un dispositivo esterno Midi (ad esempio una tastiera Midi) i messaggi MIDI <SPAN style="text-decoration:underline">senza</span> l'utilizzo delle funzioni esterne di ALSA, anche se, però, si andrà a leggere il flusso di dati grezzi Midi direttamente da un ''file-device'' specifico <SPAN style="text-decoration:underline">comunque generato da Alsa</span>.
  
Questa modalità di gestione, anche se senza l'uso delle funzioni dell'API di Alsa, è molti versi analogo a quello della gestione del [[Ricevere_dati_grezzi_Midi_attraverso_il_subsistema_RawMidi|subsistema ''RawMidi'']] di Alsa.
+
Questa modalità di gestione, anche se senza l'uso delle funzioni dell'API di Alsa, è per taluni aspetti analogo a quello della gestione del [[Ricevere_dati_grezzi_Midi_attraverso_il_subsistema_RawMidi|subsistema ''RawMidi'']] di Alsa.
  
  

Versione delle 20:22, 12 lug 2013

Introduzione

Per mera curiosità presentiamo qui una soluzioni alternativa per ricevere da un dispositivo esterno Midi (ad esempio una tastiera Midi) i messaggi MIDI senza l'utilizzo delle funzioni esterne di ALSA, anche se, però, si andrà a leggere il flusso di dati grezzi Midi direttamente da un file-device specifico comunque generato da Alsa.

Questa modalità di gestione, anche se senza l'uso delle funzioni dell'API di Alsa, è per taluni aspetti analogo a quello della gestione del subsistema RawMidi di Alsa.


Ricezione mediante la lettura del file-device con Open

Dopo aver collegato la tastiera Midi al computer, intendiamo operare sul file-device provvisoriamente creato dal sistema e corrispondente alla predetta tastiera. Con la parola chiave Watch, dopo aver aperto con la funzione OPEN il file-device, faremo si che il programma resti in attesa di un messaggio Midi in arrivo dalla tastiera medesima.

Il file-device temporaneo corrisponde al file-device dei dispositivi rawmidi, che controllano flussi di eventi Midi 'nudi' e privi di temporizzazione. Il modello di file-device di questo tipo è:

midiCcDd

laddove le lettere:

  • "C" sta per Card, ossia la scheda audio;
  • "c" è il numero identificativo (0 - 3) assegnato alla scheda audio;
  • "D" sta per Device, il dispositivo audio;
  • "d" è il numero identificativo assegnato al dispositivo (0 - 16);

esempio: midiC2D0

e si trova nel seguente percorso: /dev/snd/midiCcDd.


Lettura del file-device rawmidi

Per leggere il flusso di dati Midi grezzi che passa attraverso il predetto file-device, apriremo il file-device il lettura aggiungendo la parola "Watch". Se, dunque, potrà essere letto almeno un byte dal file-device, allora verrà chiamato l'evento File_Read():

Private varFile As File


Public Sub Form_Open()

  varFile = Open "/dev/snd/midiC2D0" For Read Watch

End


Public Sub File_Read()
 
 Dim dato As Byte

' Leggiamo il file-device e scriviamo il valore ottenuto nella variabile "dato":
  Read #varFile, dato   ' |1|


' Poiché può accadere che la tastiera Midi invii in continuazione il messaggio Midi 254 (Active Sensing) ed altri messaggi
che a noi qui non interessano, li escludiamo con un filtro:
  If by > 239 Then Return

' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo.
  Print "byte inviato dalla tastiera = "; dato

End


Come si può notare, la ricezione dei dati dalla tastiera Midi esterna è avvenuta senza l'utilizzo di alcuna funzione ALSA, ma direttamente andando a "raccoglierli" dal file-device della porta virtuale della tastiera.


Si fa presente, ad ogni modo, che tale soluzione non è proprio ottimale per almeno le tre seguenti ragioni:

  • si va in ambiente extra ALSA, laddove invece si intendeva programmare utilizzando le API di ALSA;
  • ALSA non riesce ad intercettare più i dati provenienti dalla tastiera esterna Midi, essendo stati intercettati da noi con la presente modalità extra ALSA;
  • la presente soluzione extra ALSA potrebbe interferire o comunque creare problemi alle funzionalità del sistema ALSA. Attualmente non si è a conoscenza di tutti i possibili disturbi.


Possiamo riportare, però, il caso di contemporaneo tentativo di ricezione dei dati da un medesimo dispositivo hardware Midi esterno con funzioni ALSA e con il presente metodo extra ALSA. Ebbene è stato accertato che in tale caso soltanto una delle procedure intercetterà i dati provenienti dal dispositivo Midi esterno. Se, ad esempio, verrà avviato prima il metodo extra ALSA e successivamente quello mediante le funzioni ALSA di ricezione (magari anche con due applicativi distinti), facendoli comunque funzionare contemporaneamente, l'applicativo che utilizza le funzioni di ALSA per ricevere i dati Midi, segnalerà in console il seguente errore:
"Subscribe inport: err=-11 (Resource temporarily unavailable)".


Ricezione dati da più dispositivi Midi esterni

Sino ad ora abbiamo considerato l'invio di messaggi Midi da un solo dispositivo esterno, per esempio una sola tastiera Midi. Come fare, però, se volessimo, con la modalità che stiamo ora trattando, ricevere e gestire i dati da più tastiere Midi esterne ?
Dobbiamo premettere che se colleghiamo al computer via USB un secondo dispositivo Midi esterno, possiamo notare che in /dev/snd comparirà un altro file-device simile a quello che creato dal sistema quando avevamo collegato la prima tastiera Midi esterna. Questa volta il nuovo file-device, che si riferisce alla seconda tastiera, avrà come nome: midiC3D0. Qualora collegassimo un terzo dispositivo Midi estreno, avremmo un ulteriore file-device "midiC4D0", e così via.

A questo punto, però, se proviamo ad inserire due o più OPEN.... Watch, ciascuno per ogni file-device creato e relativo ad un dispositivo Midi esterno, non potremmo intercettare i dati provenienti dalla seconda (o eventualmente anche da una terza tastiera) collegata; sia perché saremmo costretti a prevedere una sola subroutine File_Read(), sia perché - pur inserendo in detta sub-routine un Read per lo stream proveniente da ciascun file-device, riusciremmo ad intercettare soltanto i dati del primo file-device.

Dobbiamo allora trovare un'altra strategia: usare la funzione Exec per lanciare due sotto-programmi Gambas autonomi, resi demoni (quindi invisibili), con i quali gestire i dati, intercettati da ciascun file-device (opportunamente aperto in Watch) delle tastiere collegate, inviando i medesimi allo standard output del sistema. Il programma principale si preoccuperà, pertanto, solo di intercettare dallo standard output del sistema i dati provenienti da ciascun distinto sotto-programma

Scriveremo allora i due piccoli sotto-programmi in questo modo:

' Gambas-3 class file
' Primo sotto-programma


primo As File
out As Stream


PUBLIC Sub Form_Open()

' rendiamo "demone" il sotto-programma:
  Application.Daemon = True

' Apre lo standard output del sistema per la scrittura.
  out = Open "/dev/stdout" For Write

' Apre lo il file-device della prima tastiera esterna collegata e resta in attesa.
   primo = Open "/dev/snd/midiC2D0" For Watch

END


PUBLIC Sub File_read() ' Se potrà essere letto almeno un byte dal file-device, verrà scatenata questa subroutine.

Dim by As Byte
 
' Legge i dati intercettati dal file-device che rappresenta la porta virtuale della prima tastiera esterna collegata.
 Read #primo, by

' Filtra i byte "Sensing Active" e degli altri eventi qui non necessari.
 If by > 239 Then
   Return
 Endif

' Scrive quei dati nello standard output del sistema.
   Write #out, by As Byte

END

Salvato il codice sopra descritto come primo sotto-programma a se stante (lo chiameremo per esempio: "primo_sotto"), scriviamo ora il codice del secondo sotto-programma per la ricezione dei dati provenienti dal secondo dispositivo Midi esterno:

' Gambas-3 class file
' Secondo sotto-programma


secondo As File
out As Stream


PUBLIC Sub Form_Open()
 
' rendiamo "demone" il sotto-programma:
  Application.Daemon = True

' Apre lo standard output del sistema per la scrittura.
  out = Open "/dev/stdout" For Write

' Apre lo il file-device della seconda tastiera esterna collegata e resta in attesa.
   secondo = Open "/dev/snd/midiC3D0" For Watch

END


PUBLIC Sub File_read() ' Se potrà essere letto almeno un byte dal file-device, verrà scatenata questa subroutine.

Dim by As Byte
 
' Legge i dati intercettati dal file-device che rappresenta la porta virtuale della seconda tastiera esterna collegata.
 Read #secondo, by

' Filtra i byte "Sensing Active" e degli altri eventi qui non necessari.
 If by > 239 Then
   Return
 Endif

' Scrive quei dati nello standard output del sistema.
   Write #out, by As Byte

END

Proceduto a salvare con il nome "secondo_sotto" anche questo secondo sotto-programma autonomo, scriviamo il codice del programma principale, che avrà il compito, come già detto, di intercettare a sua volta i dati inviati dai due sotto-programmi allo standar output del sistema:

' Gambas-3 class file
' Programma principale


prm as process
scnd as process


PUBLIC Sub Form_Open()

' Avvia come processo il primo ed il secondo sotto-programma
' che abbiamo salvato, per esempio, nella Home.
  prm = Exec ["percorso/primo_sotto.gambas"] For Read As "primo"

    scnd = Exec ["percorso/secondo_sotto.gambas"] For Read As "secondo"

END


PUBLIC Sub primo_read() ' Se potrà essere almeno un byte dallo standard output del processo "primo", verrà sollevata questa subroutine.

Dim by As Byte
 
' Legge i dati scritti dal programma primo_sotto.
 Read #prm, by

  ' Qui per soli fini didattici semplicemente scriviamo in console i dati intercettati.
   Print by
  
END


PUBLIC Sub secondo_read() ' Se potrà essere almeno un byte dallo standard output del processo "secondo", verrà sollevata questa subroutine.

Dim by As Byte
 
' Legge i dati scritti dal programma secondo_sotto.
 Read #scnd, by

  ' Qui per soli fini didattici semplicemente scriviamo in console i dati intercettati.
   Print by
  
END


PUBLIC Sub Button1_Click() ' Per chiudere regolarmente i sotto-programmi ed il programma principale

 prm.kill()
  scnd.kill()
   Me.close()

END




Ricezione mediante Process

E' possibile in alternativa ricevere i dati dal file-device utilizzando non Open, ma Process.

varProc As Process

Public Sub ToggleButton1_Click()

  If Last.value Then

    varProc = Exec ["cat", "/dev/snd/midiC2D0"] For Read As "varproc"

' oppure:
' varProc = Shell "cat /dev/snd/midiC2D0" For Read As "varproc"

   Else
' cliccando di nuovo sul tasto, il flusso è bloccato,
' ma cliccandoci ancora il processo sarà riattivato:
    varProc.Kill

  Endif
 
End


Public Sub varproc_Read()
 
 Dim dato As Byte

' Leggiamo il file-device e scriviamo il valore ottenuto nella variabile "dato".
   Read #varProc, dato  ' |1|


' Poiché la tastiera Midi invia in continuazione il messaggio Midi 254 (Active Sensing) ed altri messaggi che a noi qui non interessano, li escludiamo.
   If by < 240 Then
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo.
    Print "byte inviato dalla tastiera = "; dato
   Endif

End




Ricezione mediante Pipe

E' possibile anche utilizzare il comando "Pipe". Tutto resta come con Open.

varFile As File

Public Sub Form_Open()

  varFile = Pipe "/dev/snd/midiC2D0" For Read Watch

End


Public Sub File_Read()  ' |2|
 
 Dim dato As Byte

  Read #varFile, dato  ' |1|

  If by < 240 Then
    Print "byte inviato dalla tastiera = "; dato
  Endif

End




Ricezione mediante Object.Attach

Potremo anche usare il metodo Object.Attach per gestire l'evento della ricezione dei dati Midi, associando l'oggetto file alla definizione dell'evento.

varFile As File


 Public Sub Form_Open()

varFile = Open "/dev/snd/midiC2D0" For Read Watch

Object.Attach(fdMid, FMain, "eventoMidi")

End


Public Sub eventoMidi_Read()
 
Dim b As Byte
 
 Read #varFile, b
' ...il solito filtro:
 If b > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b

End


Ricezione dei dati da più dispositivi Midi esterni

Con il metodo Object.Attach potremo agevolmente ricevere e gestire i dati Midi, provenienti da due o più tastiere esterne.


varFile1 As File
varFile2 As File


 Public Sub Form_Open()

 varFile1 = Open "/dev/snd/midiC2D0" For Read Watch
' Viene associato l'oggetto File alla denominazione del proprio evento:
 Object.Attach(varFile1, FMain, "eventoMidi1")


 varFile2 = Open "/dev/snd/midiC3D0" For Read Watch
' Viene associato l'oggetto File alla denominazione del proprio evento:
 Object.Attach(varFile2, FMain, "eventoMidi2")
 
End


' Vengono intercettati i dati provenienti dalla prima tastiera esterna: 
Public Sub eventoMidi1_Read()
 
Dim b1 As Byte
 
 Read #varFile, b1
' ...il solito filtro:
 If b1 > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b1

End


' Vengono intercettati i dati provenienti dalla seconda tastiera esterna:
Public Sub eventoMidi2_Read()
 
Dim b2 As Byte
 
 Read #varFile2, b2
' ...il solito filtro:
 If b2 > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b2

End




Ricezione mediante Observer

Potremo anche usare il metodo Observer per gestire l'evento della ricezione dei dati Midi.

varFile As File


 Public Sub Form_Open()

Dim oss As Observer

 varFile = Open "/dev/snd/midiC2D0" For Read Watch

 oss = New Observer(varFile) As "eventoMidi"

End


Public Sub eventoMidi_Read()
 
Dim b As Byte
 
 Read #varFile, b
' ...il solito filtro:
 If b > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b

End


Ricezione dei dati da più dispositivi Midi esterni

Ugualmente, con Observer potremo ricevere e gestire i dati Midi, provenienti da due o più tastiere esterne.


varFile1 As File
varFile2 As File


 Public Sub Form_Open()

Dim oss1 As Observer
Dim oss2 As Observer

 varFile1 = Open "/dev/snd/midiC2D0" For Read Watch
' Viene "osservato" l'oggetto File per l'evento:
 oss1 = New Observer(varFile) As "eventoMidi"Object.Attach(varFile1, FMain, "eventoMidi1")


 varFile2 = Open "/dev/snd/midiC3D0" For Read Watch
' Viene "osservato" l'oggetto File per l'evento:
 oss2 = New Observer(varFile) As "eventoMidi"Object.Attach(varFile2, FMain, "eventoMidi2")
 
End


' Vengono intercettati i dati provenienti dalla prima tastiera esterna: 
Public Sub eventoMidi1_Read()
 
Dim b1 As Byte
 
 Read #varFile, b1
' ...il solito filtro:
 If b1 > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b1

End


' Vengono intercettati i dati provenienti dalla seconda tastiera esterna:
Public Sub eventoMidi2_Read()
 
Dim b2 As Byte
 
 Read #varFile2, b2
' ...il solito filtro:
 If b2 > 239 Then Return
' Per fini didattici ci limitiamo qui solo a scrivere il dato in console per vederlo:
 Print b2

End




Ricostruzione ed invio ad ALSA di eventi ricevuti extra ALSA da una tastiera Midi esterna

Anche per questa particolare modalità di ricezione extra ALSA dei messaggi Midi da una tastiera esterna considereremo il procedimento necessario, una volta ottenuti i dati, per costruire l'evento Midi in modo adatto ad essere inviato ad ALSA.

Dovremo tenere conto che sarà ovviamente necessario integrare la parte extra ALSA (come l'abbiamo vista nei due precedenti paragrafi) di ricezione dei dati con la parte di utilizzo delle funzioni delle API di ALSA per inviargli i dati necessari dell'evento NoteON, così da ottenere il suono finale.

Pertanto porremo nella classe principale una routine di avvio con le dichiarazioni delle variabili e delle costanti, con i primi riferimenti ad ALSA e con la riga per aprire il file-device (midiC2D0) che rappresenta la porta virtuale della tastiera esterna:


' Gambas-3 class file


' impostare queste costanti per suonare
Const outdevice As Integer = 14       ' 14=midi out - ALSA
Const outport As Integer = 0          ' porta d'uscita, solitamente 0


Public alsa As CAlsa               ' classe che incapsula le funzioni alsa

Public varFile As File

bb[4] As Byte
n As Integer
 

Public Sub Form_Open()

 ' creare ("istanziare") la classe per poterla usare
  alsa = New CAlsa As "alsa"
 ' aprire alsa e assegnare un nome
  alsa.alsa_open("Gambas drum machine")
 ' scegliere la periferica su cui suonare
  alsa.setdevice(outdevice, outport)

'''''
 
  varFile = Open "/dev/snd/midiC2D0" For Read Watch
 
End


Quindi inseriamo la parte relativa all'invio dei dati Midi alle funzioni della classe CAlsa.class, preposte alla costruzione dell'evento Midi, per il suo successivo invio ad ALSA.
A fini esemplificativi invieremo semplicemente i dati utili per un evento NoteON.

Public Sub File_Read()
 
 Dim by As Byte
 
 Read #varFile, by
 
  If by > 253 Then Return   ' In questo modo eliminiamo il byte 254 (Active Sensing) costantemente inviato dalla tastiera esterna.
 
  n += 1

 bb[n] = by

 
' Quando saranno giunti i tre valori costitutivi del NoteON,
' si passano alla funzione seguente per la costruzione dell'evento Midi.
 If n = 3 Then

' Questa operazione estrae il valore del canale dal messaggio di Stato inviato dalla tastiera.
  bb[1] = bb[1] AND 15             
  
' La funzione che segue invia i dati alla funzione alsa.noteon.
   alsa.noteon(bb[1], bb[2], bb[3])
    n = 0
 Endif
  
End


Note

[1] Se non si vuole usare la variabile dichiarata come File, o nell'altro esempio la variabile dichiarata come Process, in alternativa è possibile usare #Last, il quale, come è noto, restituisce un riferimento all'ultimo oggetto chiamato da un evento:

Read #Last, dato

[2] Se si scrive Sub Pipe_Read(), come suggerisce la guida Gambas per intercettare l'evento, sembra non funzionare.