Click here to Skip to main content
15,890,438 members
Articles / Multimedia / DirectX

Play and Visualize WAV Files using Managed Direct Sound with VB.NET

Rate me:
Please Sign up or sign in to vote.
4.78/5 (32 votes)
21 Dec 2007CPOL11 min read 404.3K   87   149
“Circular Buffers” is an application developed in VB.NET (VS 2003).

Introduction

As a VB.NET developer, I must admit that it is difficult getting decent code for DirectSound on the internet. Most of the examples are either in cryptic C/C++ or in C# (a close relation to VB.NET). The benefit of the latter is that it uses the familiar .NET Framework.

This tutorial takes you through the process of creating a simple utility application which will display and play a WAV file or portions of it as selected from a simple user interface.

For the non-initiated in the work of managed direct sound, I will take you through a brief introduction of DirectSound and jump into the topic of circular buffers in direct sound. For the already initiated, I will delve into the WAV file structure culminating with a class to parse a WAV file. To get the application working, the use of system timers will follow. Last but not least is putting all the code together.

An assumption made is that you are already familiar with DirectX and you have installed the DirectX SDK on your development platform.

About the Application

Image 1

“Circular Buffers” is an application developed in VB.NET (VS 2003). It demonstrates the following concepts:

  1. Playing a WAV file using a DirectSound static buffer
  2. Reading a WAV file and parsing it in preparation of playing
  3. Playing a WAV file stream using a DirectSound circular buffer
  4. Using the system timer to have precise control on timed events
  5. Visualizing WAV file data as a graph of sound values and Graphic double buffering
  6. Playing a WAV file data array using a DirectSound circular buffer
  7. Selecting portions of a WAV file and playing it using a static buffer
  8. Simple mixing of two sounds

Getting Started with DirectSound

DirectSound is part of the DirectX components and specifically handles playback of sound, including mono, stereo, 3-D sound and multi-channel sound. To begin, load the DirectSound reference into your VB.NET project as shown.

Image 2

By adding a reference to DirectSound, you expose four basic objects required in this project to play and manipulate sounds:

ObjectPurpose
Microsoft.DirectX.DirectSound.Device This is the main audio device object required to use DirectSound.
Microsoft.DirectX.DirectSound.WaveFormat Holds the required header properties of a WAV. For custom sounds, you must set all the parameters.
Microsoft.DirectX.DirectSound.SecondaryBuffer

 

This is the buffer to which we write our sound data before the primary hardware mixes and plays the sound. You can have as many secondary buffers as RAM can allow but only 1 primary buffer which is found in the hardware.
Microsoft.DirectX.DirectSound.BufferDescription Defines the capabilities of the audio device given the WAV format. 3-D sounds, volume control, frequency, panning can be set.

Minimum Required Code to Use DirectSound

VB.NET
'Play a sound wave using a default static buffer
 Private Sub cmdDefault_Click(……) Handles cmdDefault.Click
   Try
      SoundDevice = New Microsoft.DirectX.DirectSound.Device
   SoundDevice.SetCooperativeLevel(Me.Handle_
   , Microsoft.DirectX.DirectSound.CooperativeLevel.Normal)
    SbufferOriginal = New _ Microsoft.DirectX.DirectSound._
    SecondaryBuffer(SoundFile, SoundDevice)
     SbufferOriginal.Play(0,_
        Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
   Catch ex As Exception
   End Try
 End Sub

The code shown above is the minimum code required to play a sound from a WAV file. The steps required are:

  1. Create a new sound device and assign a valid window handle as one parameter and set the cooperative level.
    • Priority: When the application has focus, only its sound will be audible
    • Normal: Restricts all sound output to 8-bit
    • WritePrimary: Allows the application to write to the primary buffer
  2. Create a secondary sound buffer and assign a valid filename/file stream and audio device as the input.
    • The sound file can only be a valid WAV file.
  3. Call the play method of the secondary buffer

Playing a WAV File using a DirectSound Static Buffer

A static buffer is by the name static. The content does not change in time and is loaded once. For continuous sound play, the secondary buffer play method uses the looping option. The steps outlined above use a static buffer. The contents of the WAV file are loaded into the secondary buffer and played.

A static buffer is best used when you have small WAV files whose size will not consume much resource. The procedure cmdDefault_Click in the above code creates and plays a static buffer.

Reading a WAV File and Parsing it in Preparation of Playing

The WAV file format is a subset of Microsoft's RIFF specification for the storage of multimedia files. A RIFF file starts out with a file header followed by a sequence of data chunks. A WAVE file is often just a RIFF file with a single "WAVE" chunk which consists of two sub-chunks -- a "fmt" chunk specifying the data format and a "data" chunk containing the actual sample data.

The figure below depicts the WAV file structure. The class CWAVReader parses the WAV file structure extracting the header details (WAV format) and actual sound data.

Image 3

The code below depicts the constructor with a series of methods parsing the WAV file structure.

VB.NET
‘Constructor to open stream
   Sub New(ByVal SoundFilePathName As String)
       mWAVFileName = SoundFilePathName
       mOpen = OpenWAVStream(mWAVFileName)
       '******************* MAIN WORK HERE ******************
       'Parse the WAV file and read the
       If mOpen Then
              'Read the Header Data in THIS ORDER
              'Each Read results in the File Pointer Moving
              mChunkID = ReadChunkID(mWAVStream)
              mChunkSize = ReadChunkSize(mWAVStream)
              mFormatID = ReadFormatID(mWAVStream)
              mSubChunkID = ReadSubChunkID(mWAVStream)
              mSubChunkSize = ReadSubChunkSize(mWAVStream)
              mAudioFormat = ReadAudioFormat(mWAVStream)
              mNumChannels = ReadNumChannels(mWAVStream)
              mSampleRate = ReadSampleRate(mWAVStream)
              mByteRate = ReadByteRate(mWAVStream)
              mBlockAlign = ReadBlockAlign(mWAVStream)
              mBitsPerSample = ReadBitsPerSample(mWAVStream)
              mSubChunkIDTwo = ReadSubChunkIDTwo(mWAVStream)
              mSubChunkSizeTwo = ReadSubChunkSizeTwo(mWAVStream)
              mWaveSoundData = ReadWAVSampleData(mWAVStream)
              mWAVStream.Close()
       End If
   End Sub

NOTE: THE ORDER MUST BE MAINTAINED! THE CODE USES A BINARY STREAM READ WHICH ADVANCES THE FILE POINTER.

Parsing the binary stream is not difficult. This involves reading a number of bytes as required using the binary reader ReadBytes method.

The code below reads the chunk ID from a WAV file. Notice that the chunk ID is in BIG-ENDIAN.

Computer architectures differ in terms of byte ordering. In some, data is stored left to right, which is referred to as big-endian. In others data is stored from right to left, which is referred to as little-endian. A notable computer architecture that uses big-endian byte ordering is Sun's Sparc. Intel architecture uses little-endian byte ordering, as does the Compaq Alpha processor.

VB.NET
'Read the ChunkID and return a string
Private Function ReadChunkID(….) As String
   Dim DataBuffer() As Byte
   Dim DataEncoder As System.Text.ASCIIEncoding
   Dim TempString As Char()
      DataEncoder = New System.Text.ASCIIEncoding
   DataBuffer = WAVIOstreamReader.ReadBytes(4)
   'Ensure we have data to spit out
   If DataBuffer.Length <> 0 Then
      TempString = DataEncoder.GetChars(DataBuffer, 0, 4)
      Return TempString(0) & TempString(1) & TempString(2) & TempString(3)
   Else
      Return ""
   End If
End Function

Since we are reading the data and converting the same into text based on the relative location (the array is read from location 0 to location 3), this is a big-endian value.

Small-endian values require a much more complicated function. The binary stream is read but the values are reversed and padded to ensure correct alignment, then converted to either text or value. The code below is one such function which takes up a byte array and reverses it to return the small-endian value.

VB.NET
'Get the small endian value
  Private Function GetLittleEndianStringValue(..) As String
    Dim ValueString As String = "&h"
    If DataBuffer.Length <> 0 Then
       'In little endian, we reverse the array data
       and pad the same where the length is 1
       If Hex(DataBuffer(3)).Length = 1 Then
            ValueString &= "0" & Hex(DataBuffer(3))
       Else
            ValueString &= Hex(DataBuffer(3))
       End If
       If Hex(DataBuffer(2)).Length = 1 Then
            ValueString &= "0" & Hex(DataBuffer(2))
       Else
            ValueString &= Hex(DataBuffer(2))
       End If
       If Hex(DataBuffer(1)).Length = 1 Then
            ValueString &= "0" & Hex(DataBuffer(1))
       Else
            ValueString &= Hex(DataBuffer(1))
       End If
       If Hex(DataBuffer(0)).Length = 1 Then
            ValueString &= "0" & Hex(DataBuffer(0))
       Else
            ValueString &= Hex(DataBuffer(0))
       End If
    Else
       ValueString = "0"
    End If
    GetLittleEndianStringValue = ValueString
  End Function

After reading the WAV’s properties, the final function is to read the entire sound data. The sound is read as a series of int16 data blocks. The memory stream has a method named ReadInt16, which is called repeatedly. The code below reads the actual sound values and converts the data from unsigned data to signed int16.

VB.NET
'returns the wave data as a byte array
   Public Function GetSoundDataValue() As Int16()
      Dim DataCount As Integer
      Dim tempStream As IO.BinaryReader
      tempStream = New IO.BinaryReader(New IO.MemoryStream(mWaveSoundData))
      tempStream.BaseStream.Position = 0
      'Create a data array to hold the data read from the stream
      'Read chunks of int16 from the stream (already aligned!)
      Dim tempData(CInt(tempStream.BaseStream.Length / 2)) As Int16
      While DataCount <= tempData.Length - 2
        tempData(DataCount) = tempStream.ReadInt16()
        DataCount += 1
      End While
      tempStream.Close()
      tempStream = Nothing
      Return tempData
   End Function

Using the System Timer to have Precise Control on Timed Events

The system.timers.timer works in much the same way as does the Windows Forms timer, but does not require the Windows message pump. Other than that, the primary difference between server timers and Windows Forms timers is that the event handlers for server timers execute on thread pool threads. This makes it possible to maintain a responsive user interface even if the event handler takes a long time to execute. Another critical difference in this case of audio programming is higher precision and thread safety.

The class timer’s constructor takes in a time interval and a function to call after the elapse of the interval. The system.timers.timer object is set to autoreset and thus continuously calls the function after the interval. The use of delegates (pointer to a function/sub) is used. Thus the timer object gets the timer interval and a delegate (pointer) of type System.Timers.ElapsedEventHandler to call. This class is used to monitor the sound buffer and ‘top-up’ data to play and also to paint the progress of the play bar while playing music.

Playing a WAV File Stream using a DirectSound Circular Buffer

According to Wikipedia, “A circular buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. This structure lends itself easily to buffering data streams.” Pictorially, a circular buffer is as shown in the figure below:

Image 4

The write pointer identifies a location from which we can write sound data. The play pointer identifies the location where the sound buffer will play data from. The red boxes identify a location where data can be written to.

In the first scenario, the write pointer is positioned at a location point larger than the play pointer. As the play pointer advances, the write pointer needs to advance with latency not large enough to get a distortion. In the second scenario, the write pointer has wrapped around and is now at a location smaller than the play pointer.

When the write pointer gets to location 7, it has to warp around. The following code enables wrapping around of the write pointer and returns the amount of data already played which is the location to which new data has to be written to.

VB.NET
'get the played data size
Function GetPlayedSize() As Integer
    Dim Pos As Integer
    Pos = SbufferOriginal.PlayPosition
    If Pos < NextWritePos Then
       Return Pos + (SbufferOriginal.Caps.BufferBytes - NextWritePos)
    Else
       Return Pos - NextWritePos
    End If
End Function

The PlayPosition is a property of the secondary sound buffer and returns the position of the play pointer. NextWritePos is an internal pointer used to identify the location of where to write data to.

As the play pointer advances, we must continuously add data to the circular buffer. In this case, we shall use a memory stream from which to read data from and write to the circular stream. As mentioned before, a circular buffer is very useful if there is a large WAV file to be read and you intend to play the data in small chunks as opposed to reading the entire data to memory. There are two ways of ‘filling’ up the circular stream: use of notifications or use of polling technique. I have implemented the latter. A system timer is used to continuously ‘fill’ the circular stream with data.

At intervals of 75 milliseconds, the timer object calls the function PlayEventHandler. This function calls other functions that determine the amount of data to write and thereafter writes this data from the stream into the secondary sound buffer.

VB.NET
'Update the Circular buffer based on either a stream of data array
Sub PlayerEventHandler(…)
   'Stop if we have read all data
   If PlayerPosition >= MYwave.SubChunkSizeTwo Then
     StopPlay()
   End If
   'If an array, use the dataArray to top up new data to the circular buffer
   If IsArray = False Then
     'Get the amount of data to write and thereafter write the data
     WriteData(GetPlayedSize())
   Else
     WriteDataArray(GetPlayedSize())
   End If
End Sub

The function GetPlayedSize uses the concept of circular buffers to return the amount of data to safely write on the secondary sound buffer. The WriteData function thereafter writes the data to the secondary buffer. The code below demonstrates the functionality required to top up the secondary buffer (circular buffer).

VB.NET
'Write data to the circular buffer (secondary buffer that is)
Sub WriteData(ByVal DataSize As Integer)
   Dim Tocopy As Integer
   'make sure the data is less than the latency amount of 300ms
   Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))
   'Only write data if there is something to write!
   If Tocopy > 0 Then
      'Restore the buffer
      If SbufferOriginal.Status.BufferLost Then
      SbufferOriginal.Restore()
      End If
      'Copy the data to the buffer
      'The DataMemStream is a binary stream object.
      'This can also be a large WAV file still on harddisk
      SbufferOriginal.Write(NextWritePos, DataMemStream, Tocopy, _
            Microsoft.DirectX.DirectSound.LockFlag.None)
      'As data is read form the DataMemStream, the position
      '(internal of the structure) advances
      'by the size of Tcopy

      'Advance the total cumulative bytes read
      PlayerPosition += Tocopy
      'advance the NextWrite pointer
      NextWritePos += Tocopy
      'If we are at the end, we wrap round
      If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
      NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
      End If
   End If
