Gestione del MIDI con O.S.S. - Uso del dispositivo /dev/sequencer2

Da Gambas-it.org - Wikipedia.

AVVERTENZA

Con la versione 4.0 di OSS è stato bloccato anche lo sviluppo del dispositivo " /dev/sequencer2 ".

Riportiamo le informazioni di questa pagina solo per curiosità storica.


Il dispositivo: /dev/sequencer2

Al posto del dispositivo "/dev/sequencer" è possibile, ed è anzi preferibile, usare " /dev/sequencer2 ".

Tale dispositivo offre i seguenti vantaggi:

· non è più necessario lanciare il comando "aconnect" in Shell per connettere
  il dispositivo con il softsynth;
· consente di utilizzare innanzitutto pacchetti (record) tutti da 8 byte, i quali,
  apparendo così più lineari ed omogenei, possono risultare più comodi nella fase di
  programmazione; e comunque sono consigliati in "soundcard.h";
· consente di utilizzare tipi di eventi di secondo livello, quindi istruzioni
  che faciltano ed abbreviano la definizione, la gestione ed il conseguente invio
  dei dati Midi;
· restituisce un errore, se gli vengono trasmessi argomenti errati;
· migliore e più ampio utilizzo delle istruzioni per la gestione della temporizzazione.

Come già detto, con il dispositivo " /dev/sequencer2 " non è più necessario l'uso dell'istruzione SHELL per connettere il dispositivo al softsynth: esso avviene automaticamente.

Bisogna premettere che il numero del device, che prima con " /dev/sequencer " era impostato a 0, ora con " /dev/sequencer2 " è da impostarsi a 3 ovvero, qualora 3 non funzioni, a 1 .


Generazione degli eventi Midi

Gli eventi richiamabili con " /dev/sequencer2 " sono suddivisi per Classi di eventi:

Classi di eventi       Num. identif.vo

  EV_SEQ_LOCAL              &h80 
  EV_TIMING                 &h81 
  EV_CHN_COMMON             &h92 
  EV_CHN_VOICE              &h93 
  EV_CHN_SYSEX              &h92 

Le Classi di eventi sono suddivise a loro volta in Sotto-eventi tipi (riporto le Classi ed i loro sotto-eventi più importanti):

Classe di evento        Sotto-evento      Num. identif.vo            Tipo di evento

     EV_TIMING             
                        TMR_WAIT_REL             1               Tempo Delta espresso in tick 
                        TMR_STOP                 3               Arresto dell'esecuzione 
                        TMR_START                4               Avvio del Timer assoluto 
                        TMR_CONTINUE             5               Continua l'esecuzione dopo l'arresto 
                        TMR_TEMPO                6               Tempo Metronomico espresso in bpm 
                        TMR_ECHO                 8               Per la sincronizzazione con la
                                                                 marcatura temporale (Timestamp
     EV_CHN_COMMON
                        MIDI_CTL_CHANGE         &hB0             Control change 
                        MIDI_PGM_CHANGE         &hC0             Program change 
                        MIDI_CHN_PRESSURE       &hD0             Channel Aftertouch 
                        MIDI_PITCH_BEND         &hE0             Pitch Bend 

     EV_CHN_VOICE
                        MIDI_NOTEOFF            &h80             Note OFF 
                        MIDI_NOTEON             &h90             Note ON 
                        MIDI_KEY_PRESSURE       &hA0             Aftertouch Polyphonic 


L'utilizzo di questi dati avviene, come detto, mediante istruzioni formate da 8 byte.


Modelli di istruzione per classi di eventi

Modello di istruzione per gli eventi EV_CHN_VOICE:

Codice:

WRITE #flusso_dati, Chr(Classe) & Chr(device) & Chr(Sotto-evento) & Chr(canale) & Chr(nota) & Chr(velocità) & Chr(0) & Chr(0), 8
Legenda:

- flusso_dati     AS file
- Classe          = il numero identificativo della Classe di eventi
- device          = il numero del device (= 1  oppure  3)
- Sotto-evento    = il numero identificativo del Sotto-evento
- canale          = il numero del canale (da 0 a 15)
- nota            = numero della nota
- velocità        = velocity
NOTA: I valori del 7° e dell'8° byte sono sempre posti a zero.


Esempi pratici:

1) Per l'invio dell'istruzione Note ON:

