Conoscere la durata di un file Midi con le sole risorse di Gambas

Da Gambas-it.org - Wikipedia.

Il procedimento, seguito per ottenere la durata in secondi di un file Midi deve tenere in debita considerazione la circostanza che all'interno di un brano musicale possono insistere una o più variazioni del tempo metronomico. La durata del file Midi rappresenta così la somma delle singole durate determinate dagli eventuali cambi del tempo metronomico all'interno del file Midi.

Ciò richiede innanzitutto la conoscenza dei seguenti elementi:

  • tempo metronomico: movimenti (battute) per minuto (bpm), che si otterrà a sua volta dallo specifico Meta-evento FF 51;
  • tick reali dell'intera traccia Midi più grande, ossia la quantità di tick ottenuti dalle somme dei Tempi Delta (quest'ultimi convertiti ciascuno dal proprio formato a lunghezza variabile, nel quale sono espressi nel file Midi) fra ogni evento Midi e quello immediatamente successivo;
  • risoluzione del Tempo Delta (TΔ), espressa in ordine Big-Endian nel 13° e nel 14° byte del file Midi.

Pertanto, per ciascun "tratto" parziale di durata devono essere ottenuti il tempo metronomico (bpm), leggendolo dall'apposito Meta-evento FF 51, e i tick reali. Da tali elementi, considerando anche la risoluzione del Tempo Delta (TΔ), si otterrà per ogni "tratto" la durata in secondi, adottando la seguente formula:

((60 / bpm) / risoluzione_TΔ) * tick_reali = secondi

Successivamente bisogna ricavare anche il tempo metronomico e la quantità di tick reali maggiore fra le tracce (MTrk), e da questi due elementi, unitamente alla costante risoluzione del Tempo Delta, si otterrà la durata in secondi del "tratto finale" (per "tratto finale" qui intendiamo la quantità di Tempo Delta compresa fra l'ultimo Meta-evento FF 51 e il Meta-evento di chiusura FF 2F 00 della traccia avente la quantità maggiore di tick rispetto alle altre).

In ogni caso va ricordato che il valore predefinito del tempo metronomico è pari a 120 bpm.

Si potrà avere dunque, ad esempio, una situazione di questo genere:

0------FF51--------FF51----------FF51--------------FF2F00   (Traccia Midi di maggiore durata)
| 120        bmp1         bmp2   |        bmp3     |
|______|___________|_____________|_________________|
     tratto iniziale durata         tratto finale
|__________________________________________________|
           Durata totale del file Midi

In altri termini si procederà a:

1) estrarre il numero dei tick reali compresi fra l'inizio assoluto delle tracce Midi (punto TΔ = 0) e l'ulimo Meta-evento FF 51, ossia fino all'ultimo Tempo Delta precedente l'ultimo Meta-evento FF 51): "1° tratto" della durata del file Midi. Questo andrà ottenuto estraendo i tick reali per ogni tratto compreso tra i vari Meta-eventi FF 51 del "1° tratto".

2) estrarre il numero dei tick reali più grande fra le tracce esistenti del file Midi. Ciò significa che si dovrà ottenere il numero di tick reali della traccia che dura di più.

3) ottenere i tick reali del "tratto finale" più lungo fra le tracce esistenti, sottraendo i tick reali del "tratto iniziale" (punto 1) ai tick reali della traccia più duratura (punto 2).

4) convertire in secondi separatamente i tick reali di ciascun tratto costituente il "tratto iniziale".

5) convertire in secondi i tick reali del "tratto finale".

6) sommare i valori temporali, espressi in secondi, dei singoli tratti (punto 4) costituenti il "tratto iniziale" della durata del file Midi.


