Blind Geeks dot org

Basic Audio Programming

Part I: Playing Audio

Chapter 2: High-Level MCI Audio

 

 

DISCLAIMER:

 

This lesson series is intended for experienced computer programming professionals.  Please read the following before continuing.

 

 

  It is assumed that student has had courses or lessons in at least one computer language.

  While languages such as Visual Basic, C#, Flex and Actionscript are demonstrated in this series, this is not a series intructing the student in programming in these languages.  This is a series in Audio Programming.

 

 

 

Part 1 – The Visuals.

 

In Chapter 1, we learned how to use MCI to play low-level audio (wave files).  In this chapter, we use MCI again but a higher level method called mciSendString.  mciSendString is a very powerful but under-documented MCI technology.  And, some of it no longer works under Windows Vista and Windows 7, so you have to test your project thoroughly before releasing it.

 

Unlike the MCI low-level approach from Chapter 1, mciSendString can play MP3 files or even video files (*.avi).  We won’t be dealing with video in this series but MCI does have that capability.

 

Basically, when you call the mciSendString method, you give it a command like open “c:\mySong.mp3” type mpegvideo alias mySong

 

In the command above, even though we are not playing a video but an MP3 file, we must specify type mpegvideo if we are playing an MP3 file.  The file name must be surrounded by quotes as well, which can be tricky in Visual Basic.  I will show you how to do this once we start programming.

 

In this chapter, we will build a pretty sophisticated D.J. package using MCI and mciSendString.

 

I will show you how to build a user control, which will be our playback “deck” and then to build an application where we use four different instances of this deck control.

 

1.                  Open Visual Studio 2010.

2.       Create a new Visual Basic Windows Forms Control Library project named BGMCI_Deck.

3.       Open Solution Explorer.

4.       Rename UserControl1.vb to BGMCI_Deck.vb.

5.       When prompted to rename all references, activate the Yes button.

6.       Open BGMCI_Deck in the designer.

7.       Open the Properties Window.

8.       Set the size property for BGMCI_Deck to 484, 187.

9.       Save your work.

 

To make it easier for us to do this chapter, I have created a form with all of the controls we will be using in this chapter.  This form is on the web site named BGAudioChap2_Start.zip.  Download and unzip this file then load it in a separate instance of Visual Studio.  When it is loaded, open the form and use Control+A, Control+C to copy all of the controls to the clipboard.  Then, navigate to the form we created in the steps above and press Control+V to paste all of the controls that I created for you into our new user control form.

 

Visual Components

 

I’ve prepared a user interface for the playback deck we will be using in this chapter that resembles a radio station “cart machine” which looks similar to an 8-track tape player of the 1970’s.  Cart machines were used in the radio industry from the 1960’s through the 1990’s.  These were used primarily for playing commercials but some radio stations that could afford the $5 per tape cost used them for music as well.

 

Several years ago, I hired an artist to create images of radio equipment for one of my applications intended for the radio industry.  Among the images was one which resembled the cart tape used in radio.  I’ve provided this image for the playback deck for this chapter.  The tape image appears when a song is loaded and disappears when there is no song loaded on the deck.  This is done by using a Picture Box control.

 

Later, in this series, I will be using a Flash animation as the playback deck and the application will communicate with the Flash SWF Object through ActiveX communications to tell the SWF object when to begin the animation, pass song information and when to stop.

 

 

Control Functionality

 

Just as we did in chapter 1, we will now add the control functionality before we put in the code to do the audio.

 

Adding code to a user control is no different than adding code to a normal Windows form.  Simply open the form in code view.

 

  1. Open BGMCI_Deck in code view.
  2. Create Click event handlers for each button in the user control (BGMCI_Deck) but leave the code blocks (inside the click event handlers) empty for now.
  3. Create Public Sub Procedures as defined in the following table:

 

Procedure Name

LoadSong

PlaySong

StopSong

PauseSong

DisplayStatus

