Visualizzare la forma d'onda di un file WAV

Da Gambas-it.org - Wikipedia.
Versione del 23 lug 2025 alle 22:15 di Vuott (discussione | contributi)
(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)
Vai alla navigazione Vai alla ricerca

E' possibile visualizzare con le sole funzioni native di Gambas la forma d'onda di un file audio WAV.

Public Sub Form_Open()

 Dim audioPath As String = "/percorso/del/file.wav"

 Dim samplesL As New Float[]
 Dim samplesR As New Float[]
 Dim sampleRate, channels As Integer

 With Me
   .Move(0, 0, Screen.AvailableWidth, Screen.AvailableHeight)
   .Arrangement = Arrange.Fill
 End With

 LoadWav(audioPath, samplesL, samplesR, ByRef sampleRate, ByRef channels)

' Caratteristiche del file wav caricato:
 Print "Canali: "; channels
 Print "Frequenza: "; sampleRate
 If samplesL.Count > 0 Then
   Print "Campione L[0]: "; samplesL[0]
 Else
   Print "Campione L[0]: Vuoto"
 Endif
 If samplesR.Count > 0 Then
   Print "Campione R[0]: "; samplesR[0]
 Else
   Print "Campione R[0]: Vuoto"
 Endif

' Analisi:
 CalcolaAnalisi("Sinistro", samplesL)
 If channels = 2 Then CalcolaAnalisi("Destro", samplesR)

' Parametri grafici:
 Dim width As Integer = Min(2000, samplesL.Count)
 Dim height As Integer = 300
 Dim centerYL As Integer = height \ 4
 Dim centerYR As Integer = (3 * height) \ 4
 Dim ampMax As Integer = (height \ 4) - 10
 Dim img As Image = New Image(width, height, Color.White)

' 🔴 Canale sinistro - peak rendering:
 Paint.Begin(img)
 Paint.LineWidth = 1
 Paint.Brush = Paint.Color(Color.Red)
 DisegnaCanalePeak(samplesL, width, centerYL, ampMax)
 Paint.Stroke
 Paint.End

' 🔵 Canale destro (se stereo):
 If channels = 2 Then
   Paint.Begin(img)
   Paint.LineWidth = 1
   Paint.Brush = Paint.Color(Color.Blue)
   DisegnaCanalePeak(samplesR, width, centerYR, ampMax, True)
   Paint.Stroke
   Paint.End
 Endif

 ImageView1.Image = img

End


' ---------------------------------------------------------------------
' Disegna una forma d'onda con min-max rendering verticale
' ---------------------------------------------------------------------
Private Sub DisegnaCanalePeak(dati As Float[], width As Integer, centerY As Integer, ampMax As Integer, Optional invert As Boolean)

 Dim x, i, iStart, iEnd As Integer
 Dim val As Float, minVal As Float, maxVal As Float

 For x = 0 To width - 1
   iStart = CInt((CLong(x) * dati.Count) \ width)
   iEnd = CInt((CLong(x + 1) * dati.Count) \ width) - 1
   If iEnd >= dati.Count Then iEnd = dati.Count - 1

   minVal = 1
   maxVal = -1

   For i = iStart To iEnd
     val = dati[i]
     If val < minVal Then minVal = val
     If val > maxVal Then maxVal = val
   Next

   If invert Then
     Paint.MoveTo(x, centerY + Int(minVal * ampMax))
     Paint.LineTo(x, centerY + Int(maxVal * ampMax))
   Else
     Paint.MoveTo(x, centerY - Int(maxVal * ampMax))
     Paint.LineTo(x, centerY - Int(minVal * ampMax))
   Endif
 Next

End