Esempio pratico:
a) 77959 tick reali della traccia più duratura;
b) 52992 tick reali del "tratto iniziale";
c) 77952 - 52992 = 24960 tick reali del "tratto finale" più lungo fra le tracce esistenti;
d) conversione in secondi di ciascun singolo tratto costituente il "tratto iniziale" della durata del file Midi;
e) secondi_tratto_iniziale + secondi_tratto_finale = durata effettiva del file Midi


Dunque, per conoscere la durata di un file Midi mediante le sole risorse di Gambas, è possibile utilizzare un codice come il seguente, nel quale si otterranno anche le durate delle singole tracce MTrk presenti nel file ed alcune generiche informazioni:

Public Sub Main()
 
 Dim fl As File
 Dim fileMidi, s As String
 Dim hbpm, tot, mx, tratto1 As Integer
 Dim td, bpm, c As Short
 Dim tr, b, n, av As Byte
 Dim cmd, tipocmd, ultimo As Byte
 Dim f, durata As Float
  
 fileMidi = "/percorso/del/file.mid"
 Print "File Midi:         "; fileMidi
 
 fl = Open fileMidi For Read
 If IsNull(fl) Then Error.Raise("Impossibile aprire il file !")
 
 Read #fl, s, 4
 If s <> "MThd" Then Error.Raise("Il file non è uno standard Midi !")
 Print "Dimensione:    "; Lof(fl); " byte"
 Seek #fl, 9
 Read #fl, c
 Print "Tipo file:         "; c
 Read #fl, tr
 Print "Numero tracce:   "; Format(tr, "###")
 Read #fl, td
 td = Rol(td, 8)
 Print "Risoluzione TΔ:  "; Format(td, "###")
 Avanza(fl, 8)
 
 bpm = 120
 
 Repeat
   tot = 0
   Do
     tot += legge_var(fl)
     Read #fl, b
     If b And 128 Then   ' Status running
       cmd = b
       If b < &F1 Then ultimo = cmd
       av = 2
     Else
       cmd = ultimo
       av = 1
     Endif
     tipocmd = Shr(cmd, 4)
     Select Case tipocmd
       Case 8 To 11
         Avanza(fl, av)
       Case 12 To 13 
         Avanza(fl, 1)
       Case 14
         Avanza(fl, av)
       Case 15
         If b = &F0 Then          ' Evento SysEx
           Repeat
             b = Read #fl As Byte
           Until b = &F7
         Else
           Read #fl, b
           Select Case b
             Case &51
               durata += DurataParziale(bpm, td, tot) 
               tratto1 += tot
               tot = 0
               Avanza(fl, 1)
               hbpm = 0
               For c = 2 To 0 Step -1
                 Read #fl, n
                 hbpm += Shl(CInt(n), 8 * c)
               Next
               bpm = 60000000 / hbpm
             Case &2F
               mx = Max(mx, tot)
               Exit
             Case Else
               Read #fl, b
               Avanza(fl, b)
           End Select
         Endif
     End Select
   Loop
   Dec tr
   If tr > 0 Then Avanza(fl, 9)
 Until tr = 0
 
 fl.Close
 
 f = DurataParziale(bpm, td, Max(mx, tratto1) - tratto1)
 
 Print "\nDURATA:       \e[31m\e[1m"; CStr(Time(0, 0, 0, (f + durata) * 1000)); "\e[0m"
 
End
 
 
Private Procedure Avanza(mf As File, av As Byte)
 
 Dim b, n As Byte
  
 For b = 1 To av
   Read #mf, n
 Next
   
End
 
Private Function legge_var(mf As File) As Integer   ' Legge un numero a lunghezza-variabile (Tempo Delta)

 Dim vl As Integer
 Dim c As Byte
 
 Repeat
   Read #mf, c
   vl = Shl(vl, 7) Or (c And 127)
 Until Not (c And 128)
 
 Return vl
 
End
 
Private Function DurataParziale(bpm As Integer, del As Short, par As Integer) As Float
 
  Return CFloat((((60000000 / bpm) / del) * par) / 1000000)
 
End