CloseSong

 

 

  1. In each click event handler, create a sub call to the appropriate procedure (LoadSong, PlaySong, etc.)
  2. Add a declaration for a private boolean class variable called bWaveFile and set its initial value to false.
  3. Type the following code in the LoadSong sub procedure block:

 

        Dim strFile As String = ""

        Dim res As DialogResult

        res = ofdLoadSong.ShowDialog()

        If res = Windows.Forms.DialogResult.OK Then

            ' load song

            If InStr(ofdLoadSong.FileName, ".wav") > 0 Then

                ' wave file

                bWaveFile = True

            Else

                ' mp3 file

                bWaveFile = False

            End If

            strFile = ofdLoadSong.FileName

        Else

            Exit Sub

        End If

        btnPlay.Enabled = True

        btnPlay.BackColor = Color.Green

        btnPlay.ForeColor = Color.White

        btnPause.Enabled = False

        btnStop.Enabled = False

 

 

        scrVolume.Value = scrVolume.Minimum

        scrVolume.Refresh()

 

        imgCart.Visible = True

        lblSongStatus.Text = "Loaded."

 

        lblRemTitle.Visible = True

        lblLenTitle.Visible = True

        lblSongInfo.Visible = True

        scrVolume.Enabled = True

 

 

  1. Type the following code inside the PlaySong procedure code block:

 

        If btnPlay.Enabled Then

            btnPlay.Enabled = False

            btnPlay.ForeColor = Color.LightSteelBlue

            btnPlay.BackColor = Color.Lime

            btnPause.Enabled = True

            btnPause.ForeColor = Color.Black

            btnPause.BackColor = Color.Gold

            btnStop.Enabled = True

            btnStop.ForeColor = Color.White

            btnStop.BackColor = Color.DarkRed

            lblSongStatus.Text = "Playing."

        End If

 

 

  1. Type the following code inside the StopSong procedure code block:

 

        If btnStop.Enabled Then

            btnPlay.Enabled = True

            btnPlay.ForeColor = Color.White

            btnPlay.BackColor = Color.DarkGreen

            btnPause.Enabled = False

            btnStop.Enabled = False

            btnStop.BackColor = Color.Red

            lblPosition.Text = " 0:00"

            lblSongStatus.Text = "Stopped."

        End If

 

  1. Type the following code inside the PauseSong procedure code block:

 

        If btnPause.BackColor = Color.Gold Then

            ' Pause

            btnPlay.Enabled = False

            btnPlay.ForeColor = Color.White

            btnPlay.BackColor = Color.DarkGreen

            btnPause.Enabled = True

            btnPause.BackColor = Color.Yellow

            btnPause.ForeColor = Color.Navy

            btnStop.Enabled = True

            lblSongStatus.Text = "Paused."

        Else

            ' Resume

            btnPlay.Enabled = False

            btnPlay.ForeColor = Color.Black

            btnPlay.BackColor = Color.Lime

            btnPause.Enabled = True

            btnPause.ForeColor = Color.Black

            btnPause.BackColor = Color.Gold

            btnStop.Enabled = True

            lblSongStatus.Text = "Playing."

 

        End If

 

 

  1. Type the following code inside the CloseSong procedure code block:

 

        StopSong()

        btnPlay.Enabled = False

        btnPause.Enabled = False

        btnStop.Enabled = False

        btnLoad.Enabled = True

        imgCart.Visible = False

        lblSongStatus.Text = ""

        lblLength.Text = ""

        lblPosition.Text = ""

        lblRemTitle.Visible = False

        lblLenTitle.Visible = False

        lblSongInfo.Text = ""

        lblSongInfo.Visible = False

        scrVolume.Enabled = False

        scrVolume.Value = scrVolume.Maximum

        scrVolume.Enabled = False

 

 

  1. Save Your Work.

 

 

Part 2, Section 1 – Simple Audio Playback Functionality

 

The first thing we need to do to get the audio functionality integrated into our project is to declare the Win32 MCI API.

 

  1. Open your user control project (BGMCI_Deck).
  2. Open your control form in code view and place the cursor just below the line that declares the class.
  3. Type in the following declaration:

 

    Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpszCommand As String, ByVal lpszReturnString As String, ByVal cchReturn As Integer, ByVal hwndCallback As Integer) As Integer

 

 

MCI’s native programming language is C++.  So, to use the MCI methods in languages such as Visual Basic and C#, we need to declare these functions providing the DLL name they are embedded in and the correct parameters for the method.  That is what we are doing in line 3 above.

 

Now, we need to add a private variable and a public property to use as an ‘Alias’ to identify the song to the MCI API.

 

  1. Add a private class variable of type String at the beginning of the class named strAlias and set its initial value to “”.
  2. Add a public property called songAlias and assign it the return value of strAlias.

 

The beginning of the class now looks like this:

 

Public Class BGMCI_Deck

    Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpszCommand As String, ByVal lpszReturnString As String, ByVal cchReturn As Integer, ByVal hwndCallback As Integer) As Integer

 

    Private bWaveFile As Boolean = False

    ' === Audio Code ===

    Private strAlias As String = ""

 

    ' Properties

    ' === Audio Code ===

    Public Property songAlias() As String

        Get

            songAlias = strAlias

        End Get

        Set(ByVal value As String)

            strAlias = value

        End Set

    End Property

 

Now that we have some data elements to hold the song information to send to the API, we can add some actual functionality.

 

We will build functionality to:

 

  1. Load Song.
  2. Close Song.
  3. Play Song.
  4. Stop Song.
  5. Pause Song.
  6. Display Song Information.

 

Load Song

 