Codice:

WRITE, #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h90) & Chr(0) & Chr(60) & Chr(100) & Chr(0) & Chr(0), 8


2) Per l'invio dell'istruzione Note OFF:

Codice:

WRITE, #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h80) & Chr(0) & Chr(60) & Chr(0) & Chr(0) & Chr(0), 8


3) Per l'invio dell'istruzione Aftertouch Polyphonic:

Codice:

WRITE, #flusso_dati, Chr(&h93) & Chr(1) & Chr(&hA0) & Chr(0) & Chr(60) & Chr(100) & Chr(0) & Chr(0), 8




Modello di istruzione per gli eventi EV_CHN_COMMON:

Codice:

WRITE #flusso_dati, Chr(Classe) & Chr(device) & Chr(Sotto-evento) & Chr(canale) & Chr(valore1) & Chr(valore2) & Chr(0) & Chr(0), 8
Legenda:

- flusso_dati     AS file
- Classe          = il numero identificativo della Classe di eventi
- device          = il numero del device (= 1  oppure  3)
- Sotto-evento    = il numero identificativo del Sotto-evento
- canale          = il numero del canale (da 0 a 15)
- valore1, 2      = valori dei parametri specifici dei Sotto-eventi
NOTA: I valori del 7° e dell'8° byte sono sempre posti a zero.


Alcuni esempi pratici:

1) Per l'invio dell'istruzione Control Change:

Codice:

WRITE, #flusso_dati, Chr(&h92) & Chr(1) & Chr(&hB0) & Chr(0) & Chr(7) & Chr(100) & Chr(0) & Chr(0), 8

(In questo esempio il valore del Volume è stato posto a 100)

2) Per l'invio dell'istruzione Program Change:

Codice:

WRITE, #flusso_dati, Chr(&h92) & Chr(1) & Chr(&hC0) & Chr(0) & Chr(70) & Chr(0) & Chr(0) & Chr(0), 8

(In questo esempio è stato scelto lo strumento n. 70: Fagotto)



Modello di istruzione per l'evento EV_TIMING:

Codice:

WRITE, #flusso_dati, Chr(Classe) & Chr(Sotto-evento) & Chr(0) & Chr(0) & Chr(valore1) & Chr(valore2) & Chr(valore3) & Chr(0), 8
Legenda:

- flusso_dati    AS file
- Classe         = il numero identificativo della Classe di eventi
- Sotto-evento   = il numero identificativo del Sotto-evento
- valore1,2,3    = valori degli eventuali parametri specifici dei Sotto-eventi
NOTE:

- Hanno almeno un parametro specifico: TMR_WAIT_REL, TMR_TEMPO, TMR_ECHO;
- il 3° ed il 4° byte sono sempre posti a zero;
- si conferma quanto già affermato nel precedente messaggio: gli EV_TIMING risultano più
  vantaggiosi del comando WAIT, poiché essi, diversamente da WAIT, consentono al programma
  di continuare comunque a fluire nella lettura, gestione ed esecuzione del proprio codice.
  Infatti, essendo la gestione delle istruzioni di temporizzazione demandata specificatamente
  a "/dev/sequencer2 ", la CPU può svolgere ogni altra operazione.


Esempi pratici

1) Per l'invio dell'istruzione TMR_WAIT_REL:

Codice:

WRITE, #flusso_dati, Chr(&h81) & Chr(1) & Chr(0) & Chr(0) & Chr(96) & Chr(0) & Chr(0) & Chr(0), 8

(In questo esempio il valore di TMR_WAIT_REL è stato posto a 96 tick)

2) Per l'invio dell'istruzione TMR_START:

Codice:

WRITE, #flusso_dati, Chr(&h81) & Chr(4) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0), 8

(TMR_START non ha parametri specifici)

Uso pratico delle istruzioni da 8 byte

Ogni qual volta si inviano gruppi di istruzioni per l'esecuzione di uno o più eventi Midi, è indispensabile assolutamente inviare all'inizio un'istruzione TMR_START, richiamata dal valore 4, la quale provvede ad avviare il Timer assoluto.