End Sub

The system.timers.timer object is also used to update the screen with the location of the player pointer with respect to the data being played and not the secondary buffer. The function MyPainPoint is called at the same time interval of 75 milliseconds. But I use a different timer to reduce the latency and sound distortion.

VB.NET
'The handler to the timer tick event :
'Paints the location of the player pointer on the sound graph as the data is played
Sub MyPainPoint(ByVal obj As Object, ByVal Args As System.Timers.ElapsedEventArgs)
   'Control to ensure we stop when the total cumulated bytes read is equal to
   'the total data size
   If PlayerPosition >= MYwave.SubChunkSizeTwo Then
     StopPlay()
     'Update the labels
     lblPos.Text = MYwave.SubChunkSizeTwo.ToString
     lbltime.Text = MYwave.PlayTimeSeconds().ToString
   End If
   'Draw a line red from top to bottom showing the current position based on play position
   'The PlayerPOsition is the absolute location of the current played data
   'This is taken as a ratio of the total data size and scaled to the width of the picture
   'control
   Dim XPos As Single = CSng((PlayerPosition / MYwave.SubChunkSizeTwo) * picWave.Width)
   Dim posgraphic As Graphics

   'Clone the original canvas (this has the original sound graph). We do not need to
   're-draw this large graph as it willtake much processing time!
   PlayPicture = CType(myPicture.Clone, Bitmap)
   posgraphic = Graphics.FromImage(PlayPicture)

   'Draw the pointer
   Dim Mypen As Pen = New Pen(Color.Red)
   posgraphic.DrawLine(Mypen, XPos, 0, XPos, picWave.Height)

   'Draw line to myPicture
   MyTime += TimerStep
   posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)

   'Update the status on the labels
   lblPos.Text = PlayerPosition.ToString
   lbltime.Text = (MyTime / 1000).ToString

   'force a redraw of the picture updated.
   Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top, picWave.Width, _
        picWave.Height))
      End Sub

The initial drawing of the sound data graph is stored as a bitmap. This bitmap is continuously cloned and a red line is drawn onto the cloned bitmap at different locations to give an effect of movement.

Visualizing WAV File Data as a Graph of Sound Values

The function DrawGraph takes in an array of int16 data and plots it out on the (vertical mid-point) picture control. Double buffering is used to speed up the drawing process. A bitmap is first created, thereafter the graphics are drawn. Once the entire line graph is drawn, the graphic is then drawn onto the bitmap. The bitmap is then transferred to the picture control. This process if faster than drawing directly onto the picture control.

VB.NET
'Draws the Sound data as a wave onto a canvas using double buffering to speed up work
Function DrawGraph(ByVal Data() As Int16) As Bitmap
   'Create the Canvas
   Dim myBitmap As System.Drawing.Bitmap

   'Create an array to hold the wav data which we can sort
   Dim tempData(Data.Length) As Integer

   'Copy the data to the temporary location  ... can be done better
   Data.CopyTo(tempData, 0)

   'Sort the array to get the maximum and minimum Y values
   Array.Sort(tempData)

   'generate the Canvas (drawing board in memory
   myBitmap = New Bitmap(picWave.Width, picWave.Height)

   'Create your paint brush, pens and drawing objects
   Dim myGraphic As System.Drawing.Graphics
   myGraphic = Graphics.FromImage(myBitmap)

   'draw the background with a custom color
   myGraphic.Clear(Color.FromArgb(181, 223, 225))

   'Get the parameters to draw the data and scale to fit the canvas but
   'draw from the middle
   Dim YMax As Integer = tempData(Data.Length - 1)
   Dim YMin As Integer = tempData(0)
   Dim XMax As Integer = picWave.Width
   Dim Xmin As Integer = 0

   'Create an array of points to draw a line
   Dim PicPoint(Data.Length - 1) As System.Drawing.PointF
   Dim Count As Integer
   Dim Step1 As Single          'Scale the data between Ymax and Ymin
   Dim Step2 As Single          'Scale the data further to fit between the canvas height
   Dim step3 As Single          'Draw the point form the middle of the canvas

   Dim Mypen As New Pen(Color.FromArgb(24, 101, 123))

   'Draw the lines from the series of points representing the sound wav
   For Count = 0 To Data.Length - 1
     Step1 = CSng(Data(Count) / (YMax - YMin))
     Step2 = CSng(Step1 * picWave.Height / 2)
     step3 = CSng(Step2 + (picWave.Height / 2))
     PicPoint(Count) = New System.Drawing.PointF(CSng(XMax * _
        (Count / Data.Length)), step3)
   Next

   'Draw the lines
   myGraphic.DrawLines(Mypen, PicPoint)

   'Draw graphics onto canvas
   myGraphic.DrawImage(myBitmap, picWave.Width, picWave.Height)

   'return the picture memory object
   Return (myBitmap)