We will now add code to the end of the LoadSong procedure.

 

  1. Open the project BGMCI_Deck.
  2. Open BGMCI_Deck.vb in code view and navigate to the end of the LoadSong procedure.
  3. Type the following lines of code at the very bottom of the procedure:

 

       ' === Audio Code ===

        ' if running control in debugger set Alias

        If strAlias = "" Then

            strAlias = "Song"

        End If

        ' close open streams

        mciSendString("close " & strAlias, Nothing, 0, 0)

        ' open new file

        mciSendString("open " & Chr(34) & strFile & Chr(34) & " type mpegvideo alias " & strAlias, "", 0, 0)

 

 

  1. Save Your Work.

 

The code above does the following.

 

  1. Sets the value for strAlias if it isn’t set.  When running the user control in the debugger, strAlias will default to blank so we set it to the string ‘Song’ to avoid run-time errors when sending it to the API.  An alias is used to refer to the song without having to send the entire path.  When we create the main DJ application that will use the user control, we will use more than one instance of the control, so we will need to set the alias for each instance separately or we will have strange results.  For example, if we set the alias to ‘Song’, then when we are playing a song in instance 1 of the control and go to load a different song into instance 2 of the control using the alias ‘Song’, Instance 1 will stop playing because we are using the same alias name for both instances.  Once we begin building the main application, I will show you what I mean.
  2. Close any song that is already loaded.
  3. Load Song selected by user.

To open and close Wave or MP3 files, we use the mciSendString function which is part of the MCI API.  You could also use this function to load videos but we will only work with audio in this series.

 

mciSendString() is a very powerful function but sadly, it was poorly documented and replaced by other technologies.  The parameter strings need to be formatted exactly or you may get an error.  Debugging such errors is very time-consuming due to the poor documentation on its use and error codes and the fact that this technology is now considered obsolete.

 

Some of the features offered with mciSendString() that once worked flawlessly under operating systems prior to Windows Vista now either give obscure errors or just appear not to function at all.  Once such feature is the song length parameter.  I spent literally hours trying to resolve this before determining that this feature was not supported in Vista and later operating systems.

 

Getting the song’s ID3 tag is also mot supported under newer versions of Windows.  I had to use a tag class I had previously developed for another application.

 

Notice in the code we just entered that we send the “open” command to the API using the mciSendString function.  This only opens the audio file.  It does not play the audio.

 

Lets examine the command we are sending to the API.

 

open “filename.mp3” type mpegvideo alias Song

 

The above line represents the command we send in the first parameter of mciSendString.  Notice that I placed double quotes around the file name of the audio file.  THIS IS REQUIRED.  In VB, this is either done like this:

 

open “ & chr(34) & “filename.mp3” & chr(34)

 

Or, like this:

 

open “”filename.mp3”””

 

The type keyword is required for all file type except wave audio.  The command for a wave file can be:

 

Open “”filename.wav”” alias WaveSong

 

As previously discussed, the alias keyword can be used to refer to an audio file instead of referring to it by the path but is not required.  The following are examples of using the close command using an alias and using the path.

 

Open “”c:\music\wavefile.wav”” alias WaveSong

Close WaveSong

 

Open “”c:\music\wavefile.wav””

Close “”c:\music\wavefile.wav””

 

 

 

To play the audio, we will need to issue the play command to the API, which we will do as part of the PlaySong procedure.  But, for now, we can test that the song was opened by adding the following lines of code after the last line we just entered:

 

       If mciSendString("play " & strAlias, "", 0, 0) <> 0 Then

            MessageBox.Show("Error Playing Song...")

            Application.Exit()

        End If

 

Now, save the project and press F5 to test it.  In Visual Studio 2010, the user control test container will appear.  Navigate to the load song button and select an MP3 file.  After you activate the OK button of the Open File dialog, the audio should begin playing.

 

After you’ve tested to be sure it works properly, close the user control test container, return to code view and remove (or comment) the lines that play the audio.

 

Close Song

 

I’ve already shown you the code that closes a song that has previously been opened.  This command was sent to mciSendString() prior to opening the song in LoadSong.  We will use this command in the CloseSong procedure.

 

  1. Open the project in Visual Studio and open the control in code view.
  2. Navigate to the end of the CloseSong procedure and type the following lines of code:

 

        ' === Audio Code ===

        mciSendString("close " & strAlias, Nothing, 0, 0)

 

  1. Save Your Work.
  2. We will not concern ourselves with this procedure again until we build the main application. 

 

Play Song

 

As we’ve already seen the command to play a song, there isn’t much to discuss here other than that it is always proper coding practice to place the mciSendString call inside of a Try…Catch block.

 

  1. Add the following lines of code to the end of the PlaySong procedure inside of the If block:

 

            Try

                If mciSendString("play " & strAlias, "", 0, 0) <> 0 Then

                    MessageBox.Show("Error Playing Song...")

                    Application.Exit()

                End If

            Catch ex As Exception

                MessageBox.Show("Error Playing Song.")

                Application.Exit()

            End Try

 

  1. Save Your Work.
  2. Test in the debugger.  After loading the song, activate the play button and the audio should play.

 

