Introduzione

L'azienda per la quale lavoro ? nel settore discografico e mi trovo spesso a gestire i file wave a runtime.

Vedremo quindi in questo articolo come ottenere il numero di canali di un file wave, il samplerate, il bitDepth, come dividere ed unire file Wave, e come avere un lettore multimediale.

Un progetto molto valido che ho trovato su Codeplex ? NAudio (http://naudio.codeplex.com)

Naudio ? un progetto molto valido. Oltre alle informazioni dei file audio d? la possibilit? di avere un equalizzatore, di avere tre tipi diversi di grafici dello spettrometro, ovviamente fa anche da player con i classici pulsanti e la conversione tra diversi formati audio.

Tuttavia fare da zero un'applicazione come quella di Naudio richiede delle conoscenze oltre che da programmazione anche di come funzionano i Bit per Samples, la frequenza di campionamento e altro ancora.

Ad esempio ho visto che muovendo gli slider dell'equalizzatore, per calcolare la nuova frequenza del brano che stiamo ascoltando necessitano di calcolo trigonometrici e non ? quindi questa la sede attuale per queste spiegazioni.

Tratteremo NAudio in un successivo articolo.

Costruiremo quindi un progetto da zero mettendo un player e le informazioni del file audio caricato.

Diamo prima brevemente qualche piccola spiegazione su cosa sono questi termini.

Sample Rate= Il numero di campioni di un suono che sono registrati per secondo. Pi? il sample rate ? alto e pi? ? accurata la rappresentazione digitale del suono.

Bit Depth= ? il numero di bit di informazioni in ogni campione, e corrisponde direttamente alla risoluzione di ogni campione.

Spettrometro: L'analizzatore di spettro ? uno strumento che permette l'analisi dello spettro dei segnali posti al suo ingresso. Quando la musica era solo analogica questa operazione era svolta da dei macchinari grandi e pesanti, oggi basta solo un po' di programmazione.

Implementazione

Creiamo un nuovo progetto WPF in Visual Studio 2015 e lo chiamiamo WPFAudio.

Salviamo poi il progetto.

Dividiamo la Window in due parti verticalmente: la parte a sinistra conterr? un menu con la barra dei pulsanti mentre il resto sar? per lo scorrimento del file audio e per le sue informazioni.

I pulsanti saranno:

1)  Apri file Audio

2)  Play

3)  Pausa

4)  Stop

5)  Back

6)  Forward

Creiamo quindi uno StackPanel e mettiamoci al suo interno i pulsanti.

Questo ? il codice XAML:

<StackPanel Grid.Column="0" Grid.Row="0" >
 <Button Name="ApriFileAudioButton" Content="Apri File Audio"/>
 <Button Name="PlayButton" Content="Play" IsEnabled="False"/>
 <Button Name="StopButton" Content="Stop"/>
 <Button Name="BackButton" Content="Back"/>
 <Button Name="ForwardButton" Content="Forward"/>
 </StackPanel>

Per la parte sulla destra questo sar? il codice:

