vbAccelerator - Contents of code file: WaveStreamReader.vb

This file is part of the download WaveStream VB, which is described in the article WaveStreamReader and WaveStreamWriter.

Imports System.IO
Imports System.Runtime.InteropServices

Public Class WaveStreamReader
    Inherits Stream


    Private waveFile As String
    Private hMmio As IntPtr = IntPtr.Zero
    Private disposed As Boolean = False
    Private format As WinMMInterop.WAVEFORMATEX
    Private dataOffset As Integer = 0
    Private audioLength As Integer = 0

    Public Overloads Sub Dispose()        
        DisposeResources(True)
        GC.SuppressFinalize(Me)
    End Sub

    ''' <summary>
    ''' Default constructor
    ''' </summary>
    Public Sub New()
        MyBase.New()
    End Sub

    ''' <summary>
    ''' Constructor for a particular wave file.
    ''' </summary>
    ''' <param name="file">File name of the wave file to read</param>
    Public Sub New(ByVal file As String)
        Me.New()
        Filename = file
    End Sub

    ''' <summary>
    ''' Destructor: ensures that the wave file handle is closed.
    ''' </summary>
    Protected Overrides Sub Finalize()
        DisposeResources(False)
    End Sub


    ''' <summary>
    ''' Gets the Multi-media IO handle to the wave file.
    ''' </summary>
    Protected Overridable ReadOnly Property Handle() As IntPtr
        Get
            Return hMmio
        End Get
    End Property

    ''' <summary>
    ''' Clears up resources associated with this class.
    ''' </summary>
    ''' <param name="disposing"><code>true</code> if disposing from the
     <c>Dispose</c>
    ''' method, otherwise <c>false</c>.</param>
    Protected Overridable Sub DisposeResources(ByVal disposing As Boolean)
        If Not (disposed) Then
            If (disposing) Then
                '// nothing to do
            End If
            CloseWaveFile()
            disposed = True
        End If
    End Sub

    ''' <summary>
    ''' Gets/sets the wave file name.
    ''' </summary>
    Public Property Filename() As String
        Get
            Return waveFile
        End Get
        Set(ByVal Value As String)
            If Not (hMmio.Equals(IntPtr.Zero)) Then
                CloseWaveFile()
            End If
            waveFile = Value
            OpenWaveFile()
        End Set
    End Property

    ''' <summary>
    ''' Gets the number of audio channels in the file.
    ''' </summary>
    Public ReadOnly Property Channels() As Int16
        Get
            Return format.nChannels
        End Get
    End Property

    ''' <summary>
    ''' Gets the sample frequency of the file.
    ''' </summary>
    Public ReadOnly Property SamplingFrequency() As Integer
        Get
            Return format.nSamplesPerSec
        End Get
    End Property

    ''' <summary>
    ''' Gets the number of bits per sample in the wave file.
    ''' </summary>
    Public ReadOnly Property BitsPerSample() As Int16
        Get
            Return format.wBitsPerSample
        End Get
    End Property


    ''' <summary>
    ''' Gets whether the stream can be read or not (true whenever a wave file
    ''' is open).
    ''' </summary>
    Public Overrides ReadOnly Property CanRead() As Boolean
        Get
            Return Not (hMmio.Equals(IntPtr.Zero))
        End Get
    End Property

    ''' <summary>
    ''' Gets whether the stream is seekable or not (true whenever a wave file
    ''' is open)
    ''' </summary>
    Public Overrides ReadOnly Property CanSeek() As Boolean
        Get
            Return Not (hMmio.Equals(IntPtr.Zero))
        End Get
    End Property

    ''' <summary>
    ''' Returns false; this is a read-only stream
    ''' </summary>
    Public Overrides ReadOnly Property CanWrite() As Boolean
        Get
            Return False
        End Get
    End Property

    ''' <summary>
    ''' Throws an exception; this is a read-only stream
    ''' </summary>
    ''' <exception cref="InvalidOperationException">Thrown exception</exception>
    Public Overrides Sub Flush()
        Throw New InvalidOperationException( _
            "This class can only read files.  Use the WaveStreamWriter class to
             write files.")
    End Sub

    ''' <summary>
    ''' Gets the length of this wave file, in bytes.
    ''' </summary>
    Public Overrides ReadOnly Property Length() As Long
        Get
            Return audioLength
        End Get
    End Property

    ''' <summary>
    ''' Throws an exception; this is a read-only stream
    ''' </summary>
    ''' <exception cref="InvalidOperationException">Thrown exception</exception>
    Public Overrides Sub SetLength(ByVal length As Long)
        Throw New InvalidOperationException( _
         "This class can only read files.  Use the WaveStreamWriter class to
          write files.")
    End Sub

    ''' <summary>
    ''' Gets/sets the position within the wave file.
    ''' </summary>
    Public Overrides Property Position() As Long
        Get
            Return 0
        End Get
        Set(ByVal Value As Long)
            Seek(Value, SeekOrigin.Begin)
        End Set
    End Property

    ''' <summary>
    ''' Reads <c>count</c> bytes into the buffer.
    ''' </summary>
    ''' <param name="buffer">Buffer to read into</param>
    ''' <param name="count">Number of bytes to read</param>
    ''' <returns>Number of bytes read.</returns>
    Public Overridable Overloads Function Read(ByVal buffer As Byte(), ByVal
     count As Integer) As Integer
        Return Read(buffer, 0, count)
    End Function

    ''' <summary>
    ''' Reads <c>count</c> bytes into the buffer.
    ''' </summary>
    ''' <param name="buffer">Buffer to read into</param>
    ''' <param name="count">Number of bytes to read</param>
    ''' <param name="offset">Offset from the current file position to start
     reading from</param>
    ''' <returns>Number of bytes read.</returns>
    Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal
     offset As Integer, ByVal count As Integer) As Integer

        If (hMmio.Equals(IntPtr.Zero)) Then
            Throw New InvalidOperationException("No wave data is open")
        End If

        If (offset <> 0) Then
            Seek(offset, SeekOrigin.Current)
        End If

        Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned)
        Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject()

        Dim dataRemaining As Integer = (dataOffset + audioLength - _
            WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR))
        Dim amtRead As Integer = 0
        If (count < dataRemaining) Then
            amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, count)
        ElseIf (dataRemaining > 0) Then
            amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining)
        End If

        If (handle.IsAllocated) Then
            handle.Free()
        End If
        Return amtRead
    End Function

    ''' <summary>
    ''' Reads <c>count</c> shorts into the buffer.
    ''' </summary>
    ''' <param name="buffer">Buffer to read into</param>
    ''' <param name="count">Number of shorts to read</param>
    ''' <returns>Number of bytes read.</returns>
    Public Overridable Function Read16bit(ByVal buffer As Short(), ByVal count
     As Integer) As Integer
        Return Read16bit(buffer, 0, count)
    End Function

    ''' <summary>
    ''' Reads <c>count</c> shorts into the buffer.
    ''' </summary>
    ''' <param name="buffer">Buffer to read into</param>
    ''' <param name="count">Number of shorts to read</param>
    ''' <param name="offset">Offset in shorts (2 bytes) from the current file
     position to start 
    ''' reading from</param>
    ''' <returns>Number of bytes read.</returns>
    Public Overridable Function Read16bit(ByVal buffer As Short(), ByVal offset
     As Integer, ByVal count As Integer) As Integer
        If (hMmio.Equals(IntPtr.Zero)) Then
            Throw New InvalidOperationException("No wave data is open")
        End If

        If (offset <> 0) Then
            Seek((offset / 2), SeekOrigin.Current)
        End If

        Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned)
        Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject()

        Dim dataRemaining As Integer = (dataOffset + audioLength - _
          WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)) / 2
        Dim read = 0
        If (count < dataRemaining) Then
            read = WinMMInterop.mmioRead(hMmio, ptrBuffer, count * 2)
        ElseIf (dataRemaining > 0) Then
            read = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining * 2)
        End If

        If (handle.IsAllocated) Then
            handle.Free()
        End If
        Return read / 2
    End Function

    ''' <summary>
    ''' Throws an exception; this is a read-only stream
    ''' </summary>
    ''' <exception cref="InvalidOperationException">Thrown exception</exception>
    Public Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer,
     ByVal count As Integer)
        Throw New InvalidOperationException( _
         "This class can only read files.  Use the WaveStreamWriter class to
          write wave files.")
    End Sub

    ''' <summary>
    ''' Seeks to the specified position in the stream, in bytes
    ''' </summary>
    ''' <param name="position">Position to seek to</param>
    ''' <param name="origin">Specifies the starting postion of the seek</param>
    Public Overrides Function Seek(ByVal position As Long, ByVal origin As
     SeekOrigin) As Long
        If (hMmio.Equals(IntPtr.Zero)) Then
            Throw New InvalidOperationException("No wave data is open")
        End If

        Dim offset As Integer = position
        Dim mmOrigin As Integer = WinMMInterop.SEEK_CUR
        If (origin = SeekOrigin.Begin) Then
            mmOrigin = WinMMInterop.SEEK_SET
        ElseIf (origin = SeekOrigin.End) Then
            mmOrigin = WinMMInterop.SEEK_END
        End If
        Dim result As Integer = WinMMInterop.mmioSeek(hMmio, offset, mmOrigin)
        If (result = -1) Then
            Throw New WaveStreamException( _
             String.Format("Failed to seek to position {0} in file", position))
        End If
        Return result
    End Function

    Private Sub OpenWaveFile()
        CloseWaveFile()

        If Not (File.Exists(waveFile)) Then
            Throw New FileNotFoundException( _
                String.Format("The file {0} does not exist", waveFile))
        End If

        hMmio = WinMMInterop.mmioOpen(waveFile, IntPtr.Zero,
         WinMMInterop.MMIO_READ)
        If (hMmio.Equals(IntPtr.Zero)) Then
            Throw New IOException( _
             String.Format("Could not open file {0}", waveFile))
        End If

        GetWaveData()

    End Sub

    Private Sub GetWaveData()
        Dim result As Integer = 0
        Dim mmckInfoParent As WinMMInterop.MMCKINFO = New
         WinMMInterop.MMCKINFO()

        '// Descend into the wave header:
        mmckInfoParent.fccType = WinMMInterop.mmioStringToFOURCC("WAVE", 0)
        result = WinMMInterop.mmioDescendParent(hMmio, mmckInfoParent, 0, _
            WinMMInterop.MMIO_FINDRIFF)
        If (result <> WinMMInterop.MMSYSERR_NOERROR) Then
            CloseWaveFile()
            Throw New WaveStreamException( _
             String.Format("The file {0} is not a wave file", waveFile))
        End If

        '// Descend into the fmt chunk:
        Dim mmckSubChunkIn As WinMMInterop.MMCKINFO = New
         WinMMInterop.MMCKINFO()
        mmckSubChunkIn.ckid = WinMMInterop.mmioStringToFOURCC("fmt", 0)
        result = WinMMInterop.mmioDescend(hMmio, mmckSubChunkIn,
         mmckInfoParent, _
    WinMMInterop.MMIO_FINDCHUNK)
        If (result <> WinMMInterop.MMSYSERR_NOERROR) Then
            CloseWaveFile()
            Throw New WaveStreamException( _
             String.Format("Unable to locate the format chunk in file {0}",
              waveFile))
        End If

        format = New WinMMInterop.WAVEFORMATEX()
        result = WinMMInterop.mmioReadWaveFormat(hMmio, format,
         mmckSubChunkIn.ckSize)
        If (result = -1) Then
            CloseWaveFile()
            Throw New WaveStreamException( _
                String.Format("Unable to read the wave format from file {0}",
                 waveFile))
        End If

        '// Find the data subchunk
        result = WinMMInterop.mmioAscend(hMmio, mmckSubChunkIn, 0)

        mmckSubChunkIn.ckid = WinMMInterop.mmioStringToFOURCC("data", 0)
        result = WinMMInterop.mmioDescend(hMmio, mmckSubChunkIn,
         mmckInfoParent, _
          WinMMInterop.MMIO_FINDCHUNK)
        If (result <> WinMMInterop.MMSYSERR_NOERROR) Then
            CloseWaveFile()
            Throw New WaveStreamException( _
             String.Format("Unable to locate the data chunk in file {0}",
              waveFile))
        End If

        dataOffset = WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)
        audioLength = mmckSubChunkIn.ckSize
    End Sub

    Private Sub CloseWaveFile()
        If Not (hMmio.Equals(IntPtr.Zero)) Then
            WinMMInterop.mmioClose(hMmio, 0)
            hMmio = IntPtr.Zero
            audioLength = 0
        End If
    End Sub


End Class