One thing that you will notice is that after the song plays and you click Play again, nothing appears to happen.

 

Normally, there is an event that you would look for that tells you when the file has finished playing.  This event no longer works in MCI with operating systems after Windows XP.

 

Instead, later in this chapter, we will obtain the song length information and use a Timer object to know when the song has finished and set the buttons accordingly.

 

In most cases, you will want the play head of the player to start at the beginning of the song but there are some instances where you might wish to start a song at some other point.  For example, if there is a large amount of silence at the beginning of the file, you may want to start the song at one second into the file which is the standard silence for CD cuts.  In this instance, the play command will look like this:

 

Play Song From 1000

 

The above command plays the file referred to by the alias Song starting from 1000 milliseconds (1 second) into the file.  The following command plays a song until its 30 second location:

 

Play Song To 30000

 

The following command plays a song from 1 second into the file and ends 30 seconds into the file, playing for exactly 29 seconds:

 

Play Song From 1000 To 30000

 

Real_World Application of this?  In Radio, it is called cue-points.  Radio software these days can detect the when the audio begins and when it fades inside an audio file.  Back when I was a professional in radio in the 1980s, we used to “cue up” a vinyl record.  We will not get as sophisticated as that in this series.  However, if you listen to the beginning and ending of the song then note when the audio begins and when it ends, you can save that information to an XML file so that you can have your application perform cross-fading.  In the days of vinyl records and magnetic tapes, the radio station (usually the Program Director) would type this information on a label which was physically placed on the record or tape for the announcer to see.

 

 

Stop Song

 

To stop audio that is currently playing, you issue the stop command but it does not close the audio file.  It simply stops the audio from playing.  The interesting thing is that the stop command actually acts like a pause command, in which it stops the audio and leaves the audio play head at the last place it was.  To make it function like a real stop command, we then issue a seek command and tell it to go to position 0 (remember, positions by default are in milliseconds).

 

  1. Add the following code to the StopSong procedure at the end of the procedure but still in the IF block:

 

            ' === audio Code ===

            mciSendString("stop " & strAlias, "", 0, 0)

            'Seek to position 0

            mciSendString("seek " & strAlias & " to 0", "", 0, 0)

 

  1. Save Your Work.
  2. Test Your Work.

 

If you were using the cue points method I suggested above, the seek point would be where the audio starts in the song.  For example, the audio starts at 0.2 seconds into the file.  The seek command would be:

 

Seek Song to 200

 

We will play with this a little before the end of the chapter.

 

Pause Song

 

You can probably guess how to create the pause and resume functionality based on what I said in the Stop Song section.  You would be correct.  You simply use the stop command without using the seek command afterwards.  You would then send a play command to resume the audio playback.

 

  1. There is an IF statement in the PauseSong procedure.  Copy the following line of code at the end of the IF portion of the IF block to perform the pause functionality:

 

            ' == Audio Code ==

            mciSendString("stop " & strAlias, "", 0, 0)

 

  1. Type the following line of code at the end of the Else block to perform the resume functionality:

 

            ' == Audio code ==

            mciSendString("play " & strAlias, "", 0, 0)

 

  1. Save Your Work.
  2. Test Your Work.

 

If all you wanted from this chapter was how to load, play, pause and stop audio files; you are not interested in distributing this application to other people; are not concerned about obtaining and using song information such as artist, title and song length and are not interested in learning how to control the volume of the audio or allowing the user to place the play head as the song plays, feel free to skip ahead to Section 3 where we build the application that uses this control.

 

However, if you want to build a professional application that has some pretty sophisticated features, keep reading on.

 

 

Part 2, Section 2 – Intermediate Audio Playback Functionality

 

In this section, we are going to obtain and display information about the song as well as some intermediate features such as controlling the volume of the play back deck and adding tracking features to the deck to allow the user to specify where the play back head goes.

 

 

Obtaining & Displaying Song Playback Status

 

To obtain the song playback status information (mode), we use the status command of the mciSendString function.  The command is as follows:

 

Status Song mode

 

Unlike the playback commands we used in section 1, this command requires the name of a buffer to place the status information into.  So, we pass mciSendString() the name of a string variable instead of Null as the second parameter.  The VB call looks like this:

 

mciSendStatus(“status Song mode”,strMode,128,0)

 

The first parameter is the status command.  The second parameter is the name of the string variable to place the information into.  This must be a space-filled variable.  The third parameter is the length of the buffer and the fourth variable is a call-back function.  We place a zero in this parameter if there is no callback function.

 