<Grid Grid.Column="1" Margin="0,0,0,10"  >
 <Grid.RowDefinitions>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 <RowDefinition Height="Auto"/>
 </Grid.RowDefinitions>
 <Grid Grid.Row="0" >
 <Grid.RowDefinitions>
 <RowDefinition/>
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="Auto"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Label Content="File audio:"  Margin="3" />
 <TextBox Grid.Column="1" Margin="3" Name="FileAudioTextBox"  IsReadOnly="True" />
 </Grid>
 <Grid Grid.Row="1">
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="Auto"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition/>
 </Grid.RowDefinitions>
 <Label Content="Durata: " />
 <TextBox  Margin="3" Name="DurataBranoLabel" Grid.Column="1" IsReadOnly="True" />
 </Grid>
 <Grid Grid.Row="2" >
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="Auto"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition/>
 </Grid.RowDefinitions>
 <Label Content="Dimensione file in Bytes: " Margin="3" Grid.Row="0" Grid.Column="0" ></Label>
 <TextBox Margin="3" Name="DimensioneFileLabel" Grid.Row="0" Grid.Column="1" IsReadOnly="True"></TextBox>
 </Grid>
 <Grid Grid.Row="3" >
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="Auto"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition/>
 </Grid.RowDefinitions>
 <Label Content="Player: " Grid.Row="0" Height="Auto" />
 <TextBox Margin="3" Name="TempoTextBox"  Grid.Row="0" Grid.Column="1" IsReadOnly="True"></TextBox>
 </Grid>
 <MediaElement Grid.Row="4" Name="Player" LoadedBehavior="Manual" Source="{Binding Path=FileAudio, Mode=OneWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"  />
 <Slider Margin="3" Grid.Row="5" x:Name="seekBar" Thumb.DragStarted="seekBar_DragStarted"  Thumb.DragCompleted="seekBar_DragCompleted" />
 <Grid Grid.Row="6">
 <Grid.ColumnDefinitions>
 <ColumnDefinition Width="Auto"/>
 <ColumnDefinition Width="*"/>
 </Grid.ColumnDefinitions>
 <Grid.RowDefinitions>
 <RowDefinition/>
 </Grid.RowDefinitions>
 <Label Content="Informazioni tecniche: " />
 <TextBox IsReadOnly="True" Name="InformazioniTextBox"  Grid.Column="1" TextWrapping="Wrap" />
 </Grid>
 </Grid>

Vediamo ora il codice:

Con il seguente codice selezioniamo un file Wave.

Dim dlg As New Microsoft.Win32.OpenFileDialog()
 dlg.Filter = "Audio Files|*.wav"
 dlg.ShowDialog()
 If dlg.FileName = "" Then Exit Sub
 FileAudioTextBox.Text = dlg.FileName

I dati tecnici del file audio sono calcolati cos?:

Dim fh As WaveHeader
 Dim stream As New FileStream(FileAudioTextBox.Text, FileMode.Open)
 Dim br As New BinaryReader(stream)
 fh.Chunk = br.ReadChars(4)
 fh.ChunkSize = br.ReadInt32
 fh.Format = br.ReadChars(4)
 fh.SubChunk1 = br.ReadChars(4)
 fh.SubChunk1Size = br.ReadInt32
 fh.AudioFormat = br.ReadInt16
 fh.Channels = br.ReadInt16
 fh.SampleRate = br.ReadInt32
 fh.ByteRate = br.ReadInt32
 fh.BlockAlign = br.ReadInt16
 fh.BitsPerSample = br.ReadInt16
 For i = 1 To fh.SubChunk1Size - 16
 br.ReadByte()
 Next
 fh.SubChunk2 = br.ReadChars(4)
 fh.SubChunk2Size = br.ReadInt32
 br.Close()
 stream.Close()

La dimensione del file la otteniamo cos?:

Dim infoReader As System.IO.FileInfo
 infoReader = My.Computer.FileSystem.GetFileInfo(PercorsoNomeFile)
 uu = infoReader.Length.ToString

La durata del brano invece la otteniamo con il seguente codice:

durata = MCI.GetSoundLength(PercorsoNomeFile)
Poi con le funzioni TimeSpan la convertiamo in minuti e secondi:
 Dim ts1 As TimeSpan
 ts1 = New TimeSpan(0, 0, durata / 1000)

Le funzioni del player multimediale sono le seguenti:

Play:  Player.Play()

Pausa:  Player.Pause()

Stop:  Player.Stop()

Indietro:  Player.Position = Player.Position - TimeSpan.FromSeconds(10)

Avanti:  Player.Position = Player.Position + TimeSpan.FromSeconds(10)

Ci servir? un timer affinch? in un altro thread ogni secondo andr? ad aggiornare il tempo di riproduzione:

  seekBar.Value = Player.Position.TotalSeconds

  Dim ts As TimeSpan

  ts = New TimeSpan(0, 0, Player.Position.TotalSeconds)

  TempoTextBox.Text = "Player: " & ts.ToString

Mettiamo assieme il tutto ed ecco il codice finale:

