Estrarre da un file sf2 i dati audio di ciascun suono campionato e creare un file wav

Da Gambas-it.org - Wikipedia.

Un file banco di suoni di formato sf2 è semplicemente una libreria di suoni per la riproduzione di dati Midi che si basa su tabelle di suoni campionati (wavetable).

Il file contiene, dunque, dati audio di suoni campionati che possono essere successivamente manipolati dal calcolatore per generare le restanti frequenze sonore appartenenti a quel timbro originario.

Il file .sf2 è costituito, fra l'altro, da diversi blocchi comprendenti specifiche informazioni di carattere generale sul file stesso. In particolare il sub-blocco denominato "shdr" contiene i nomi di ciascun suono campionato e la posizione iniziale e finale dei suoi dati audio grezzi all'interno del sub-blocco "sdtasmpl". Quest'ultimo contiene i dati audio grezzi, dunque, di tutti i suoni campionati contenuti nel file sf2.


Mostriamo un possibile codice per estrarre da un file sf2 i dati audio grezzi di ciascun suono campionato, presenti nel file, con i quali si generarà per ciascun suono un distinto file audio wav:

Public Struct sfSample
  achSampleName[20] As Byte
  dwStart As Integer
  dwEnd As Integer
  dwStartLoop As Integer
  dwEndLoop As Integer
  dwSampleRate As Integer
  byOriginalPitch As Byte
  chPitchCorrection As Byte
  wSampleLink As Short
  sfSampleType As Short
End Struct


Public Sub Main()
   
 Dim fl As File
 Dim sf2, s As String
 Dim bb As Byte[]
 Dim i, j, i2 As Integer
 Dim sfS As SfSample
 Dim ssff As SfSample[]

  sf2 = "/percorso/del/file.sf2"
  
  fl = Open sf2 For Read
  If IsNull(fl) Then Error.Raise("Impossibile caricare il file '" & sf2 & " ' !")
  
  With bb = New Byte[Lof(fl)]
    .Read(fl, 0, Lof(fl))
    s = .ToString(0, bb.Count)
    If IsNull(s) Then Error.Raise("Il file caricato non contiene dati !")
  End With
  bb.Clear
    
' Individua la posizione del sub-Blocco 'shdr':
  i = InStr(s, "shdr")
  If i = 0 Then Error.Raise("Impossibile trovare il sub-blocco 'shdr' !")

' Individua la posizione del sub-blocco "sdtasmpl":
  i2 = InStr(s, "sdtasmpl")
  If i2 = 0 Then Error.Raise("Impossibile trovare il sub-blocco 'sdtasmpl' !")
 
' Individua la quantità di byte che compongono l'intero blocco 'shdr':
  Seek #fl, i + 3
  j = Read #fl As Integer
 
' Raccoglie ad ogni ciclo 46 byte contenenti i vari elementi del suono campionato, rappresentati dai membri della "Struttura".
' In particolare serviranno in seguito i seguenti 4 membri: nome del suono campionato, byte iniziale del suono campionato dall'inizio dei dati audio del sotto-blocco "sdtasmpl",
' byte finale del suono campionato e frequenza di campionamento dei dati del campione. Va precisato che la dimensione del suono campionato, data dalla sottrazione
' dei membri della Struttura ".dWEnd" e ".dwStart" è espressa in dati di campionamento, i quali hanno sempre una risoluzione a 16 bit:
  ssff = New SfSample[]

  While Seek(fl) < j + i
  
    sfS = New SfSample
  
    With sfS
      .achSampleName.Read(fl, 0, 20)
      .dwStart = Read #fl As Integer
      .dwEnd = Read #fl As Integer
      .dwStartLoop = Read #fl As Integer
      .dwEndLoop = Read #fl As Integer
      .dwSampleRate = Read #fl As Integer
      .byOriginalPitch = Read #fl As Byte
      .chPitchCorrection = Read #fl As Byte
      .wSampleLink = Read #fl As Short
      .sfSampleType = Read #fl As Short
    End With
    
    ssff.Add(sfS)
   
  Wend
  
' Elimina l'ultima variabile di tipo "Struttura", poiché contenente i dati terminale del blocco 'shdr' che sono non significativi:
  ssff.Remove(ssff.Max)
 
' Mostra i nomi ad alcune caratteristiche dei suoni campionati presenti nel file sf2:
  Print "Suoni campionati presenti nel file: "; File.Name(sf2)
  
  For j = 0 To ssff.Max
    Print "\nSuono campionato n. "; j; ":"
    With ssff[j]
      Print "  - nome assegnato: "; .achSampleName.ToString()
      Print "  - frequenza campionamento: "; .dwSampleRate;; "hertz"
      Print "  - dimensione: "; (.dwEnd - .dwStart) * 2;; "byte"
    End With
   
    CreaFileWAV(fl, ssff[j])
   
  Next
  
  fl.Close  
 
End


Private Procedure CreaFileWAV(camp As File, sf As SfSample)

 Dim wav As File
 Dim bb As Byte[]
 Dim dmn As Integer
 
  wav = Open "/tmp" &/ sf.achSampleName.ToString() & ".wav" For Create
  
  dmn = (sf.dwEnd - sf.dwStart) * SizeOf(gb.Short)
  
  Seek #camp, sf.dwStart * SizeOf(gb.Short)

' Comincia a scrivere il blocco d'intestazione del futuro file wav:
  Write #wav, "RIFF"
   
' Continua ad aggiungere altri elementi costituenti il blocco d'intestazione del futuro file wav:
  Write #wav, (dmn) + 36 As Integer
  bb = New Byte[]
  bb = [&57, &41, &56, &45, &66, &6D, &74, &20, 16, 0, 0, 0, 1, 0, 1, 0]
  bb.Write(wav, 0, bb.Count)
  bb.Clear
 
  Write #wav, sf.dwSampleRate As Integer
  Write #wav, sf.dwSampleRate * 2 As Integer
 
  bb = [2, 0, 16, 0, &64, &61, &74, &61]
  bb.Write(wav, 0, bb.Count)
  bb.Clear
  
  Write #wav, dmn As Integer
  
  bb = New Byte[dmn * 2]

  bb.Read(camp, 0, dmn)
  bb.Write(wav, 0, bb.Count)
  
' Al termine dei dati audio di ciascun suono campionato facciamo avanzare il puntatore interno del file di 92 byte (46 * 16 bit),
' poiché il valore espresso dal membro ".dwEnd" della Struttura rappresenta il primo di 46 byte di valore zero, posti dopo i dati audio del suono campionato.
' Come già detto in precedenza tali valori, indicanti quantità di byte all'interno del sotto-blocco "sdtasmpl", vanno moltiplicati per i bit (16 bit)della risoluzione di ogni campione audio.
  Seek #camp, Seek(camp) + 92

End



Riferimenti