End Function

Playing a WAV File Data Array using a Direct Sound Circular Buffer

Playing data from an array is not very different from playing data from a memory stream. The only difference here is that the secondary buffer method has an overload to read data from an array. As the programmer, you have to fetch data from the source (a memory stream) and create the data array. As listed in the code, the memory stream is repositioned to the last location of a read (playerposition) and data is read from there to the length of safe data to write.

VB.NET
'Write data to a Data Array.... similar to the above. using memory a stream
Sub WriteDataArray(ByVal DataSize As Integer)
   Dim Tocopy As Integer
   'ensure we do not have a big latency . the maximum is 300 ms
   Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))

   'is we have data, then write to the array and play
   If Tocopy > 0 Then
     'Restore the buffer
     If SbufferOriginal.Status.BufferLost Then
       SbufferOriginal.Restore()
     End If

     'Copy the data to the Array
     're-create the data array (this is very slow!!)
     ReDim DataArray(Tocopy - 1)

     'Position the memory stream to the last location we read from.
     DataMemStream.Position = PlayerPosition

     'Copy the data from the stream to the array
     DataMemStream.Read(DataArray, 0, Tocopy - 1)

     'Write the data to the secondary buffer
     SbufferOriginal.Write(NextWritePos, DataArray, _
        Microsoft.DirectX.DirectSound.LockFlag.None)

     'Advance the pointers
     PlayerPosition += Tocopy
     NextWritePos += Tocopy
     If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
       NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
     End If
   End If
End Sub

Selecting Portions of a WAV File and Playing it using a Static Buffer

To play a selected portion of the sound file, highlight the portion and select capture 1 or capture 2 . If both buttons are selected on different portions, then two different sounds can be played simultaneously (mixing).

Image 5

To select a portion of the sound, toggle the left mouse button over the picture control and move the mouse to the right. Once you let go of the left mouse button, the portion to be played will be highlighted. Click on the capture 1 button. Repeat the same for another portion and click capture 2.

The selection is made possible by using the mousedown, mousemove and mouseup event of the picture control. The rectangle is made transparent by using alpha blending. The code listed below is the implementation:

VB.NET
'Draw the band over the selection
Sub DrawSelection()
    'Draw a rubber band
    Dim posgraphic As Graphics
    Dim RubberRect As Rectangle
    RubberRect = New Rectangle(StartPoint.X, 0, _
        EndPoint.X - StartPoint.X, picWave.Height - 3)

    'Clone the original canvas
    PlayPicture = CType(myPicture.Clone, Bitmap)
    posgraphic = Graphics.FromImage(PlayPicture)

    'Draw the pointer
    Dim Mypen As Pen = New Pen(Color.Green)

    'Create a transparent brush using alpha blending techniques
    Dim MyBrush As SolidBrush = New SolidBrush(Color.FromArgb(85, 204, 32, 92))

    'Draw the boarder
    posgraphic.DrawRectangle(Mypen, RubberRect)

    'Fill the color
    posgraphic.FillRectangle(MyBrush, RubberRect)
    'Draw the picture on the form with the updated section of music to clip
    posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)

    'redraw the portion only
    Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top, _
        picWave.Width, picWave.Height))
End Sub

To play the custom select sound, data is read to a data array. You must ensure that the data read is aligned based on the blockalign value. If not, noise results which is not very pleasant to hear!

VB.NET
'Plays a segment of data based on the rubber-band we have drawn prior to
'raising this event
Public Sub SetSegment(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles cmdSeg1.Click, cmdSeg2.Click
   'set local variables to use
   Dim tag As Int16
   Dim theButton As Button
   Dim DataStart As Integer
   Dim DataStop As Integer
   Dim BufferSize As Integer
   'initialize the direct sound objects
   Dim Format As Microsoft.DirectX.DirectSound.WaveFormat
   Dim Desc As Microsoft.DirectX.DirectSound.BufferDescription
   Dim MixBuffer As Microsoft.DirectX.DirectSound.SecondaryBuffer

   cmdBrowse.Enabled = False
   cmdDefault.Enabled = False
   cmdCustom.Enabled = False
   cmdCircular.Enabled = False
   cmdSeg1.Enabled = False
   cmdSeg2.Enabled = True
   cmdStop.Enabled = False

   theButton = CType(sender, Button)
   theButton.Enabled = False

   'get the locations from where to read data from
   DataStart = CInt(MYwave.SubChunkSizeTwo * (StartPoint.X / picWave.Width))
   DataStop = CInt(MYwave.SubChunkSizeTwo * (EndPoint.X / picWave.Width))

   StartPoint = Nothing
   EndPoint = Nothing

   'ensure that the data is aligned.. if not, noise results
   DataStart = DataStart - (DataStart Mod CInt(MYwave.BlockAlign))
   DataStop = DataStop - (DataStop Mod CInt(MYwave.BlockAlign))

   'Get the data into a Data array
   Dim DataSegment(DataStop - DataStart) As Byte

   'Read the data from the Stream
   DataMemStream.Position = DataStart

   'Read from the stream to the array buffer
   DataMemStream.Read(DataSegment, 0, DataStop - DataStart)

   'Now play the sound.
   'For custom sound, you must set the format
   Format = New Microsoft.DirectX.DirectSound.WaveFormat
   Format.AverageBytesPerSecond = CInt(MYwave.ByteRate)
   Format.BitsPerSample = CShort(MYwave.BitsPerSample)
   Format.BlockAlign = CShort(MYwave.BlockAlign)
   Format.Channels = CShort(MYwave.NumChannels)
   Format.FormatTag = Microsoft.DirectX.DirectSound.WaveFormatTag.Pcm
   Format.SamplesPerSecond = CInt(MYwave.SampleRate)

   Desc = New Microsoft.DirectX.DirectSound.BufferDescription(Format)
   BufferSize = DataStop - DataStart + 1

   'ensure the size is also aligned
   BufferSize = BufferSize + (BufferSize Mod CInt(MYwave.BlockAlign))

   Desc.BufferBytes = BufferSize
   Desc.ControlFrequency = True
   Desc.ControlPan = True
   Desc.ControlVolume = True
   Desc.GlobalFocus = True

   'Play the sound
   Try
     MixBuffer = New Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, SoundDevice)
     MixBuffer.Stop()
     MixBuffer.SetCurrentPosition(0)

     If MixBuffer.Status.BufferLost Then
      MixBuffer.Restore()
     End If

     MixBuffer.Write(0, DataSegment, Microsoft.DirectX.DirectSound.LockFlag.None)
     MixBuffer.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
   Catch ex As Exception
     MsgBox(ex.Message)
   End Try
End Sub

Points of Interest

Playing with DirectSound is fun, especially when you get some real sound after hours and days of struggling. It took me a couple of days to get the circular buffer working. The WAV parser was something that got me thinking especially the endian bit! I intend to build this further and incorporate FFT (fast fourier transform) for real music mixing!

So that is it! I hope this is helpful for you VB.NET direct sound enthusiasts who have not benefited from the C/C++, C# found on the internet. Much of the work done here was trial and error, again due to scanty material and books!

Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
ENO
Software Developer
Kenya Kenya
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSource Needed - Please Pin
bomedia7-Feb-09 13:33
bomedia7-Feb-09 13:33 
JokeRe: Source Needed - Please Pin
bomedia13-Feb-09 0:13
bomedia13-Feb-09 0:13 
GeneralRe: Source Needed - Please Pin
dangregory12-Mar-09 15:04
dangregory12-Mar-09 15:04 
GeneralGREAT ARTICLE BUT... Pin
Member 109444319-Dec-08 10:44
professionalMember 109444319-Dec-08 10:44 
GeneralSource code please Pin
Montxo18-Dec-08 11:13
Montxo18-Dec-08 11:13 
QuestionGreat code - but I got an error Pin
seanclancy21-Oct-08 6:52
seanclancy21-Oct-08 6:52 
QuestionNice but anybody has the source code yet ? Pin
plouvou17-Oct-08 9:32
plouvou17-Oct-08 9:32 
AnswerRe: Nice but anybody has the source code yet ? Pin
ENO21-Oct-08 4:22
ENO21-Oct-08 4:22 
'By Edgar Nyamweya Okioga
'.....@gamil.com
'on 24/11/2007
'For AfricaDotNet user Group (www.africadotnet.net)
'Demonstration of static and Circular buffers
'Demonstration of how to play sound portions
'Demonstration of the system.timer class
'Demonstration of double buffering and alpha blending