Imports System.IO
Imports System.Windows.Threading
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Class MainWindow
 Dim CartellaDefault = My.Application.Info.DirectoryPath
 Private isDragging = False
 Private Timer As DispatcherTimer
 Public Structure WaveHeader
 Public Chunk As Char()
 Public ChunkSize As Int32
 Public Format As Char()
 Public SubChunk1 As Char()
 Public SubChunk1Size As Int32
 Public AudioFormat As Int16
 Public Channels As Int16
 Public SampleRate As Int32
 Public ByteRate As Int32
 Public BlockAlign As Int16
 Public BitsPerSample As Int16
 Public SubChunk2 As Char()
 Public SubChunk2Size As Int32
 End Structure
 Private Sub ApriFileAudioButton_Click(sender As Object, e As RoutedEventArgs) Handles ApriFileAudioButton.Click
 Dim dlg As New Microsoft.Win32.OpenFileDialog()
 dlg.Filter = "Audio Files|*.wav"
 dlg.ShowDialog()
 If dlg.FileName = "" Then Exit Sub
 FileAudioTextBox.Text = dlg.FileName
 Dim fh As WaveHeader
 Dim stream As New FileStream(FileAudioTextBox.Text, FileMode.Open)
 Dim br As New BinaryReader(stream)
 fh.Chunk = br.ReadChars(4)
 fh.ChunkSize = br.ReadInt32
 fh.Format = br.ReadChars(4)
 fh.SubChunk1 = br.ReadChars(4)
 fh.SubChunk1Size = br.ReadInt32
 fh.AudioFormat = br.ReadInt16
 fh.Channels = br.ReadInt16
 fh.SampleRate = br.ReadInt32
 fh.ByteRate = br.ReadInt32
 fh.BlockAlign = br.ReadInt16
 fh.BitsPerSample = br.ReadInt16
 For i = 1 To fh.SubChunk1Size - 16
 br.ReadByte()
 Next
 fh.SubChunk2 = br.ReadChars(4)
 fh.SubChunk2Size = br.ReadInt32
 br.Close()
 stream.Close()
 Dim t As String = ""
 t &= "Chunk  " & fh.Chunk & Environment.NewLine
 t &= "Chunksize  " & fh.ChunkSize & Environment.NewLine
 t &= "Format  " & fh.Format & Environment.NewLine
 t &= "subChunk1  " & fh.SubChunk1 & Environment.NewLine
 t &= "subchunk1size  " & fh.SubChunk1Size & Environment.NewLine
 t &= "PCM  " & fh.AudioFormat & Environment.NewLine
 t &= "Channels  " & fh.Channels & Environment.NewLine
 t &= "Samplerate  " & fh.SampleRate & Environment.NewLine
 t &= "ByteRate  " & fh.ByteRate & Environment.NewLine
 t &= "Block Align  " & fh.BlockAlign & Environment.NewLine
 t &= "Bits/Sample  " & fh.BitsPerSample & Environment.NewLine
 t &= "subChunk2  " & fh.SubChunk2 & Environment.NewLine
 t &= "subChunk2size  " & fh.SubChunk2Size & Environment.NewLine
 InformazioniTextBox.Text = t
 Player.Source = New Uri(FileAudioTextBox.Text)
 PlayButton.IsEnabled = True
 DurataBranoLabel.Text = CalcolaDurataBrano(FileAudioTextBox.Text)
 Dim DimensioneFile = CalcolaDimensioneFile(FileAudioTextBox.Text)
 DimensioneFileLabel.Text = DimensioneFile & " (" & CalcolaMega(DimensioneFile) & " MB)"
 End Sub
 Private Function CalcolaDurataBrano(ddd As String)
 CopiaFileProvvisorio(ddd)
 Dim ritorna As String
 Dim durata As Double
 durata = MCI.GetSoundLength(CartellaDefault & "\provvisorio.wav") ' The return value will be in milliseconds.
 Dim ts1 As TimeSpan
 ts1 = New TimeSpan(0, 0, durata / 1000)
 Dim ts As String
 ts = ToglieOra(ts1)
 ritorna = ts.ToString
 CancellaFileProvvisorio(ddd)
 Return ritorna
 End Function
 Private Function ToglieOra(ts1 As TimeSpan) As String
 Dim ritorna = Right(ts1.ToString, 5)
 Return ritorna
 End Function
 Private Sub CopiaFileProvvisorio(ddd As String)
 File.Copy(ddd, CartellaDefault & "\provvisorio.wav", True)
 End Sub
 Private Sub CancellaFileProvvisorio(ddd As String)
 File.Delete(CartellaDefault & "\provvisorio.wav")
 End Sub
 Private Sub FileAudioTextBox_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles FileAudioTextBox.TextChanged
 If FileAudioTextBox.Text.Length > 0 Then
 Player.Stop()
 IsPlaying(False)
 PlayButton.IsEnabled = True
 End If
 End Sub
 Private Sub IsPlaying(bValue As Boolean)
 StopButton.IsEnabled = bValue
 BackButton.IsEnabled = bValue
 ForwardButton.IsEnabled = bValue
 PlayButton.IsEnabled = bValue
 seekBar.IsEnabled = bValue
 End Sub
 Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
 If Not isDragging Then
 seekBar.Value = Player.Position.TotalSeconds
 Dim ts As TimeSpan
 ts = New TimeSpan(0, 0, Player.Position.TotalSeconds)
 TempoTextBox.Text = "Player: " & ts.ToString
 End If
 End Sub
 Private Sub seekBar_DragCompleted(sender As System.Object, e As System.Windows.Controls.Primitives.DragCompletedEventArgs)
 isDragging = False
 Player.Position = TimeSpan.FromSeconds(seekBar.Value)
 End Sub
 Private Sub seekBar_DragStarted(sender As System.Object, e As System.Windows.Controls.Primitives.DragStartedEventArgs)
 isDragging = True
 End Sub
 Private Sub Player_MediaOpened(sender As Object, e As System.Windows.RoutedEventArgs) Handles Player.MediaOpened
 If (Player.NaturalDuration.HasTimeSpan) Then
 Dim ts As TimeSpan = Player.NaturalDuration.TimeSpan
 seekBar.Maximum = ts.TotalSeconds
 seekBar.SmallChange = 1
 seekBar.LargeChange = Math.Min(10, ts.Seconds / 10)
 End If
 Timer.Start()
 End Sub
 Private Function CalcolaDimensioneFile(ddd As String) As String
 Dim uu As String
 If ddd = "" Then uu = 0 : GoTo yte
 Dim infoReader As System.IO.FileInfo
 infoReader = My.Computer.FileSystem.GetFileInfo(ddd)
 uu = infoReader.Length.ToString
