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 di tipo "1", 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 diventa 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 (espressi invece nel file Midi in formato a lunghezza variabile) fra ogni evento Midi e quello immediatamente successivo;
  • risoluzione del Tempo Delta (TΔ).

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 tli elementi, considerando anche la risoluzione del Tempo Delta, 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
| 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 TD = 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 di tipo 1 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, b
  If b <> 1 Then Error.Raise("Il tipo " & b & " non è supportato dal programma !")
  Seek #fl, 11
  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
        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 &F0          Evento SysSex
        Repeat
          b = Read #fl As Byte
        Until b = &7F
          Avanza(fl, 1)
        Case 15
          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
      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 "\n\e[31m"; CStr(Date(0, 0, 0, 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