To implement this command, we will use a timer control that is already part of the project named tmStatus.

 

  1. Open BGMCI_Deck.vb in code view.
  2. Create a Public sub procedure named DisplayStatus().
  3. Create a Tick event handler for tmStatus.
  4. Type the following line of code inside of the tmStatus event handler:

 

        DisplayStatus()

 

  1. Add the following code to DisplayStatus():

 

        Dim strMode As String = Space(128)

        mciSendString("status " & strAlias & " mode", strMode, 128, 0)

        lblSongStatus.Text = strMode.Trim

 

        Select Case Microsoft.VisualBasic.Left(strMode.Trim(), 7)

            Case "stopped"

                If btnPause.Enabled Or btnStop.Enabled = False Then

                    lblSongStatus.Text = "Stopped."

                    StopSong()

                    Exit Sub

                Else

                    lblSongStatus.Text = "Paused."

                End If

            Case "playing"

                lblSongStatus.Text = "Playing."

            Case Else

                lblSongStatus.Text = "Loaded..."

        End Select

 

  1. Add the following code to the bottom of PlaySong(), inside of the IF block:

 

            ' == Song Status ==

            tmStatus.Enabled = True

            tmStatus.Start()

 

 

 

  1. Add the following code to the bottom of the IF block in PauseSong:

 

           ' == Song Status ==

            tmStatus.Enabled = False

            tmStatus.Stop()

 

  1. Add the following code to the bottom of the Else block:

 

            ' == Song Status ==

            tmStatus.Enabled = True

            tmStatus.Start()

 

  1. Add the following code to the bottom of StopSong():

 

            ' == Song Status ==

            tmStatus.Enabled = False

            tmStatus.Stop()

 

  1. Save Your Work.

 

The code above does the following:

 

  1. Creates the buffer and assigns space values to it.
  2. Calls the status mode command.
  3. Displays the mode on the screen.
  4. If the mode is stopped, calls the StopSong method.
  5. If the mode is paused, displays ‘Paused.’
  6. Else, displays ‘Loaded.’.

 

This code handles when a song is at the end of the file by calling StopSong, which places the playback head at the beginning and sets the button states correctly.

Test the application to make sure it works properly and that the song can be played again after it is over.

 

 

Obtaining & Displaying Song Time Information

 

When I first began this series, I had started on this chapter first and had begun using status mode command to retrieve the song length.  It worked very well in Windows XP but when I was about to finish the work, I tested it on Vista and was horrified by the problems I discovered and the fact that status is useless for anything except playback mode.

 

So, I had to find other ways to obtain and use song information.  In 2004, I developed two modules in VB for getting the header and ID3 tag information from MP3 files.  It was originally written in VB6 and I converted it to VB.NET.

 

It wasn’t until I had the Blind Geeks moderator, Jeff Durham, go through this chapter that we discovered that the code did not work in all instances.  It worked great for MP3 files that had a constant bit rate (CBR) but didn’t work for variable bit rate (VBR).  So, we had to abandon the use of this code.

 

Instead of spending the time required to modify the code I had written, I found an open source library named UltraID3Lib.  I’ve included this library in the start project downloaded from the web site.  We are going to use this library to obtain MP3 Song information.

 

  1. Open the BGMCI_Deck project.
  2. Add a Reference to the file named UltraID3Lib.dll.
  3. Go to the very top og BGMCI_Deck.vb before the class definition and type the following line of code:

 

Imports ID3Tag = HundredMilesSoftware.UltraID3Lib

 

 

  1. At the top of BGMCI_Deck.vb, add the following declarations to the class:

 

    ' === Song Time Info

    Private intRemaining = 0 'MilliSeconds Remaining

    Private intSongLength = 0

    Private MP3ID3Tag As ID3Tag.UltraID3

 

  1. Create a public function named GetSongLength that returns a string value.
  2. Add the following lines of code to GetSongLength():

 

        If MP3ID3Tag.FirstMPEGFrameInfo.VBRInfo.WasFound Then

            Return MP3ID3Tag.FirstMPEGFrameInfo.Duration.TotalSeconds * 1000

        Else

            Return MP3ID3Tag.Duration.TotalSeconds * 1000

        End If

 

  1. Create a private function named GetTimeFormat that returns a string value and accepts a integer parameter named intMilliseconds.
  2. Add the following code to GetTimeFormat():

 

       Dim retVal As String = ""

        Dim intSeconds As Integer = 0

        Dim intMinutes As Integer = 0

        Dim intHours As Integer = 0

        Dim intMilli As Integer = 0

 

        intHours = (intMilliseconds / 1000) / 60 / 24 ' To get Hours

        intMinutes = (intMilliseconds / 1000) / 60 'To get Minutes

        intSeconds = Int(Str(intMilliseconds / 1000).Trim)

        Dim iSecs As Integer = intSeconds Mod 60

        Dim iMins As Integer = Int(Str(intSeconds / 60).Trim)

        retVal = IIf(iMins < 10, " " & iMins, iMins) & ":" & IIf(iSecs < 10, "0" & iSecs, iSecs)

        Return Microsoft.VisualBasic.Left(retVal, 5)

 

 

  1. Navigate to LoadSong and type the following code at the end of the IF block (before the Else):

 

            ' === Song Time Info ===

            MP3ID3Tag = New ID3Tag.UltraID3

            MP3ID3Tag.Read(strFile)

 