yte:
 Return uu
 End Function
 Private Function CalcolaMega(DimensioneFile As Double) As String
 Dim ritorna As String
 ritorna = (DimensioneFile / 1048576 / 8).ToString
 ritorna = Mid(ritorna, 1, 4)
 Return ritorna
 End Function
 Private Sub PlayButton_Click(sender As Object, e As RoutedEventArgs) Handles PlayButton.Click
 IsPlaying(True)
 If (PlayButton.Content.ToString() = "Play") Then
 Player.Play()
 PlayButton.Content = "Pause"
 Else
 Player.Pause()
 PlayButton.Content = "Play"
 End If
 End Sub
 Private Sub StopButton_Click(sender As Object, e As RoutedEventArgs) Handles StopButton.Click
 Player.Stop()
 PlayButton.Content = "Play"
 IsPlaying(False)
 PlayButton.IsEnabled = True
 End Sub
 Private Sub BackButton_Click(sender As Object, e As RoutedEventArgs) Handles BackButton.Click
 Player.Position = Player.Position - TimeSpan.FromSeconds(10)
 End Sub
 Private Sub ForwardButton_Click(sender As Object, e As RoutedEventArgs) Handles ForwardButton.Click
 Player.Position = Player.Position + TimeSpan.FromSeconds(10)
 End Sub
 Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
 IsPlaying(False)
 Timer = New DispatcherTimer()
 Timer.Interval = TimeSpan.FromMilliseconds(200)
 AddHandler Timer.Tick, AddressOf Timer_Tick
 End Sub
End Class

Eseguiamo l'applicazione, apriamo un file e verranno visualizzate le informazioni:

Rilassiamoci ora giocando con il player ed ascoltando i nostri brani preferiti.

Conclusioni

Abbiamo visto in questo articolo come costruire un player musicale e come ottenere le informazioni da un file Wave.