' ---------------------------------------------------------------------
' Calcola RMS, picco, range dinamico in dB
' ---------------------------------------------------------------------
Private Sub CalcolaAnalisi(nome As String, dati As Float[])

 If dati.Count = 0 Then
   Print "Canale "; nome; ": vuoto"
   Return
 Endif

 Dim sumSq As Float = 0
 Dim minVal As Float = dati[0]
 Dim maxVal As Float = dati[0]
 Dim i As Integer

 For i = 0 To dati.Count - 1
   Dim val As Float = dati[i]
   sumSq += val ^ 2
   If val < minVal Then minVal = val
   If val > maxVal Then maxVal = val
 Next

 Dim rms As Float = Sqr(sumSq / dati.Count)
 Dim peak As Float = Max(Abs(minVal), Abs(maxVal))
 Dim rangeDB As Float = 0

 If minVal <> 0 And maxVal <> 0 Then
   rangeDB = 20 * Log10(Abs(maxVal / minVal))
 Endif

 Print " Canale "; nome
 Print " - RMS:   "; Format$(rms, "0.000")
 Print " - Picco: "; Format$(peak, "0.000")
 Print " - Range dinamico: "; Format$(rangeDB, "0.0"); " dB"

End


' ---------------------------------------------------------------------
' WAV parser robusto con lettura fmt e data
' ---------------------------------------------------------------------
Private Sub LoadWav(audioPath As String, samplesL As Float[], samplesR As Float[], ByRef sampleRate As Integer, ByRef channels As Integer)

 Dim f As File
 Dim header As Byte[]
 Dim i, fmtOffset, dataOffset As Integer
 Dim fmtSize, sampleStart As Integer
 Dim audioFormat, bitsPerSample, dataSize As Integer
 Dim bytesPerSample, numSamples As Integer

 f = Open audioPath For Read
 header = New Byte[Lof(f)]
 header.Read(f, 0, header.Count)
 Close #f

 If header.ToString(0, 4) <> "RIFF" Then Error.Raise("Non è un file WAV RIFF.")
 If header.ToString(8, 4) <> "WAVE" Then Error.Raise("Formato WAV non riconosciuto.")

 i = 12
 While i < header.Count - 8
   If header.ToString(i, 4) = "fmt " Then
     fmtSize = ReadLE32(header, i + 4)
     fmtOffset = i + 8
     Exit
   Endif
   Inc i
 Wend

 If fmtOffset = 0 Or fmtSize < 16 Then Error.Raise("Blocco 'fmt ' non valido.")

 audioFormat = ReadLE16(header, fmtOffset)
 channels = ReadLE16(header, fmtOffset + 2)
 sampleRate = ReadLE32(header, fmtOffset + 4)
 bitsPerSample = ReadLE16(header, fmtOffset + 14)

 If audioFormat <> 1 Then Error.Raise("Solo WAV PCM supportati.")
 If bitsPerSample <> 16 Then Error.Raise("Solo WAV a 16 bit supportati.")

 bytesPerSample = bitsPerSample \ 8

 i = fmtOffset + fmtSize
 While i < header.Count - 8
   If header.ToString(i, 4) = "data" Then
     dataSize = ReadLE32(header, i + 4)
     dataOffset = i + 8
     Exit
   Endif
   Inc i
 Wend

 If dataOffset = 0 Then Error.Raise("Blocco 'data' non trovato.")

 sampleStart = dataOffset
 numSamples = dataSize \ (bytesPerSample * channels)

 For i = 0 To numSamples - 1
   Dim offset As Integer = sampleStart + (i * channels * bytesPerSample)

   Dim sampleL As Integer = ReadLE16(header, offset)
   If sampleL > 32767 Then sampleL -= 65536
   samplesL.Push(sampleL / 32768.0)

   If channels = 2 Then
     Dim sampleR As Integer = ReadLE16(header, offset + 2)
     If sampleR > 32767 Then sampleR -= 65536
     samplesR.Push(sampleR / 32768.0)
   Endif
 Next

End


Private Function ReadLE16(b As Byte[], index As Integer) As Integer

 Return CInt(b[index]) Or Shl(CInt(b[index + 1]), 8)

End


Private Function ReadLE32(b As Byte[], index As Integer) As Integer

 Return CInt(b[index]) Or Shl(CInt(b[index + 1]), 8) Or Shl(CInt(b[index + 2]), 16) Or Shl(CInt(b[index + 3]), 24)

End