In the code above, we instantiate a new MP3ID3Tag object which will hold all of the information about the MP3 file such as bit rate, frequency, duration, genre, etc.  We then open the MP3 file and load the information into MP3ID3Tag.

 

  1. At the end of LoadSong, add the following code:

 

        ' === Song Time Info ===

        Dim strLength As String = Space(128)

        strLength = GetSongLength()

        intSongLength = Int(strLength)

        intRemaining = intSongLength

 

        ' display initial values

        lblLength.Text = GetTimeFormat(strLength).Trim

        lblPosition.Text = lblLength.Text

 

In the code above, we create a string buffer and assign it value of the space character (“ “), which is how we prepare it to use with the mp3Info module.

 

We then call the method GetSongLength() and assign the return value to strLength.  We convert this value to type integer and assign it to int variables we will use as counters.

 

Now, we display these values. 

 

  1. Add the following code to the bottom of PlaySong, at the bottom of the IF block.

 

            ' == Song Time Info ===

            tmRemaining.Enabled = True

            tmRemaining.Start()

 

  1. Add the following code to the bottom of the IF block in PauseSong():

 

            ' === Song Time Info ===

            tmRemaining.Stop()

            tmRemaining.Enabled = False

  1. Add the following lines of code to the bottom of the Else block:

 

            ' === Song Time Info ===

            tmRemaining.Start()

            tmRemaining.Enabled = True

 

  1. Add the following lines of code to the bottom of the IF block in StopSong():

 

            ' === Song Time Info ===

            tmRemaining.Stop()

            tmRemaining.Enabled = False

            intRemaining = intSongLength

            lblPosition.Text = GetTimeFormat(intRemaining)

 

 

  1. Create a Tick event handler for the tmRemaining Timer control.
  2. Add the following code to the Tick event handler you just added:

 

        ' === Song Time Info ===

        If intRemaining > 0 Then

            intRemaining -= 1000

            lblPosition.Text = GetTimeFormat(intRemaining)

        Else

            intRemaining = 0

            lblPosition.Text = " 0:00"

        End If

  1. Save Your Work.

 

 

 

Obtaining & Displaying Song ID3 Tag Information

 

 

  1. Add a private sub procedure named DisplayID3TagInfo that accepts a string parameter named strFile.
  2. Add the following code to DisplayID3TagInfo:

 

      lblSongInfo.Text = MP3ID3Tag.Artist & " --- " & MP3ID3Tag.Title

      lblLength.Text = GetTimeFormat(GetSongLength()).Trim

          intSongLength = Int(GetSongLength())

 

  1. Add the following code to the end of LoadSong():

 

        ' === Song ID3 Tag ===

        DisplayID3TagInfo(strFile)

 

  1. Save Your Work.

 

 

 

Controlling The Volume Using A Scrollbar

 

The TAB key does work in this application and the TAB key does land on the scroll bar for the volume.  You must use the left/right arrows to change the value of the volume scroll bar, not the up/down keys.  It is possible that JAWS will not speak when you land on the volume scroll bar.  I combat this in our main application that we will build later in this chapter by creating a menu strip then placing a menu item for Volume on the menu strip, assigning a shortcut (F7) to the volume menu item then setting the volume scroll bar focus in the click event handler for the menu item.  I will demonstrate this before finishing this section.

 

Note:    The volume scroll bar is verticle.  Due to this, the keys used to adjust volume are reversed.  The LEFT arrow key turns the volume high and the RIGHT arrow key lowers the volume.

 

Now, let’s make the volume scroll bar work.

 

  1. Add a ValueChanged event handler to scrVolume.
  2. Add the following code to the event handler:

 

        Dim intValue As UInteger = IIf(scrVolume.Value < 0, scrVolume.Value * -1, 0)

        If intValue = 100 Then

            intValue = 0

        End If

        If intValue > 0 Then

            mciSendString("setaudio " & strAlias & " volume to " & Str(intValue).Trim, Nothing, 255, 0)

        Else

            mciSendString("setaudio " & strAlias & " volume to 0", Nothing, 255, 0)

        End If

  1. Save Your Work.
  2. Compile and Test.

 

 