Esempio di invio istruzioni per far suonare e far cessare due note:

Codice:

' Definisce la variabile flusso_dati come file (stream):
Private flusso_dati AS FILE

Public Sub Form_Open()
  
  flusso_dati = Open "/dev/sequencer2" For Write
   
End


Public Sub Button1_Click()
 
' Invio di TMR_START:
 WRITE #flusso_dati, Chr(&h81) & Chr(4) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0), 8

' E' possibile impostare il tempo metronomico: 120 bmp:
 WRITE #flusso_dati, Chr(&h81) & Chr(6) & Chr(0) & Chr(0) & Chr(120) & Chr(0) & Chr(0) & Chr(0), 8

' 1° evento Midi – Program Change: Fagotto:
 WRITE #flusso_dati, Chr(&h92) & Chr(1) & Chr(&hC0) & Chr(0) & Chr(70) & Chr(0) & Chr(0) & Chr(0), 8

' 2° evento Midi – EV_CHN_VOICE - Note ON:
 WRITE #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h90) & Chr(0) & Chr(60) & Chr(100) & Chr(0) & Chr(0), 8
 
' Temporizzazione (tempo delta)– TMR_REL  = 40 tick:
 WRITE #flusso_dati, Chr(&h81) & Chr(1) & Chr(0) & Chr(0) & Chr(40) & Chr(0) & Chr(0) & Chr(0), 8

' 3° evento Midi – EV_CHN_VOICE - Note OFF:
 WRITE #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h80) & Chr(0) & Chr(60) & Chr(100) & Chr(0) & Chr(0), 8

' 4° evento Midi – EV_CHN_VOICE - Note ON:
 WRITE #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h90) & Chr(0) & Chr(72) & Chr(100) & Chr(0) & Chr(0), 8

' Temporizzazione (tempo delta) – TMR_REL  = 40 tick:
 WRITE #flusso_dati, Chr(&h81) & Chr(1) & Chr(0) & Chr(0) & Chr(40) & Chr(0) & Chr(0) & Chr(0), 8

' 5° evento Midi – EV_CHN_VOICE - Note OFF:
 WRITE #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h80) & Chr(0) & Chr(72) & Chr(100) & Chr(0) & Chr(0), 8

End


Se si vuole mettere in "Pausa" l'esecuzione degli eventi Midi, basta lanciare l'istruzione: TMR_STOP, per esempio mediante un altro tasto, e inviare contemporaneamente un'istruzione Control Change di All Note OFF.

Codice:

PUBLIC SUB Pausa_Click()
 WRITE #flusso_dati, Chr(&h81) & Chr(3) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0), 8
 WRITE #flusso_dati, Chr(&92) & Chr(1) & Chr(&hB0) & Chr(0) & Chr(123) & Chr(0) & Chr(0) & Chr(0), 8
END


Per riavviare dopo la pausa, utilizzare l'istruzione TMR_CONTINUE (sotto-evento = 5). L'esecuzione ripartirà dall'evento successivo a quello in cui l'esecuzione è stata posta in pausa:

Codice:

PUBLIC SUB Continua_Click()
 WRITE #flusso_dati, Chr(&h81) & Chr(5) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0), 8
END


Il Timestamp con il sotto-evento TMR_ECHO

Nella programmazione Midi l'uso di una marcatura temporale (timestamp) risulta utile se è richiesta la verifica dell'avvenimento di uno o più eventi. Tale marca temporale può essere ottenuta mediante il sotto-evento TMR_ECHO (numero identificativo: 8 ):

Codice:

WRITE #flusso_dati, Chr(&h81) & Chr(8) & Chr(0) & Chr(0) & Chr(valore1) & Chr(valore2) & Chr(valore3) & Chr(valore4), 8


NOTA: il 3° ed il 4° byte sono sempre posti a zero.

