Imports System.Math
''' <summary>
''' Classe che gestisce i dati audio ottenuti dal Wave Reader, e si preoccupa di comprimerli
''' e visualizzarli.
''' </summary>
Public Class WaveDataManager
Private _AudioData() As Int16
Private _MinimumAplitude, _MaximumAmplitude As Int16
Private _ByteRate As Int64
Private _Visualizer As IVisualizer
Private DefaultVisualizer As WaveFormVisualizer
''' <summary>
''' I dati audio estratti dal file, espressi come array di Int16. Ogni valore rappresenta
''' l'ampiezza dell'onda in un dato punto.
''' </summary>
Public ReadOnly Property AudioData() As Int16()
Get
Return _AudioData
End Get
End Property
''' <summary>
''' Minima ampiezza assunta dall'onda in tutto il brano.
''' </summary>
Public ReadOnly Property MinimumAmplitude() As Int16
Get
Return _MinimumAplitude
End Get
End Property
''' <summary>
''' Massima ampiezza assunta dall'onda in tutto il brano.
''' </summary>
Public ReadOnly Property MaximumAmplitude() As Int16
Get
Return _MaximumAmplitude
End Get
End Property
''' <summary>
''' Numero di byte letti per secondo.
''' </summary>
Public ReadOnly Property ByteRate() As Int64
Get
Return _ByteRate
End Get
End Property
''' <summary>
''' Rappresenta il Visualizer che questo oggetto userà per disegnare la visualizzazione
''' dell'onda.
''' </summary>
Public Property Visualizer() As IVisualizer
Get
If _Visualizer Is Nothing Then
_Visualizer = Me.DefaultVisualizer
End If
Return _Visualizer
End Get
Set(ByVal value As IVisualizer)
_Visualizer = value
End Set
End Property
Sub New(ByVal Data() As Int16, ByVal ByteRate As Int64)
_AudioData = Data
Me._ByteRate = ByteRate
_MinimumAplitude = Short.MaxValue
_MaximumAmplitude = Short.MinValue
For I As Int64 = 0 To Me.AudioData.Length - 1
If Me.AudioData(I) < Me.MinimumAmplitude Then
_MinimumAplitude = Me.AudioData(I)
End If
If Me.AudioData(I) > Me.MaximumAmplitude Then
_MaximumAmplitude = Me.AudioData(I)
End If
Next
DefaultVisualizer = New WaveFormVisualizer(Me)
Me.Visualizer = DefaultVisualizer
End Sub
''' <summary>
''' Comprime i dati audio estratti dal file wave, effettuando una scernita di valori da tenere,
''' e altri da scartare. Infatti, se il refresh viene fatto ogni 10ms, è inutile tenere in memoria,
''' inutilizzati, tutti i dati che corrispondono a intervalli minori di 10ms. L'algoritmo è
''' indubbiamente data-lossy, ma dato che serve solo per la visualizzazione, non è rilevante
''' scartare valori che non possiamo emmeno percepire.
''' </summary>
''' <param name="RefreshTime">Tempo di refresh dell'immagine (in secondi).</param>
Public Sub CompressData(ByVal RefreshTime As Double)
Dim ShortPerInterval As Int64 = RefreshTime * _ByteRate / 2
Dim TempData() As Int16
ReDim TempData(Math.Ceiling(Me.AudioData.Length / ShortPerInterval) - 1)
For I As Int64 = 0 To Me.AudioData.Length - 1 Step ShortPerInterval
TempData(I / ShortPerInterval) = Me.AudioData(I)
Next
Me._ByteRate /= ShortPerInterval
_AudioData = TempData
TempData = Nothing
End Sub
''' <summary>
''' Ottiene una bitmap che rappresenta la forma d'onda di una parte o di tutto il file.
''' </summary>
''' <param name="Width">Larghezza dell'immagine da generare.</param>
''' <param name="Height">Altezza dell'immagine da generare.</param>
''' <param name="From">Punto da cui far iniziare il rendering: è espresso in numero di secondi
''' trascorsi dall'inizio del brano.</param>
''' <param name="To">Punto in cui terminare il rendering: è espresso in numero di secondi
''' trascorsi dall'inizio del brano.</param>
''' <returns>Restituisce una bitmap delle dimensioni specificate, con i colori impostati
''' nel Visualizer. Se Width e Height sono 0, restituisce Nothing.</returns>
Public Function GetWaveForm(ByVal Width As Int16, ByVal Height As Int16, Optional ByVal [From] As Double = -1, Optional ByVal [To] As Double = -1) As Bitmap
Return DefaultVisualizer.Render(Width, Height, [From], [To])
End Function
''' <summary>
''' Imposta come Visualizer quello di default.
''' </summary>
Public Sub SetDefaultVisualizer()
Me.Visualizer = DefaultVisualizer
End Sub
End Class
''' <summary>
''' Definisce i metodi e le proprietà standard di un Visualizer, ossia di un oggetto che, ricevendo
''' in input i dati audio passatagli dal WaveDataManager, crea un'immagine che rappresenti le
''' caratteristiche della forma d'onda.
''' </summary>
''' <remarks>Ogni Visualizer deve implementare l'interfaccia IVisualizer.</remarks>
Public Interface IVisualizer
''' <summary>
''' Crea e restituisce l'immagine creata dal Visualizer.
''' </summary>
''' <param name="Width">Larghezza dell'immagine da generare.</param>
''' <param name="Height">Altezza dell'immagine da generare.</param>
''' <param name="From">Punto da cui far iniziare il rendering: è espresso in numero di secondi
''' trascorsi dall'inizio del brano.</param>
''' <param name="To">Punto in cui terminare il rendering: è espresso in numero di secondi
''' trascorsi dall'inizio del brano.</param>
''' <returns>Restituisce una bitmap delle dimensioni specificate, con i colori impostati
''' nel Visualizer. Se Width e Height sono 0, restituisce Nothing.</returns>
Function Render(ByVal Width As Int16, ByVal Height As Int16, Optional ByVal [From] As Double = -1, Optional ByVal [To] As Double = -1) As Bitmap
''' <summary>
''' Indica il WaveDataManager associato a questo Visualizer.
''' </summary>
''' <remarks>Questa proprietà è necessaria per evitare di ripassare tutti i dati alla funzione
''' di rendering ad ogni refresh, il che comporterebbe un enorme spreco di memoria.</remarks>
ReadOnly Property AssociatedManager() As WaveDataManager
''' <summary>
''' Colore usato per lo sfondo.
''' </summary>
Property BackgroundColor() As Color
''' <summary>
''' Colore usato per la visualizzazione.
''' </summary>
Property ForeColor() As Color
End Interface
''' <summary>
''' Visualzer standard: disegna la forma d'onda associata ai dati di input.
''' </summary>
''' <remarks></remarks>
Public Class WaveFormVisualizer
Implements IVisualizer
Private _AssociatedManager As WaveDataManager
Private _WaveColor As Color
Private _BackgroundColor As Color
Public ReadOnly Property AssociatedManager() As WaveDataManager Implements IVisualizer.AssociatedManager
Get
Return _AssociatedManager
End Get
End Property
Public Property WaveColor() As Color Implements IVisualizer.ForeColor
Get
Return _WaveColor
End Get
Set(ByVal value As Color)
_WaveColor = value
End Set
End Property
Public Property BackgroundColor() As Color Implements IVisualizer.BackgroundColor
Get
Return _BackgroundColor
End Get
Set(ByVal value As Color)
_BackgroundColor = value
End Set
End Property
Public Function Render(ByVal Width As Short, ByVal Height As Short, Optional ByVal From As Double = -1.0, Optional ByVal [To] As Double = -1.0) As System.Drawing.Bitmap Implements IVisualizer.Render
If Width = 0 Or Height = 0 Then
Return Nothing
End If
Dim B As New Bitmap(Width, Height)
Dim G As Graphics = Graphics.FromImage(B)
Dim Step1 As Single
Dim Step2 As Single
Dim Step3 As Single
Dim Points As New List(Of PointF)
Dim IndexFrom, IndexTo As Int64
With Me.AssociatedManager
'Calcola l'indice di partenza e quello di arrivo sulla base dei limiti di tempo
'forniti con i parametri From e To
If [From] > -1 And [To] > -1 And [To] > [From] Then
IndexFrom = (.AudioData.Length * ([From] / (.AudioData.Length * 2 / .ByteRate)))
IndexTo = (.AudioData.Length * ([To] / (.AudioData.Length * 2 / .ByteRate)))
ElseIf [From] <= -1 And [To] > -1 Then
IndexFrom = 0
IndexTo = (.AudioData.Length * ([To] / (.AudioData.Length * 2 / .ByteRate)))
Else
IndexFrom = 0
IndexTo = .AudioData.Length - 1
End If
For I As Int64 = IndexFrom To IndexTo
If I < 0 Then
Points.Add(New PointF(CSng(Width * (I - IndexFrom) / (IndexTo - IndexFrom)), 0))
Continue For
End If
If I > .AudioData.Length - 1 Then
Exit For
End If
'Assegna all'ordinata di questo punto un valora tanto maggiore quando più
'l'ampiezza è grande. Step1 è sempre comprso tra 0 e 1, e indica il rapporto
'tra l'ampiezza dell'onda in questo punto e la massima oscillazione avuta
'nell'onda stessa
Step1 = .AudioData(I) / (CSng(.MaximumAmplitude) - CSng(.MinimumAmplitude))
'Step2 assume un valore che indica il valore reale dell'altezza dell'onda
Step2 = Step1 * Height
'Step3 è l'ordinata del punto
Step3 = Step2 + Height / 2
Points.Add(New PointF(CSng(Width * (I - IndexFrom) / (IndexTo - IndexFrom)), Step3))
Next
End With
G.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
G.Clear(Me.BackgroundColor)
G.DrawCurve(New Pen(Me.WaveColor), Points.ToArray)
Return B
End Function
Sub New(ByVal Manager As WaveDataManager)
_AssociatedManager = Manager
_WaveColor = Color.Black
_BackgroundColor = Color.White
End Sub
End Class
''' <summary>
''' Visualizer a rettangoli: disegna due rettangoli la cui lunghezza è proporzionale al valore di picco
''' raggiunto dall'onda al termina dell'intervallo considerato.
''' </summary>
Public Class RectangleVisualizer
Implements IVisualizer
Private _AssociatedManager As WaveDataManager
Private PreviousImage As Bitmap
Private _WaveColor As Color
Private _BackgroundColor As Color
Public ReadOnly Property AssociatedManager() As WaveDataManager Implements IVisualizer.AssociatedManager
Get
Return _AssociatedManager
End Get
End Property
Public Property RectangleColor() As Color Implements IVisualizer.ForeColor
Get
Return _WaveColor
End Get
Set(ByVal value As Color)
_WaveColor = value
End Set
End Property
Public Property BackgroundColor() As Color Implements IVisualizer.BackgroundColor
Get
Return _BackgroundColor
End Get
Set(ByVal value As Color)
_BackgroundColor = value
End Set
End Property
Public Function Render(ByVal Width As Short, ByVal Height As Short, Optional ByVal From As Double = -1.0, Optional ByVal [To] As Double = -1.0) As System.Drawing.Bitmap Implements IVisualizer.Render
Dim B As New Bitmap(Width, Height)
Dim G As Graphics = Graphics.FromImage(B)
Dim IndexTo As Int64
Dim Length As Single = 1
With Me.AssociatedManager
IndexTo = (.AudioData.Length * ([To] / (.AudioData.Length * 2 / .ByteRate)))
If IndexTo <= 0 Then
IndexTo = 1
End If
If IndexTo > .AudioData.Length - 2 Then
IndexTo = .AudioData.Length - 2
End If
'Se questo è un punto di minimo o di massimo, aggiorna l'immagine, altrimenti
'usa quella vecchia
If Abs(.AudioData(IndexTo)) > Abs(.AudioData(IndexTo - 1)) And Abs(.AudioData(IndexTo)) > Abs(.AudioData(IndexTo + 1)) Then
Length = Abs(.AudioData(IndexTo) / (CSng(.MaximumAmplitude) - CSng(.MinimumAmplitude))) * Width
G.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
G.Clear(Me.BackgroundColor)
G.FillRectangle(New SolidBrush(Me.RectangleColor), New Rectangle(Width / 2 - Length, Height / 2 - 20, Length, 40))
G.FillRectangle(New SolidBrush(Me.RectangleColor), New Rectangle(Width / 2, Height / 2 - 20, Length, 40))
PreviousImage = B
Else
B = PreviousImage
End If
End With
Return B
End Function
Sub New(ByVal Manager As WaveDataManager)
_AssociatedManager = Manager
End Sub
End Class
''' <summary>
''' Visualizer a fasce: disegna un certo numero di fasce di colore (pari a Width/5). Ogni fascia è
''' più chiara (ossia più vicino al colore di sfondo) se l'onda, in quell'intervallo, assume valori
''' di ampiezza bassi; è invece più scura (ossia più vicino al colore di visualizzazione) se l'onda,
''' in quell'intervallo, assume valori di ampiezza alti.
''' </summary>
Public Class LoudnessVisualizer
Implements IVisualizer
Private _AssociatedManager As WaveDataManager
Private PreviousImage As Bitmap
Private _WaveColor As Color
Private _BackgroundColor As Color
Public ReadOnly Property AssociatedManager() As WaveDataManager Implements IVisualizer.AssociatedManager
Get
Return _AssociatedManager
End Get
End Property
Public Property ForeColor() As Color Implements IVisualizer.ForeColor
Get
Return _WaveColor
End Get
Set(ByVal value As Color)
_WaveColor = value
End Set
End Property
Public Property BackgroundColor() As Color Implements IVisualizer.BackgroundColor
Get
Return _BackgroundColor
End Get
Set(ByVal value As Color)
_BackgroundColor = value
End Set
End Property
Public Function Render(ByVal Width As Short, ByVal Height As Short, Optional ByVal From As Double = -1.0, Optional ByVal [To] As Double = -1.0) As System.Drawing.Bitmap Implements IVisualizer.Render
Dim B As New Bitmap(Width, Height)
Dim G As Graphics = Graphics.FromImage(B)
Dim IndexTo, IndexFrom As Int64
Dim Length As Single = 1
Dim Brush As New Drawing2D.LinearGradientBrush(New Point(0, 0), New Point(Width, 0), Me.ForeColor, Me.BackgroundColor)
Dim SplitPoints As New List(Of Single)
Dim Colors As New List(Of Color)
With Me.AssociatedManager
If [From] > -1 And [To] > -1 And [To] > [From] Then
IndexFrom = (.AudioData.Length * ([From] / (.AudioData.Length * 2 / .ByteRate)))
IndexTo = (.AudioData.Length * ([To] / (.AudioData.Length * 2 / .ByteRate)))
ElseIf [From] <= -1 And [To] > -1 Then
IndexFrom = 0
IndexTo = (.AudioData.Length * ([To] / (.AudioData.Length * 2 / .ByteRate)))
Else
IndexFrom = 0
IndexTo = .AudioData.Length - 1
End If
Dim NewIndexFrom As Int32 = (IndexFrom \ 5) * 5
Dim NewIndexTo As Int64 = IndexFrom + ((IndexTo - IndexFrom) \ 5 + 1) * 5
For I As Int64 = NewIndexFrom To NewIndexTo Step 5
If I < 0 Then
SplitPoints.Add(0)
Colors.Add(Color.FromArgb(0, Me.ForeColor))
Continue For
End If
If I > .AudioData.Length - 1 Or I + 4 > .AudioData.Length - 1 Then
Exit For
End If
'Calcola la massima oscillazione nei 5 punti successivi a questo
Dim Max, Min As Int16
Dim Percent As Single
Max = Int16.MinValue
Min = Int16.MaxValue
For J As Int64 = I To (I + 4)
If .AudioData(J) > Max Then
Max = .AudioData(J)
End If
If .AudioData(J) < Min Then
Min = .AudioData(J)
End If
Next
'Ottiene un valore percentuale tra 0 e 1, confrontandolo con la
'massima oscillazione di tutta l'onda
Percent = (CSng(Max) - CSng(Min)) / (CSng(.MaximumAmplitude) - CSng(.MinimumAmplitude))
'Aggiunge un punto di sfumatura, il cui colore è tanto più trasparente
'quando più debole è l'oscillazione compiuta
SplitPoints.Add((I - NewIndexFrom) / (NewIndexTo - NewIndexFrom))
Colors.Add(Color.FromArgb(255 * Percent, Me.ForeColor))
Next
G.Clear(Me.BackgroundColor)
Dim Blend As New Drawing2D.ColorBlend(Colors.Count)
If SplitPoints(0) <> 0 Then
SplitPoints.Insert(0, 0)
Colors.Insert(0, Color.FromArgb(0, Me.ForeColor))
End If
If SplitPoints(SplitPoints.Count - 1) <> 1 Then
SplitPoints.Add(1)
Colors.Add(Color.FromArgb(0, Me.ForeColor))
End If
Blend.Colors = Colors.ToArray()
Blend.Positions = SplitPoints.ToArray()
Brush.InterpolationColors = Blend
G.FillRectangle(Brush, New Rectangle(0, 0, Width, Height))
End With
Return B
End Function
Sub New(ByVal Manager As WaveDataManager)
_AssociatedManager = Manager
End Sub
End Class