If JAWS does not speak when it lands on the volume scroll bar, do the following:

 

  1. Create a MenuStrip object.
  2. Create a Shortcuts menu item.
  3. Under shortcuts, create a menu item for Volume and assign it a shortcut key of F7.
  4. Create a click event handler for the volume menu item.
  5. Add the following line of code to the click event handler just created:

 

scrVolume.Focus()

 

  1. Save Your Work.
  2. Test.

 

Before going on to Part 2 of this chapter, you will need to delete the menu strip so it does not conflict with the menu strip that will be built in Part 2.

 

 

Tracking & Setting The Playhead Using The Click Event of A Scrollbar

 

In professional audio software, the user is given the ability to track and set the play head’s position.  There are better looking controls for doing this but the goal in this series is to create an accessible application that has powerful features so we will use the horizontal scroll bar control for getting and setting the play head’s position.

 

When the song loads, we will set the minimum, maximum and value properties of the scroll bar.  While the song is playing, we will adjust the value and when the song stops, we will reset the value to zero.

 

  1. Add the following code to the bottom of LoadSong():

 

        ' === Song Tracking ===

        scrSongProgress.Minimum = 0

        scrSongProgress.Value = 0

        scrSongProgress.Maximum = intSongLength

 

  1. Add the following code to the bottom of tmRemaining.Tick():

 

        ' === Song Tracking ===

        If scrSongProgress.Value < scrSongProgress.Maximum Then

            Try

                scrSongProgress.Value += 1000

            Catch ex As Exception

                scrSongProgress.Value = scrSongProgress.Maximum

            End Try

        Else

            scrSongProgress.Value = scrSongProgress.Maximum

        End If

 

 

Add the following code to StopSong() (at the bottom of the IF block):

 

            '=== Song Tracking ===

            scrSongProgress.Value = 0

 

Now, to allow the user to specify the position of the play head, we use the scroll event handler of scrSongProgress.

 

Add the following code to this event handler after creating it:

 

      ' === Set Song Position ===

        If InStr(lblSongStatus.Text, "Play") > -1 Then

            mciSendString("play " & strAlias & " from " & scrSongProgress.Value, "", 0, 0)

            intRemaining = intSongLength - scrSongProgress.Value

        Else

            scrSongProgress.Value = 0

            intRemaining = intSongLength

            scrSongProgress.Refresh()

 

        End If

 

For the most part, we are finished with the user control piece.

 

We now need to prepare it to be used from an application.

 

  1. Switch the project to Release mode.  You can do this by navigating to the project properties then to the compile window then select release under Configuration.
  2. Build the project.

 

In the next section, we will use the DLL for this control which we just built (located in bin\release folder) in Release mode.

 

 

Part 3 – Putting It All Together

 

The first thing we will do is build the main application project that will serve as our DJ application.

 

  1. Open Visual Studio 2010.
  2. Create a New Windows Forms Visual Basic project named BJMCI_DJ.
  3. When the project displays, rename Form1.vb to frmMain.vb.
  4. When prompted to rename all references to Form1, select Yes.
  5. Set the properties for frmMain to the following:

 

Property Name

Value

Size

1048, 488

BackColor

Navy

ForeColor

White

Text

Blind Geeks Basic Audio Programming MCI DJ System

 

 

Addng The  BGMCI_Deck Control

 

 

  1. In Solution Explorer, activate the context menu on the solution.
  2. Select Add -> Existing Project.
  3. Navigate to  BGMCI_Deck.vbproj and select it.
  4. Activate the Open button.
  5. The BGMCI_Deck project will be added to your solution.
  6. Open the Toolbox.
  7. You will see a category named BGMCI_Deck components.  Open it.
  8. Place four instances of the BGMCI_Deck component on your form.
  9. Name the controls Deck1, Deck2, Deck3 and Deck4.
  10. Set the Deck locations as follows:

 

Deck:

Location:

Deck1

16, 61

Deck2

532, 61

Deck3

16, 251

Deck4

532, 251

 

  1. Set the following properties for each deck:

 

Property

Value

BackColor

Control

ForeColor

ControlText

Size

484, 185

 

 

Now, your solution should compile and run but if you attempt to load audio files into two of the decks, the decks will only play the last audio file loaded.  Ooops.

 

To resolve this, we need to add some code.

 

We need to add a public write-only property to BGMCI_Deck.vb.  Yje property will look like this:

 

    Public WriteOnly Property DeckTitle() As String

        Set(ByVal value As String)

            lblDeck.Text = value

        End Set

    End Property

 

 

This property sets the label text of the deck label.

 