I valori contenuti nei byte 5, 6, 7 ed 8 (ossia: Chr(valore1) & Chr(valore2) & Chr(valore3) & Chr(valore4) ) dell'istruzione di TMR_ECHO costituiscono la "key" indicata in "soundcard.h", ossia la chiave inviata a "/dev/sequencer2" con TMR_ECHO. Poiché in un flusso di istruzioni Midi potrebbero essere necessarie moltissime marcature temporali (timestamp), con valori numerici ovviamente crescenti, si rende necessario l'uso di un dato integer passato al dispositivo con TMR_ECHO come parametro. TMR_ECHO accetta una "chiave" che è un intero definito a scelta del programmatore. Gli ultimi 4 byte dell'istruzione contengono, appunto, i quattro valori numerici utili per la ricostruzione dell'integer.

Dunque con l'invio del sotto-evento TMR_ECHO "/dev/sequencer2" ci restituisce il valore della "chiave" integer, spezzettato nei valori numerici degli ultimi 4 byte del sotto-evento. E' quindi necessario poter leggere dal dispositivo "/dev/sequencer2" tali valori dopo l'invio del sotto-evento TMR_ECHO mediante la funzione READ


Semplice esempio pratico del funzionamento di TMR_ECHO:

Codice:

' Definisce la variabile flusso_dati come file (stream):
Private flusso_dati AS FILE

Public SUB Form_Open()

' Il dispositivo viene aperto sia per leggere che per scrivere:
 eco_dati = OPEN  "/dev/sequencer2" FOR READ WRITE

End


Public Sub ButtonProvaTmrEcho_Click()
  
 Dim eco_dati AS String
 Dim j AS Integer

' Invia un Note ON:
  WRITE #flusso_dati, Chr(&h93) & Chr(1) & Chr(&h90) & Chr(0) & Chr(72) & Chr(100) & Chr(0) & Chr(0), 8

' Quindi invia TMR_ECHO. Nei 4 ultimi byte dovranno essere inseriti 4 numeri a piacere:
  WRITE #flusso_dati, Chr(&h81) & Chr(8) & Chr(0) & Chr(0) & Chr(valore1) & Chr(valore2) & Chr(valore3) & Chr(valore4), 8

' Legge gli otto dati da "/dev/sequencer2":
  READ #flusso_dati, eco_dati, -8

' Verifica empirica della "chiave" marca temporale (timestamp):
  For j = 5 To 8

' Legge dal 5° valore fino all'8°, cioè appunto i 4 valori costituenti l'integer "chiave":
   PRINT Asc(eco_dati, j)

  Next

End


NOTA: L'istruzione TMR_ECHO funziona. E' necessario avere l'accortezza di instaurare due cicli diversi: uno per mandare gli eventi Midi, l'altro per leggere da "/dev/sequencer2" i dati di ritorno da TMR_ECHO. Il ciclo può essere, per esempio, attivato con un Timer con delay posto fra i 20 ed i 50 ms. E' opportuno, per non creare intasamento, inviare un certo numero di eventi Midi insieme con il numero scelto di eventi TMR_ECHO, e poi ad ogni evento di Timer leggere da "/dev/sequencer2" i dati di ritorno di TMR_ECHO. Il ciclo virtuoso, insomma, sarebbe: invio di un gruppo di dati, poi lettura continua delle "chiavi" TMR_ECHO di ritorno afferenti a questo gruppo di eventi Midi; poi, prima che l'esecuzione del gruppo degli eventi Midi termini, inviare un altro gruppo di eventi Midi, quindi lettura continua delle "chiavi" TMR_ECHO di ritorno di quest'altro gruppo; e così via fino alla fine. Tutto ciò è possibile, poiché bisogna non dimenticare che gli eventi Midi temporizzati mediante l'evento di timing TMR_WAIT_REL (che rappresenta il Tempo Delta) vengono "accodati" e gestiti dal dispositivo l'uno dopo l'altro secondo l'ordine temporale della loro attivazione imposto da TMR_WAIT_REL (Tempo Delta), ossia attivati ciascuno "a suo tempo". Mentre, dunque, "/dev/sequencer2" gestisce in modo pienamente autonomo (è utile ricordare anche questa caratteristica) gli eventi Midi, il programma può effettuare tranquillamente la lettura della "eco" di ritorno della "chiave" di TMR_ECHO proveniente da "/dev/sequencer2".