Public Class CircualrSounds
Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents lblPos As System.Windows.Forms.Label
Friend WithEvents lbltime As System.Windows.Forms.Label
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents cmdStop As System.Windows.Forms.Button
Friend WithEvents picWave As System.Windows.Forms.PictureBox
Friend WithEvents cmdDefault As System.Windows.Forms.Button
Friend WithEvents txtData As System.Windows.Forms.TextBox
Friend WithEvents cmdCustom As System.Windows.Forms.Button
Friend WithEvents cmdCircular As System.Windows.Forms.Button
Friend WithEvents cmdSeg1 As System.Windows.Forms.Button
Friend WithEvents cmdSeg2 As System.Windows.Forms.Button
Friend WithEvents cmdBrowse As System.Windows.Forms.Button
Friend WithEvents dlgWav As System.Windows.Forms.OpenFileDialog
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.Label1 = New System.Windows.Forms.Label
Me.lblPos = New System.Windows.Forms.Label
Me.lbltime = New System.Windows.Forms.Label
Me.Label3 = New System.Windows.Forms.Label
Me.cmdStop = New System.Windows.Forms.Button
Me.picWave = New System.Windows.Forms.PictureBox
Me.cmdDefault = New System.Windows.Forms.Button
Me.txtData = New System.Windows.Forms.TextBox
Me.cmdCustom = New System.Windows.Forms.Button
Me.cmdCircular = New System.Windows.Forms.Button
Me.cmdSeg1 = New System.Windows.Forms.Button
Me.cmdSeg2 = New System.Windows.Forms.Button
Me.cmdBrowse = New System.Windows.Forms.Button
Me.dlgWav = New System.Windows.Forms.OpenFileDialog
Me.SuspendLayout()
'
'Label1
'
Me.Label1.Location = New System.Drawing.Point(160, 12)
Me.Label1.Name = "Label1"
Me.Label1.Size = New System.Drawing.Size(48, 16)
Me.Label1.TabIndex = 3
Me.Label1.Text = "Position"
'
'lblPos
'
Me.lblPos.Location = New System.Drawing.Point(216, 8)
Me.lblPos.Name = "lblPos"
Me.lblPos.Size = New System.Drawing.Size(80, 24)
Me.lblPos.TabIndex = 4
'
'lbltime
'
Me.lbltime.Location = New System.Drawing.Point(336, 8)
Me.lbltime.Name = "lbltime"
Me.lbltime.Size = New System.Drawing.Size(72, 24)
Me.lbltime.TabIndex = 6
'
'Label3
'
Me.Label3.Location = New System.Drawing.Point(304, 12)
Me.Label3.Name = "Label3"
Me.Label3.Size = New System.Drawing.Size(32, 16)
Me.Label3.TabIndex = 5
Me.Label3.Text = "Time"
'
'cmdStop
'
Me.cmdStop.Enabled = False
Me.cmdStop.Location = New System.Drawing.Point(416, 8)
Me.cmdStop.Name = "cmdStop"
Me.cmdStop.Size = New System.Drawing.Size(56, 24)
Me.cmdStop.TabIndex = 16
Me.cmdStop.Text = "Stop"
'
'picWave
'
Me.picWave.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or
System.Windows.Forms.AnchorStyles.Left) _
Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
Me.picWave.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
Me.picWave.Enabled = False
Me.picWave.Location = New System.Drawing.Point(8, 40)
Me.picWave.Name = "picWave"
Me.picWave.Size = New System.Drawing.Size(814, 184)
Me.picWave.TabIndex = 17
Me.picWave.TabStop = False
'
'cmdDefault
'
Me.cmdDefault.Enabled = False
Me.cmdDefault.Location = New System.Drawing.Point(88, 8)
Me.cmdDefault.Name = "cmdDefault"
Me.cmdDefault.Size = New System.Drawing.Size(64, 24)
Me.cmdDefault.TabIndex = 18
Me.cmdDefault.Text = "Default"
'
'txtData
'
Me.txtData.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or
System.Windows.Forms.AnchorStyles.Left) _
Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
Me.txtData.Location = New System.Drawing.Point(8, 232)
Me.txtData.Multiline = True
Me.txtData.Name = "txtData"
Me.txtData.ReadOnly = True
Me.txtData.ScrollBars = System.Windows.Forms.ScrollBars.Vertical
Me.txtData.Size = New System.Drawing.Size(816, 120)
Me.txtData.TabIndex = 19
Me.txtData.Text = ""
'
'cmdCustom
'
Me.cmdCustom.Enabled = False
Me.cmdCustom.Location = New System.Drawing.Point(488, 8)
Me.cmdCustom.Name = "cmdCustom"
Me.cmdCustom.Size = New System.Drawing.Size(56, 24)
Me.cmdCustom.TabIndex = 20
Me.cmdCustom.Text = "Stream"
'
'cmdCircular
'
Me.cmdCircular.Enabled = False
Me.cmdCircular.Location = New System.Drawing.Point(560, 8)
Me.cmdCircular.Name = "cmdCircular"
Me.cmdCircular.Size = New System.Drawing.Size(48, 24)
Me.cmdCircular.TabIndex = 21
Me.cmdCircular.Text = "Array"
'
'cmdSeg1
'
Me.cmdSeg1.Enabled = False
Me.cmdSeg1.Location = New System.Drawing.Point(624, 8)
Me.cmdSeg1.Name = "cmdSeg1"
Me.cmdSeg1.Size = New System.Drawing.Size(80, 24)
Me.cmdSeg1.TabIndex = 22
Me.cmdSeg1.Tag = "1"
Me.cmdSeg1.Text = "Capture 1"
'
'cmdSeg2
'
Me.cmdSeg2.Enabled = False
Me.cmdSeg2.Location = New System.Drawing.Point(720, 8)
Me.cmdSeg2.Name = "cmdSeg2"
Me.cmdSeg2.Size = New System.Drawing.Size(80, 24)
Me.cmdSeg2.TabIndex = 23
Me.cmdSeg2.Tag = "2"
Me.cmdSeg2.Text = "Capture 2"
'
'cmdBrowse
'
Me.cmdBrowse.Location = New System.Drawing.Point(8, 8)
Me.cmdBrowse.Name = "cmdBrowse"
Me.cmdBrowse.Size = New System.Drawing.Size(72, 24)
Me.cmdBrowse.TabIndex = 24
Me.cmdBrowse.Text = "File..."
'
'dlgWav
'
Me.dlgWav.Filter = """WAV Files""|*.wav"
Me.dlgWav.ReadOnlyChecked = True
Me.dlgWav.RestoreDirectory = True
Me.dlgWav.Title = """Select WAV file to open"""
'
'CircualrSounds
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(830, 356)
Me.Controls.Add(Me.cmdBrowse)
Me.Controls.Add(Me.cmdSeg2)
Me.Controls.Add(Me.cmdSeg1)
Me.Controls.Add(Me.cmdCircular)
Me.Controls.Add(Me.cmdCustom)
Me.Controls.Add(Me.txtData)
Me.Controls.Add(Me.cmdDefault)
Me.Controls.Add(Me.picWave)
Me.Controls.Add(Me.cmdStop)
Me.Controls.Add(Me.lbltime)
Me.Controls.Add(Me.Label3)
Me.Controls.Add(Me.lblPos)
Me.Controls.Add(Me.Label1)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = "CircualrSounds"
Me.Text = "Circular Buffers"
Me.ResumeLayout(False)

End Sub

#End Region

#Region "Variables in use"
'Direct Sound Device variable
Dim SoundDevice As Microsoft.DirectX.DirectSound.Device

'Direct Sound secondary buffer to hold data for mixing
Dim SbufferOriginal As Microsoft.DirectX.DirectSound.SecondaryBuffer

'Sample Music
Dim SoundFile As String

'Wave file data parser
Dim MYwave As CWAVReader

'Event handler to process tick intervals
Dim MyPaint As System.Timers.ElapsedEventHandler

'Timer object
Dim MyPaintTimer As Timer

'Varibale to accumulate ticks in milliseconds
Dim MyTime As Integer

'Variable to hold total play time of the wav file
Dim TotalTime As Single

'Variable to holed wav data to draw graph
Dim Data() As Byte

'Wave data picture
Dim myPicture As Bitmap

'Circular buffer update interval variable
Dim TimerStep As Integer

'Play indicator and wav data picture composite
Dim PlayPicture As Bitmap

'For Circular buffer

'Next location in circular buffer we can write
Dim NextWritePos As Integer

'Total bytes writen to circular buffer
Dim PlayerPosition As Integer

'Maximum latency control time in milliseconds....
Dim Latency As Integer = 300

'Memeroy stream to hold actual sound Data.
Dim DataMemStream As IO.MemoryStream

'Event handler to process tick intervals
Dim PlayerTime As System.Timers.ElapsedEventHandler

'Timer object
Dim PlayerEvent As Timer

'DataArray for Circular sound
Dim DataArray As Byte()

'varibale to control if we play using memory stream or Data Array
Dim IsArray As Boolean = False

'Varibales to capture the portions of the sound we want to play
Dim StartPoint As Point
Dim EndPoint As Point
#End Region

#Region "Graphic methods and functions"
'Draws the Sound data as a wave onto a canvas using double buffering
to speed up work
Function DrawGraph(ByVal Data() As Int16) As Bitmap
'Create the Canvas
Dim myBitmap As System.Drawing.Bitmap

'Create an array to hold the wav data which we can sort
Dim tempData(Data.Length) As Integer

'Copy the data to the temporary location .. can be done better
Data.CopyTo(tempData, 0)

'Sort the array to get the maximum and minimum Y values
Array.Sort(tempData)

'generate the Canvas (drawing board in memory
myBitmap = New Bitmap(picWave.Width, picWave.Height)

'Create your paint brush, pens and drawing objects
Dim myGraphic As System.Drawing.Graphics
myGraphic = Graphics.FromImage(myBitmap)

'draw the backgound with a custom color
myGraphic.Clear(Color.FromArgb(181, 223, 225))

'Get the paramerets to draw the data and scale to fit the canvans but
draw form the middle
Dim YMax As Integer = tempData(Data.Length - 1)
Dim YMin As Integer = tempData(0)
Dim XMax As Integer = picWave.Width
Dim Xmin As Integer = 0

'Create an array of points to draw a line
Dim PicPoint(Data.Length - 1) As System.Drawing.PointF

Dim Count As Integer
Dim Step1 As Single 'Scale the data between Ymax and Ymin
Dim Step2 As Single 'Scale the data further to fit between the canvas hieght
Dim step3 As Single 'Draw the point form the middle of the canvas

Dim Mypen As New Pen(Color.FromArgb(24, 101, 123))

'Draw the lines from the series of points representing the sound wav
For Count = 0 To Data.Length - 1
Step1 = CSng(Data(Count) / (YMax - YMin))
Step2 = CSng(Step1 * picWave.Height / 2)
step3 = CSng(Step2 + (picWave.Height / 2))
PicPoint(Count) = New System.Drawing.PointF(CSng(XMax * (Count /
Data.Length)), step3)
Next

'Draw the lines
myGraphic.DrawLines(Mypen, PicPoint)

'Draw graphics onto canvas
myGraphic.DrawImage(myBitmap, picWave.Width, picWave.Height)

'return the picture memeory object
Return (myBitmap)
End Function

'The handler to the timer tick event :
'Paints the location of the player pointer on the sound graph as the
data is played
Sub MyPainPoint(ByVal obj As Object, ByVal Args As
System.Timers.ElapsedEventArgs)
'Control to ensure we stop when the total cumulated bytes read is
equal to the total data size
If PlayerPosition >= MYwave.SubChunkSizeTwo Then
StopPlay()
'Update the labels
lblPos.Text = MYwave.SubChunkSizeTwo.ToString
lbltime.Text = MYwave.PlayTimeSeconds().ToString
End If
'Draw a line red from top to bottom showing the curent position based
on play position
'The PlayerPOsition is the absolute location of the current played data
'This is taken as a ratio of the total data size adn scaled to the
width of the picture
'control
Dim XPos As Single = CSng((PlayerPosition / MYwave.SubChunkSizeTwo) *
picWave.Width)
Dim posgraphic As Graphics

'Clone the original canvas (this has the original sound graph). We do
not need to
're-draw this large graph as it willtake much processing time!
PlayPicture = CType(myPicture.Clone, Bitmap)
posgraphic = Graphics.FromImage(PlayPicture)

'Draw the pointer
Dim Mypen As Pen = New Pen(Color.Red)
posgraphic.DrawLine(Mypen, XPos, 0, XPos, picWave.Height)

'Draw line to myPicture
MyTime += TimerStep
posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)

'Update the status on the labels
lblPos.Text = PlayerPosition.ToString
lbltime.Text = (MyTime / 1000).ToString

'force a redraw of the picture updated.
Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top,
picWave.Width, picWave.Height))
End Sub


'Capture the start point
Private Sub picWave_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles picWave.MouseDown
'Get the position of the mouse and draw a line
StartPoint = New Point
If e.Button = MouseButtons.Left Then
StartPoint.X = e.X
StartPoint.Y = e.Y
End If
End Sub

'Draw a band over the data we want to capture
Private Sub picWave_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles picWave.MouseMove
If e.Button = MouseButtons.Left Then
If StartPoint.IsEmpty = False Then
EndPoint = New Point
EndPoint.X = e.X
EndPoint.Y = e.Y
DrawSelection()
End If
End If
End Sub

'Draw the band over the selection
Sub DrawSelection()
'Draw a rubberband
Dim posgraphic As Graphics
Dim RubberRect As Rectangle
RubberRect = New Rectangle(StartPoint.X, 0, EndPoint.X - StartPoint.X,
picWave.Height - 3)

'Clone the original canvas
PlayPicture = CType(myPicture.Clone, Bitmap)
posgraphic = Graphics.FromImage(PlayPicture)

'Draw the pointer
Dim Mypen As Pen = New Pen(Color.Green)

'Create a transparent brush using alpha blending techniques
Dim MyBrush As SolidBrush = New SolidBrush(Color.FromArgb(85, 204, 32, 92))

'Draw the boarder
posgraphic.DrawRectangle(Mypen, RubberRect)

'Fill the color
posgraphic.FillRectangle(MyBrush, RubberRect)
'Draw the picture on the form with the updated section of music to clip
posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)

'redraw the portion only
Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top,
picWave.Width, picWave.Height))
End Sub

'Complete the drawing and capture the start and width/hieght of the rubber band
Private Sub picWave_MouseUp(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles picWave.MouseUp
EndPoint.X = e.X
EndPoint.Y = e.Y
DrawSelection()
End Sub

#End Region

#Region "Sound functions and helper code"

'Creates a memory stream from Array Data to support playing sound from
a memory stream
'In an actual implementation, this would be reading data from an
actual file on the Harddisk
Function GetMemeoryStream(ByVal Data() As Byte) As IO.MemoryStream
Return New IO.MemoryStream(Data)
End Function

'Stop processing any sound
Private Sub cmdStop_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdStop.Click
StopPlay()
cmdBrowse.Enabled = False
cmdDefault.Enabled = True
cmdCustom.Enabled = True
cmdCircular.Enabled = True
cmdSeg1.Enabled = False
cmdSeg2.Enabled = False
cmdStop.Enabled = False
End Sub

'using the form events paint, redraw the picture
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
'Draw the picture of the updated pointer location
picWave.Image = PlayPicture
End Sub

'Stop playing the sound
Sub StopPlay()
'Get the status of the player
If SbufferOriginal.Status.Playing Then
SbufferOriginal.Stop()
'shut the paint timer
If Not IsNothing(MyPaintTimer) Then
MyPaintTimer.Enable(False)
MyPaintTimer = Nothing
End If

'shut the player timer
If Not IsNothing(PlayerTime) Then
PlayerEvent.Enable(False)
PlayerEvent = Nothing
End If
End If
End Sub

'Update the Circular buffer based on either a stream of data array
Sub PlayerEventHandler(ByVal obj As Object, ByVal Args As
System.Timers.ElapsedEventArgs)
'Stop if we have read all data
If PlayerPosition >= MYwave.SubChunkSizeTwo Then
StopPlay()
End If
'If an array, use the dataArray to top up new data to the circular buffer
If IsArray = False Then
'Get the amount of data to write and thereafter write the data
WriteData(GetPlayedSize())
Else
WriteDataArray(GetPlayedSize())
End If
End Sub

'Play a sound wave using a default static buffer
Private Sub cmdDefault_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdDefault.Click
TimerStep = 25 'Interval to draw the pointer on the graph
cmdCustom.Enabled = False
cmdCircular.Enabled = False
cmdSeg1.Enabled = False
cmdSeg2.Enabled = False
Try
MyPaintTimer = New Timer(TimerStep, MyPaint)

TotalTime = CSng(MYwave.SubChunkSizeTwo / MYwave.ByteRate)
SbufferOriginal = New
Microsoft.DirectX.DirectSound.SecondaryBuffer(SoundFile, SoundDevice)
Me.Text = "Buffer size :" & SbufferOriginal.Caps.BufferBytes.ToString

'Start the timer
MyPaintTimer.Enable(True)
MyTime = 0
SbufferOriginal.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub


'Use a circular buffer to update the primary buffer with data
Private Sub cmdCustom_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdCustom.Click
Dim Format As Microsoft.DirectX.DirectSound.WaveFormat
Dim Desc As Microsoft.DirectX.DirectSound.BufferDescription

cmdBrowse.Enabled = False
cmdDefault.Enabled = False
cmdCustom.Enabled = True
cmdCircular.Enabled = False
cmdSeg1.Enabled = False
cmdSeg2.Enabled = False
cmdStop.Enabled = True

IsArray = False

'create a format object tro describe the wave properties
'These are not random numbers.. they are corelated based on
'formulae such that a change in one will result to change in
'related properties. Ref the contents of the label during run time
Format = New Microsoft.DirectX.DirectSound.WaveFormat
Format.AverageBytesPerSecond = CInt(MYwave.ByteRate)
Format.BitsPerSample = CShort(MYwave.BitsPerSample)
Format.BlockAlign = CShort(MYwave.BlockAlign)
Format.Channels = CShort(MYwave.NumChannels)
Format.FormatTag = Microsoft.DirectX.DirectSound.WaveFormatTag.Pcm
Format.SamplesPerSecond = CInt(MYwave.SampleRate)

'Create the buffer description and buffer size
Desc = New Microsoft.DirectX.DirectSound.BufferDescription(Format)
Desc.BufferBytes = CInt(MYwave.SampleRate) * 1

'Add capabilites of the buffer
Desc.ControlFrequency = True
Desc.ControlPan = True
Desc.ControlVolume = True
Desc.GlobalFocus = True

'Create a secondary sound buffer
SbufferOriginal = New
Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, SoundDevice)
TimerStep = Latency \ 10
MyTime = 0
SbufferOriginal.Stop()
SbufferOriginal.SetCurrentPosition(0)
WriteData(SbufferOriginal.Caps.BufferBytes)
NextWritePos = 0
PlayerPosition = 0
MyPaintTimer = New Timer(TimerStep, MyPaint)

'Create a timer
PlayerTime = New System.Timers.ElapsedEventHandler(AddressOf PlayerEventHandler)
PlayerEvent = New Timer(TimerStep, PlayerTime)
PlayerEvent.Enable(True)

'Play the sound
SbufferOriginal.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
MyPaintTimer.Enable(True)
End Sub

'get the played data size
Function GetPlayedSize() As Integer
Dim Pos As Integer
Pos = SbufferOriginal.PlayPosition
If Pos < NextWritePos Then
Return Pos + (SbufferOriginal.Caps.BufferBytes - NextWritePos)
Else
Return Pos - NextWritePos
End If
End Function

'Ensure we have a latency of maximum 300 milliseconds
Function TimeToDataSize(ByVal Latency As Integer) As Integer
Dim DataSize As Integer
DataSize = CInt(Latency * SbufferOriginal.Format.AverageBytesPerSecond / 1000)
DataSize = DataSize - (DataSize Mod SbufferOriginal.Format.BlockAlign)
Return DataSize
End Function

'Write data to the circular buffer (secondary buffer that is)
Sub WriteData(ByVal DataSize As Integer)
Dim Tocopy As Integer
'make sure the data is less than the latency amount of 300ms
Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))

'Only write data if there is something to write!
If Tocopy > 0 Then
'Restore the buffer
If SbufferOriginal.Status.BufferLost Then
SbufferOriginal.Restore()
End If

'Copy the data to the buffer
'The DataMemStream is a binary stream object. This can also be a
'large WAV file still on 'harddisk
SbufferOriginal.Write(NextWritePos, DataMemStream, Tocopy,
Microsoft.DirectX.DirectSound.LockFlag.None)
'As data is read form the DataMemStream, the position (internal of the
structure) advances
'by the size of Tcopy

'Advance the total cumulative bytes read
PlayerPosition += Tocopy

'advance the NextWrite pointer
NextWritePos += Tocopy

'If we are at the end, we wrap round
If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
End If
End If
End Sub

'Write data to a Data Array.... similar to the above..using memory a stream
Sub WriteDataArray(ByVal DataSize As Integer)
Dim Tocopy As Integer
'ensure we do not have a big latency . the maximum is 300 ms
Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))

'is we have data, then write to the array and play
If Tocopy > 0 Then
'Restore the buffer
If SbufferOriginal.Status.BufferLost Then
SbufferOriginal.Restore()
End If

'Copy the data to the Array
're-create the data array (this is very slow!!)
ReDim DataArray(Tocopy - 1)

'Position the memory stream to the last location we read from.
DataMemStream.Position = PlayerPosition

'Copy the data from the stream to the array
DataMemStream.Read(DataArray, 0, Tocopy - 1)

'Write the data to the seconadry buffer
SbufferOriginal.Write(NextWritePos, DataArray,
Microsoft.DirectX.DirectSound.LockFlag.None)

'Adavance the pointers
PlayerPosition += Tocopy
NextWritePos += Tocopy
If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
End If
End If
End Sub

'Create a circular buffer based on the data array... similar to the
former button...
Private Sub cmdCircular_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles cmdCircular.Click
Dim Format As Microsoft.DirectX.DirectSound.WaveFormat
Dim Desc As Microsoft.DirectX.DirectSound.BufferDescription

cmdBrowse.Enabled = False
cmdDefault.Enabled = False
cmdCustom.Enabled = False
cmdCircular.Enabled = True
cmdSeg1.Enabled = False
cmdSeg2.Enabled = False
cmdStop.Enabled = True

IsArray = True

Format = New Microsoft.DirectX.DirectSound.WaveFormat
Format.AverageBytesPerSecond = CInt(MYwave.ByteRate)
Format.BitsPerSample = CShort(MYwave.BitsPerSample)
Format.BlockAlign = CShort(MYwave.BlockAlign)
Format.Channels = CShort(MYwave.NumChannels)
Format.FormatTag = Microsoft.DirectX.DirectSound.WaveFormatTag.Pcm
Format.SamplesPerSecond = CInt(MYwave.SampleRate)

Desc = New Microsoft.DirectX.DirectSound.BufferDescription(Format)
Desc.BufferBytes = CInt(MYwave.SampleRate) * 1
Desc.ControlFrequency = True
Desc.ControlPan = True
Desc.ControlVolume = True
Desc.GlobalFocus = True

SbufferOriginal = New
Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, SoundDevice)
TimerStep = Latency \ 5
MyTime = 0
SbufferOriginal.Stop()
SbufferOriginal.SetCurrentPosition(0)
WriteDataArray(SbufferOriginal.Caps.BufferBytes)
NextWritePos = 0
PlayerPosition = 0
MyPaintTimer = New Timer(TimerStep, MyPaint)
PlayerTime = New System.Timers.ElapsedEventHandler(AddressOf PlayerEventHandler)
PlayerEvent = New Timer(TimerStep, PlayerTime)
PlayerEvent.Enable(True)
SbufferOriginal.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
MyPaintTimer.Enable(True)
End Sub
'Plays a segment of data based on the rubber-band we have drawn prior
to raising this event
Public Sub SetSegment(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdSeg1.Click, cmdSeg2.Click
'set local variables to use
Dim tag As Int16
Dim theButton As Button
Dim DataStart As Integer
Dim DataStop As Integer
Dim BufferSize As Integer
'initialize the direct sound objects
Dim Format As Microsoft.DirectX.DirectSound.WaveFormat
Dim Desc As Microsoft.DirectX.DirectSound.BufferDescription
Dim MixBuffer As Microsoft.DirectX.DirectSound.SecondaryBuffer

cmdBrowse.Enabled = False
cmdDefault.Enabled = False
cmdCustom.Enabled = False
cmdCircular.Enabled = False
cmdSeg1.Enabled = False
cmdSeg2.Enabled = True
cmdStop.Enabled = False

theButton = CType(sender, Button)
theButton.Enabled = False

'get the locations from where to read data from
DataStart = CInt(MYwave.SubChunkSizeTwo * (StartPoint.X / picWave.Width))
DataStop = CInt(MYwave.SubChunkSizeTwo * (EndPoint.X / picWave.Width))

StartPoint = Nothing
EndPoint = Nothing

'ensure that the data is aligned.. if not, noise results
DataStart = DataStart - (DataStart Mod CInt(MYwave.BlockAlign))
DataStop = DataStop - (DataStop Mod CInt(MYwave.BlockAlign))

'Get the data into a Data array
Dim DataSegment(DataStop - DataStart) As Byte

'Read the data from the Stream
DataMemStream.Position = DataStart

'Read from the stream to the array buffer
DataMemStream.Read(DataSegment, 0, DataStop - DataStart)

'Now play the sound.
'For custom sound, you must set the format
Format = New Microsoft.DirectX.DirectSound.WaveFormat
Format.AverageBytesPerSecond = CInt(MYwave.ByteRate)
Format.BitsPerSample = CShort(MYwave.BitsPerSample)
Format.BlockAlign = CShort(MYwave.BlockAlign)
Format.Channels = CShort(MYwave.NumChannels)
Format.FormatTag = Microsoft.DirectX.DirectSound.WaveFormatTag.Pcm
Format.SamplesPerSecond = CInt(MYwave.SampleRate)

Desc = New Microsoft.DirectX.DirectSound.BufferDescription(Format)
BufferSize = DataStop - DataStart + 1

'ensure the size is also aligned
BufferSize = BufferSize + (BufferSize Mod CInt(MYwave.BlockAlign))

Desc.BufferBytes = BufferSize
Desc.ControlFrequency = True
Desc.ControlPan = True
Desc.ControlVolume = True
Desc.GlobalFocus = True

'Play the sound
Try
MixBuffer = New Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, SoundDevice)
MixBuffer.Stop()
MixBuffer.SetCurrentPosition(0)

If MixBuffer.Status.BufferLost Then
MixBuffer.Restore()
End If

MixBuffer.Write(0, DataSegment, Microsoft.DirectX.DirectSound.LockFlag.None)
MixBuffer.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

'open a file dialogue box
Private Sub cmdBrowse_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdBrowse.Click
dlgWav.ShowDialog(Me)
SoundFile = dlgWav.FileName
If SoundFile <> "" Then
Me.Text = "..reading file....."
Me.Refresh()
'Read the data now!
OpenWavAndSetSystem()
cmdBrowse.Enabled = False
cmdDefault.Enabled = True
cmdCustom.Enabled = True
cmdCircular.Enabled = True
cmdSeg1.Enabled = True
cmdSeg2.Enabled = True
cmdStop.Enabled = True
Me.Text = "Circular Buffers " & SoundFile
picWave.Enabled = True
End If
Me.Refresh()
End Sub

'Open the wave file and read the Data
'Load the wave file and draw the wave data graph
Sub OpenWavAndSetSystem()
'Create the Direct Sound Device object
SoundDevice = New Microsoft.DirectX.DirectSound.Device

'You have to assign the window handler to the device inorder not to
hog the sound card
'This is also required!
SoundDevice.SetCooperativeLevel(Me.Handle,
Microsoft.DirectX.DirectSound.CooperativeLevel.Normal)

'Create the wav file parser and process the wave file
MYwave = New CWAVReader(SoundFile)

'Set the timer using delegates (similar to events)
'Create a pointer to the code that runs whenever a tick event occurs
MyPaint = New System.Timers.ElapsedEventHandler(AddressOf MyPainPoint)
'Create a timer object and pass the interval and function pointer to
the code that shall
'handle the tick events using delegate (similar to event sinks)

'Draw the baseline sound wave from the data onto a canvas : bitmap
myPicture = DrawGraph(MYwave.GetSoundDataValue())

'Create a memory stream from the data contained int he sound file.
'In an actual implementation, we should be reading the data directly from the
'file and play it via stream or small segments of data in an array
DataMemStream = GetMemeoryStream(MYwave.WaveSoundData)


'Draw all other data on the clone picture graph
PlayPicture = CType(myPicture.Clone, Bitmap)

'Get the sound (wav data properties) and display on screen
Dim myData As New System.Text.StringBuilder
myData.Append("Audio Format (Must be PCM for WAV):" &
MYwave.AudioFormat & vbCrLf)
myData.Append("Bits Per Sample :" & MYwave.BitsPerSample & vbCrLf)
myData.Append("Block Align :" & MYwave.BlockAlign & "=[NumChannels *
BitsPerSample/8]" & vbCrLf)
myData.Append("Byte Rate (Number of Bytes per second):" &
MYwave.ByteRate & "=[SampleRate * NumChannels * BitsPerSample/8]" &
vbCrLf)
myData.Append("Format ID :" & MYwave.FormatID & vbCrLf)
myData.Append("Audio Frequency :" & MYwave.Frequency & vbCrLf)
myData.Append("Number of samples:" & MYwave.NumberOfSamples & vbCrLf)
myData.Append("Number of Channels (1 Mono- 2 Stereo):" &
MYwave.NumChannels & vbCrLf)
myData.Append("Audio Play Time:" & MYwave.PlayTimeSeconds.ToString & vbCrLf)
myData.Append("Sample Rate :" & MYwave.SampleRate & vbCrLf)
myData.Append("Data Chunk size :" & MYwave.SubChunkSizeTwo &
"=[NumSamples * NumChannels * BitsPerSample/8]" & vbCrLf)
myData.Append("Audio File Name :" & MYwave.WAVFileName & vbCrLf)

'Spit out the properties of the sound data to a text box
txtData.Text = myData.ToString
End Sub
#End Region

End Class

Class CWAVReader
'by ENO
'To Read a WAV File and Populate basic Properties
'On 3/11/2007
'For AfricaDotNet user group

'BigEndian bytes number 0 - 4
Private mChunkID As String
Public ReadOnly Property ChunkID() As String
Get
ChunkID = mChunkID
End Get
End Property
'from the Constructor
Private mWAVFileName As String
Public ReadOnly Property WAVFileName() As String
Get
WAVFileName = mWAVFileName
End Get
End Property

'Constructor to open stream
Sub New(ByVal SoundFilePathName As String)
mWAVFileName = SoundFilePathName
mOpen = OpenWAVStream(mWAVFileName)
'******************* MAIN WORK HERE ******************
'Parse the WAV file and read the
If mOpen Then
'Read the Header Data in THIS ORDER
'Each Read results to the File Pointer Moving
mChunkID = ReadChunkID(mWAVStream)
mChunkSize = ReadChunkSize(mWAVStream)
mFormatID = ReadFormatID(mWAVStream)
mSubChunkID = ReadSubChunkID(mWAVStream)
mSubChunkSize = ReadSubChunkSize(mWAVStream)
mAudioFormat = ReadAudioFormat(mWAVStream)
mNumChannels = ReadNumChannels(mWAVStream)
mSampleRate = ReadSampleRate(mWAVStream)
mByteRate = ReadByteRate(mWAVStream)
mBlockAlign = ReadBlockAlign(mWAVStream)
mBitsPerSample = ReadBitsPerSample(mWAVStream)
mSubChunkIDTwo = ReadSubChunkIDTwo(mWAVStream)
mSubChunkSizeTwo = ReadSubChunkSizeTwo(mWAVStream)
mWaveSoundData = ReadWAVSampleData(mWAVStream)
mWAVStream.Close()
End If
End Sub

'Property to tell if stream is open or not
Private mOpen As Boolean
Public ReadOnly Property IsWAVFileOPen() As Boolean
Get
IsWAVFileOPen = mOpen
End Get
End Property

'Open an IO Binary Stream to Read the WavFile
Private mWAVStream As IO.BinaryReader
Private Function OpenWAVStream(ByVal SoundFilePathName As String) As Boolean
Dim WAVStreamReader As IO.StreamReader
WAVStreamReader = New IO.StreamReader(SoundFilePathName)
mWAVStream = New IO.BinaryReader(WAVStreamReader.BaseStream)
If mWAVStream Is Nothing Then
Return False
Else
Return True
End If
End Function

'Dipose the resource
Sub Dispose()
If Not mWAVStream Is Nothing Then
mWAVStream.Close()
mWAVStream = Nothing
mOpen = False
End If
GC.SuppressFinalize(Me)
End Sub

'Close the stream
Protected Overrides Sub Finalize()
Me.Dispose()
MyBase.Finalize()
End Sub

'Read the ChunkID and return a string
Private Function ReadChunkID(ByVal WAVIOstreamReader As
IO.BinaryReader) As String
Dim DataBuffer() As Byte
Dim DataEncoder As System.Text.ASCIIEncoding
Dim TempString As Char()

DataEncoder = New System.Text.ASCIIEncoding
DataBuffer = WAVIOstreamReader.ReadBytes(4)
'Ensure we have data to spit out
If DataBuffer.Length <> 0 Then
TempString = DataEncoder.GetChars(DataBuffer, 0, 4)
Return TempString(0) & TempString(1) & TempString(2) & TempString(3)
Else
Return ""
End If
End Function

'Chunk size little endian bytes No. 5-13
Private mChunkSize As Long
Public ReadOnly Property ChunkSize() As Long
Get
ChunkSize = mChunkSize
End Get
End Property

'Read the ChunkID and return a string
Private Function ReadChunkSize(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(4)
ReadChunkSize = CLng(GetLittleEndianStringValue(DataBuffer))
End Function

'BigEndian bytes number 9 - 13
Private mFormatID As String
Public ReadOnly Property FormatID() As String
Get
FormatID = mFormatID
End Get
End Property

'Read the Format and return a string
Private Function ReadFormatID(ByVal WAVIOstreamReader As
IO.BinaryReader) As String
Dim DataBuffer() As Byte
Dim DataEncoder As System.Text.ASCIIEncoding
Dim TempString As Char()

DataEncoder = New System.Text.ASCIIEncoding
DataBuffer = WAVIOstreamReader.ReadBytes(4)
'Ensure we have data to spit out
If DataBuffer.Length <> 0 Then
TempString = DataEncoder.GetChars(DataBuffer, 0, 4)
Return TempString(0) & TempString(1) & TempString(2) & TempString(3)
Else
Return ""
End If
End Function

'Little Endian bytes number 13 - 17
Private mSubChunkID As String
Public ReadOnly Property SubChunkID() As String
Get
SubChunkID = mSubChunkID
End Get
End Property

'Read the sub chunkID and return a string
Private Function ReadSubChunkID(ByVal WAVIOstreamReader As
IO.BinaryReader) As String
Dim DataBuffer() As Byte
Dim DataEncoder As System.Text.ASCIIEncoding
Dim TempString As Char()

DataEncoder = New System.Text.ASCIIEncoding
DataBuffer = WAVIOstreamReader.ReadBytes(4)
'Ensure we have data to spit out
If DataBuffer.Length <> 0 Then
TempString = DataEncoder.GetChars(DataBuffer, 0, 4)
Return TempString(0) & TempString(1) & TempString(2) & TempString(3)
Else
Return ""
End If
End Function

'SubChunk size little endian bytes No. 17-21
Private mSubChunkSize As Long
Public ReadOnly Property SubChunkSize() As Long
Get
SubChunkSize = mSubChunkSize
End Get
End Property

'Read the SubChunkID and return a string
Private Function ReadSubChunkSize(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(4)
ReadSubChunkSize = CLng(GetLittleEndianStringValue(DataBuffer))
End Function

'Audio Format little endian bytes No. 20-22
Private mAudioFormat As Long
Public ReadOnly Property AudioFormat() As Long
Get
AudioFormat = mAudioFormat
End Get
End Property

'Read the SubChunkID and return a string
Private Function ReadAudioFormat(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(2)
ReadAudioFormat = CLng(GetLittleEndianStringValueDuplex(DataBuffer))
End Function

'Number of Channels little endian bytes No. 22-24
Private mNumChannels As Long
Public ReadOnly Property NumChannels() As Long
Get
NumChannels = mNumChannels
End Get
End Property

'Read the SubChunkID and return a string
Private Function ReadNumChannels(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(2)
ReadNumChannels = CLng(GetLittleEndianStringValueDuplex(DataBuffer))
End Function

'Sample Rate little endian bytes No. 24-28
Private mSampleRate As Long
Public ReadOnly Property SampleRate() As Long
Get
SampleRate = mSampleRate
End Get
End Property

Public ReadOnly Property Frequency() As Long
Get
Frequency = mSampleRate
End Get
End Property

'Get the number of samples
Public ReadOnly Property NumberOfSamples() As Long
Get
NumberOfSamples = SubChunkSizeTwo * 8 \ (NumChannels * BitsPerSample)
End Get
End Property

'Read the Sample Rate and return a string
Private Function ReadSampleRate(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(4)
ReadSampleRate = CLng(GetLittleEndianStringValue(DataBuffer))
End Function

'Byte Rate little endian bytes No. 28-32
Private mByteRate As Long
Public ReadOnly Property ByteRate() As Long
Get
ByteRate = mByteRate
End Get
End Property

'Get the play time in seconds
Public ReadOnly Property PlayTimeSeconds() As Single
Get
PlayTimeSeconds = CSng(SubChunkSizeTwo / ByteRate)
End Get
End Property

'Read the Byte Rate and return a string
Private Function ReadByteRate(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(4)
ReadByteRate = CLng(GetLittleEndianStringValue(DataBuffer))
End Function

'Block Align little endian bytes No. 32-34
Private mBlockAlign As Long
Public ReadOnly Property BlockAlign() As Long
Get
BlockAlign = mBlockAlign
End Get
End Property

'Read the Block Align and return a string
Private Function ReadBlockAlign(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(2)
ReadBlockAlign = CLng(GetLittleEndianStringValueDuplex(DataBuffer))

End Function

'Buits Per Sample little endian bytes No. 34-36
Private mBitsPerSample As Long
Public ReadOnly Property BitsPerSample() As Long
Get
BitsPerSample = mBitsPerSample
End Get
End Property

'Read the Bits Per Sample and return a string
Private Function ReadBitsPerSample(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(2)
ReadBitsPerSample = CLng(GetLittleEndianStringValueDuplex(DataBuffer))

End Function

'Buits Per Sample Big endian bytes No. 36-40
Private mSubChunkIDTwo As String
Public ReadOnly Property SubChunkIDTwo() As String
Get
SubChunkIDTwo = mSubChunkIDTwo
End Get
End Property

'Read the SubChunkTwoID Sample and return a string
Private Function ReadSubChunkIDTwo(ByVal WAVIOstreamReader As
IO.BinaryReader) As String
Dim DataBuffer() As Byte
Dim DataEncoder As System.Text.ASCIIEncoding
Dim TempString As Char()
Dim Datastr As String

DataEncoder = New System.Text.ASCIIEncoding
DataBuffer = WAVIOstreamReader.ReadBytes(1)
'Ensure we have data to spit out
TempString = DataEncoder.GetChars(DataBuffer, 0, 1)
Datastr = TempString(0)
If Datastr <> "d" Then
'Read until you get data
DataBuffer = WAVIOstreamReader.ReadBytes(1)
TempString = DataEncoder.GetChars(DataBuffer, 0, 1)
Datastr = TempString(0)
While Datastr <> "d"
DataBuffer = WAVIOstreamReader.ReadBytes(1)
TempString = DataEncoder.GetChars(DataBuffer, 0, 1)
Datastr = TempString(0)
End While
DataBuffer = WAVIOstreamReader.ReadBytes(3)
TempString = DataEncoder.GetChars(DataBuffer, 0, 3)
Datastr = "d" & (TempString(0) & TempString(1) & TempString(2))

Else
DataBuffer = WAVIOstreamReader.ReadBytes(3)
TempString = DataEncoder.GetChars(DataBuffer, 0, 3)
Datastr = "d" & (TempString(0) & TempString(1) & TempString(2))
Return Datastr
End If
End Function

'Buits Per Sample little endian bytes No. 40-44
Private mSubChunkSizeTwo As Long
Public ReadOnly Property SubChunkSizeTwo() As Long
Get
SubChunkSizeTwo = mSubChunkSizeTwo
End Get
End Property

'Read the Bits Per Sample and return a string
Private Function ReadSubChunkSizeTwo(ByVal WAVIOstreamReader As
IO.BinaryReader) As Long
Dim DataBuffer() As Byte
DataBuffer = WAVIOstreamReader.ReadBytes(4)
ReadSubChunkSizeTwo = CLng(GetLittleEndianStringValue(DataBuffer))
End Function

'Get the little endian value
Private Function GetLittleEndianStringValueDuplex(ByVal DataBuffer()
As Byte) As String
Dim ValueString As String = "&h"

If DataBuffer.Length <> 0 Then
'In little endian
If Hex(DataBuffer(1)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(1))
Else
ValueString &= Hex(DataBuffer(1))
End If

If Hex(DataBuffer(0)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(0))
Else
ValueString &= Hex(DataBuffer(0))
End If
Else
ValueString = "0"
End If
GetLittleEndianStringValueDuplex = ValueString
End Function

'Get the small endian value
Private Function GetLittleEndianStringValue(ByVal DataBuffer() As
Byte) As String
Dim ValueString As String = "&h"

If DataBuffer.Length <> 0 Then
'In little endian we reverse the aray data and pad the same where the
length is 1
If Hex(DataBuffer(3)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(3))
Else
ValueString &= Hex(DataBuffer(3))
End If

If Hex(DataBuffer(2)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(2))
Else
ValueString &= Hex(DataBuffer(2))
End If

If Hex(DataBuffer(1)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(1))
Else
ValueString &= Hex(DataBuffer(1))
End If

If Hex(DataBuffer(0)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(0))
Else
ValueString &= Hex(DataBuffer(0))
End If
Else
ValueString = "0"
End If
GetLittleEndianStringValue = ValueString
End Function

'Buffers to hold Data
Private mWaveSoundData As Byte()

'Return the sound Data to display
ReadOnly Property WaveSoundData() As Byte()
Get
WaveSoundData = mWaveSoundData
End Get
End Property

'Read Data to Pans both left and right
Private Function ReadWAVSampleData(ByVal WAVIOstreamReader As
IO.BinaryReader) As Byte()
Dim tempBuffer() As Byte
tempBuffer = WAVIOstreamReader.ReadBytes(CInt(mSubChunkSizeTwo))
Return tempBuffer
End Function

'returns the wave data as a byte array
Public Function GetSoundDataValue() As Int16()
Dim DataCount As Integer
Dim tempStream As IO.BinaryReader
tempStream = New IO.BinaryReader(New IO.MemoryStream(mWaveSoundData))
tempStream.BaseStream.Position = 0
'Create a data array to hold the data read from the stream
'Read chunks of int16 from the stream (already aligned!)
Dim tempData(CInt(tempStream.BaseStream.Length / 2)) As Int16
While DataCount <= tempData.Length - 2
tempData(DataCount) = tempStream.ReadInt16()
DataCount += 1
End While
tempStream.Close()
tempStream = Nothing
Return tempData
End Function
End Class

'By ENO
'System Timer to control precicly the timer interval
'uses delegates
'For AfricaDoTnet user group
'Used for Circular buffer and wave position painting
Imports System.Timers

'Function pointer
Delegate Sub OnTimer(ByVal Obj As Object, ByVal Arg As
System.Timers.ElapsedEventArgs)

Public Class Timer
Dim mTimer As Timers.Timer
Dim MyonTimer As System.Timers.ElapsedEventHandler
Dim mtimerEnabled As Boolean

'Class constructor
Sub New(ByVal Interval As Double, ByRef handle As
System.Timers.ElapsedEventHandler)
mTimer = New Timers.Timer
mTimer.Interval = Interval
MyonTimer = handle
End Sub

'to enable / disable the timer
Sub Enable(ByVal SetIT As Boolean)
If SetIT = True Then
AddHandler mTimer.Elapsed, MyonTimer
mTimer.Enabled = SetIT
mTimer.AutoReset = True
Else
mTimer.Enabled = SetIT
mTimer.AutoReset = False
RemoveHandler mTimer.Elapsed, MyonTimer
End If
mtimerEnabled = SetIT
End Sub

'Get the status of the timer .. running or not
ReadOnly Property TimerEnabled() As Boolean
Get
mtimerEnabled = TimerEnabled
End Get
End Property

End Class
QuestionI would love the source code [modified] Pin
seanclancy17-Oct-08 7:35
seanclancy17-Oct-08 7:35 
AnswerRe: I would love the source code Pin
ENO21-Oct-08 4:20
ENO21-Oct-08 4:20 
GeneralSpeed change Pin
seanclancy17-Oct-08 7:13
seanclancy17-Oct-08 7:13 
GeneralRe: Speed change Pin
plouvou17-Oct-08 9:38
plouvou17-Oct-08 9:38 
GeneralRe: Speed change Pin
plouvou12-Feb-09 9:47
plouvou12-Feb-09 9:47 
GeneralLoaderLock Pin
legcsabi8-Oct-08 6:40
legcsabi8-Oct-08 6:40 
GeneralRe: LoaderLock Pin
JonFrost26-Feb-09 0:43
JonFrost26-Feb-09 0:43 
Generalneed SourceCode for this article Pin
Member 397625623-Sep-08 19:36
Member 397625623-Sep-08 19:36 
QuestionWave file Recording with microphone (VB.Net)? Pin
nabilg9-Sep-08 2:52
nabilg9-Sep-08 2:52 
QuestionWave file Recording with microphone (VB.Net)? Pin
nabilg9-Sep-08 2:51
nabilg9-Sep-08 2:51 
GeneralSource Code Pin
DenTruman3-Sep-08 23:45
DenTruman3-Sep-08 23:45 
Questioncan you send me source code?? Pin
jackyxinli3-Sep-08 15:28
jackyxinli3-Sep-08 15:28 
Questionthe message with title post source code it out Pin
>>MonMon<<9-Aug-08 11:03
>>MonMon<<9-Aug-08 11:03 
QuestionHow about Capture example? Pin
positivebalance11-Apr-08 15:51
positivebalance11-Apr-08 15:51 
GeneralOpenWAVStream(mWAVFileName) Pin
Member 170279722-Jan-08 22:56
Member 170279722-Jan-08 22:56 
GeneralRe: OpenWAVStream(mWAVFileName) Pin
ENO23-Jan-08 22:42
ENO23-Jan-08 22:42 
GeneralRe: OpenWAVStream(mWAVFileName) Pin
Member 17027971-Feb-08 1:03
Member 17027971-Feb-08 1:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.