Now, add the following code to the Load Event handler of BGMCI_DJ.frmMain.vb:

 

        Deck1.songAlias = "Deck1"

        Deck1.deckTitle = "Deck #1"

        Deck2.songAlias = "Deck2"

        Deck2.deckTitle = "Deck #2"

        Deck3.songAlias = "Deck3"

        Deck3.deckTitle = "Deck #3"

        Deck4.songAlias = "Deck4"

        Deck4.deckTitle = "Deck #4"

 

 

Save your work.

 

The code above set the default values for the properties DeckTitle and SongLias.

 

Now, if you were to compile and run the program, the problem mentioned above is resolved.

 

Building The Menu & Shortcuts

 

A trick I picked up in 2004 when in MCSD training was the use of menu items to assign shortcut keys to buttons and other controls, outside of the Alt+ key strokes.

 

Basically, you will set a menu item group then assign shortcuts to menu items underneath for whatever control or function/procedure you want executed.  Then, before releasing the application, you set the visible property for that menu item group to false.

 

For the functionality we want added to our applications, we don’t need to set the menu item group visible property to false.  But, this is an technique you can use in your applications.

 

We will be working with frmMain.vb in the designer now.

 

 

  1. Create a menu strip control named MenuStrip1.
  2. Add Menu Strip Items for the following:

 

Menu Strip Item Name

Text Property

mnuFile

&File

mnuDeck1

Deck &1

mnuDeck2

Deck &2

mnuDeck3

Deck &3

mnuDeck4

Deck &4

mnuMixer

&Mixer

 

 

  1. Set the visible property of mnuMixer to False.  We will not use this until the Advanced section at the end of the chapter.
  2. Add the following menu items to mnuDeck1, mnuDeck2, mnuDeck3 and mnuDeck4.  Substitute the # symbol in the menu item name for the Deck number.

 

Menu Item Name

Text Property

Shortcut

Deck1

Shortcut

Deck2

Shortcut

Deck3

Shortcut

Deck4

mnuLoadDeck#

Load

F2

Shift+F2

Cntrl+F2

Alt+F2

mnuPlayDeck#

Play

F3

Shift+F3

Cntrl+F3

Alt+F3

mnuPauseDeck#

Pause

F4

Shift+F4

Cntrl+F4

Alt+F4

mnuStopDeck#

Stop

F5

Shift+F5

Cntrl+F5

Alt+F5

mnuEjectDeck#

Eject

F6

Shift+F6

Cntrl+F6

Alt+F6

 

 

  1. Set the ShowShortcutKey property to True for all deck menu items.
  2. Set the frmMain form property for MainMenuStrip to MenuStrip1.
  3. Add a click event handler for the load menu items for each deck.
  4. Add the following code to the new event handlers, substituting the deck number for the # symbol.

 

    Private Sub mnuLoadDeck#_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuLoadDeck#.Click

        Deck#.LoadSong()

    End Sub

 

  1. Add a click event handler for the play menu items for each deck.
  2. Add the following code to the new event handlers, substituting the deck number for the # symbol.

 

    Private Sub mnuPlayDeck#_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuPlayDeck#.Click

        Deck#.PlaySong()

    End Sub

 

  1. Add a click event handler for the pause menu items for each deck.
  2. Add the following code to the new event handlers, substituting the deck number for the # symbol.

 

    Private Sub mnuPauseDeck#_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuPauseDeck#.Click

        Deck#.PauseSong()

    End Sub

 

  1. Add a click event handler for the stop menu items for each deck.
  2. Add the following code to the new event handlers, substituting the deck number for the # symbol.

 

    Private Sub mnuStopDeck#_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuStopDeck#.Click

        Deck#.StopSong()

    End Sub

 

  1. Add a click event handler for the eject menu items for each deck.
  2. Add the following code to the new event handlers, substituting the deck number for the # symbol.

 

    Private Sub mnuEjectDeck#_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuEjectDeck#.Click

        Deck#.CloseSong()

    End Sub

 

  1. Add a menu item to the File menu named mnuExit.
  2. Create a click event handler for mnuExit.
  3. Add the following code to the mnuExit click event handler.

 

        ' Close all open playback devices

        Deck1.CloseSong()

        Deck2.CloseSong()

        Deck3.CloseSong()

        Deck4.CloseSong()

        Application.Exit()

 

  1. Save your work.
  2. Compile and Test.

 

 

We’ve come to the end of Chapter 2 of the Basic Audio Programming course.  I will be adding an addendum to this chapter later, as time permits which will include the following advanced topics:

 

  1. Adding A Mixer.
  2. Getting/Setting The Audio WaveOut Device.
  3. Getting/Setting Cue Points with MCI & XML.
  4. On Your Own:  Setting Audio Balance.
  5. On Your Own:  Fade On Next Song.
  6. On Your Own:   Automating Decks.

 

In Chapter 3 of the Basic Audio course, we will be learning basic audio programming with Windows Media.  Chapter 3 won’t be as comprehensive as the last two chapters have been.