Coding a Euchre Game, Part 6: Pump up the volume (Matt Gertz)

Anthony D. Green [MSFT]

Coding a Euchre Game, Part 6:  Pump up the volume

Ever since Commodore first introduced the SID chip to their line of PCs way back in the earlier 80’s, sound has been an important experience in any computer game.  What’s particularly exciting to me is that audio has caught up to graphics as far as game relevance goes – I can’t imagine Planescape: Torment having the same sort of immersive experience without Mark Morgan’s haunting, searching themes, or Icewind Dale messaging the loneliness and weariness of travelling across the ice plains without Jeremy Soules’ amazing harmonies.

Alas, I can’t pretend that I will be walking you through composing a soundtrack for the Euchre game – you’re on your own for that one (I can’t even play the piano without looking at my fingers – which is actually the same problem I have with typing, come to think of it).  Nevertheless, there’s a lot you can do with sound in Visual Basic 2005.  I’m going to cover two areas:  sound effects and text-to-speech.

Sound Effects

Recording a sound is fairly simple, and for a card game where the action is fast, the sounds need to be punchy and iconic.  Overall, I wanted my game to convey that same feeling I get when playing cards with real people, so I identified three types of sounds that I would miss if they weren’t in the game:

          Shuffling the deck

          Slapping a card to the table

          Some sort of reaction to a good hand or a good game – applause, for example

I dug around in my desk, came up with a microphone, and recorded the first two sounds myself using the Sound Recorder that comes with Windows.  For the reaction noise, I grabbed three different public domain sounds of applause (soft applause, loud applause, and wild applause) – of course, if I was going to sell this game, I would have licensed the sounds or recorded my own.  All of these got stored as WAV files.

If you’ve been following along with my previous posts, you’ve probably already guess that the next step is to get the sounds into the resource manager.  Right-click the project and bring up “Properties,” click the Resource tab, and use the Add Resource->Add Existing File to bring in the WAV files.  (To show the audio resources, click the first dropdown in the RM and select “Audio” – you can play any resource in there by right-clicking it and choosing the appropriate command.)

Now, let’s start using the sound.  Fortunately, playing sounds is very easy using the VB2005 My.Computer functionality:

    Private Sub PlayApplause(ByVal level As Integer)

            Select Case level

                Case 1

                    My.Computer.Audio.Play(My.Resources.SoundApplauseSoft, _

 AudioPlayMode.WaitToComplete)

                Case 2

                    My.Computer.Audio.Play(My.Resources.SoundApplauseLoud, _

 AudioPlayMode.WaitToComplete)

                Case 3

                    My.Computer.Audio.Play(My.Resources.SoundApplauseWild, _

 AudioPlayMode.WaitToComplete)

            End Select

    End Sub

 

Let’s take a  deeper look at what’s going on here.  “Play” is an overloaded method available to you in the My.Computer.Audio namespace which will play sounds immediately for you.  The first argument is either the name of the sound resource, its location on disk, or a stream; the second argument specifies how the sound plays (foreground, background, or loop in the background).  In my case, I’ve specified the audio resource directly (using the friend property automatically generated for me by the resource manager).  I’ve also elected to go with “WaitToComplete” (i.e., foreground) as my play mode, since the sounds are short and thus I’m not worried about the user having to wait a long time for them to finish.  I also don’t want to drown out the text-to-speech I’ll be implementing later, which could happen if both played at once, so I’ve preferred to block on the applause.

Using sound is more than just playing the sound, however.  For example, when I added a shuffling sound to the game, I hard-coded the sound to play three times in a row.  This ended up being an irritation – I found myself mocking the sound as it played, because the pattern was the same every time.  It was a perfectly good sound, but I came to realize that it seemed a bit predictable in its playback.  Furthermore, playing the shuffle sound three times in a row made it a long time to wait if I decided to close the game or restart it.  It took a very minor change to make all of this feel more natural to me – I simply introduced some randomness to the amount of time the cards were shuffled, and then added a message pump beween sounds to facilitate handling any events that might have occurred:

            For i = 0 To EuchreTable.GenRandomNumber(2) + 1

                My.Computer.Audio.Play(My.Resources.SoundShuffleDeck, _

 AudioPlayMode.WaitToComplete)

 

                ‘ The shuffle sound is long, and so I need to pump

          ‘ messages to see if the user closes or restarts

                ‘ the application.

                Application.DoEvents()

                If Exiting = True Then

                    Throw New EuchreException(“ExitGame”)

                End If

                If Restarting = True Then

                    Throw New EuchreException(“NewGame”)

                End If

            Next i

 

(If you’ll bear with me, I’d like to digress for a moment into the realm of randomness.  I’ve been asked many times how one generates a random number, what seeds to use, etc, etc..  For the record, here’s my preferred method for generating a random integer, which comes straight from the MSDN topic on RNGCryptoServiceProvider:

    Public Shared Function GenRandomNumber(ByVal MaxVal As Integer) _

As Integer

        ‘ Create a byte array to hold the random value.

        Dim randomNumber(0) As Byte

 

        ‘ Create a new instance of the RNGCryptoServiceProvider.

        Dim Gen As New Security.Cryptography.RNGCryptoServiceProvider()

 

        ‘ Fill the array with a random value.

        Gen.GetBytes(randomNumber)

 

        ‘ Convert the byte to an integer value to make the modulus

        ‘ operation easier.

        Dim rand As Integer = Convert.ToInt32(randomNumber(0))

 

        ‘ Return the random number mod the number

        ‘ of possibilities.  The possible values are zero-

        ‘ based.

        Return rand Mod MaxVal

    End Function

I’ve found that most other random number generators are too non-random for me, especially in a card game where the randomness will be asked for a lot.  It’s probably overkill for this app — Randomize + Rnd would have worked fine — but I don’t like multiple ways of doing things, and I know this one will always be nicely random, whatever the application.)

I then proceed to add calls to play the varying levels of applause when the user wins a hand or a game, as well as the appropriate sound every time a card as played.  My.Computer.Audio makes this all very straightforward.

Text To Speech

Now, if this was a professional-quality game, it’s unlikely that I’d be using text-to-speech in it – instead, I would hire voice actors to speak any relevant lines.  However, as neither Patrick Stewart nor James Earl Jones has been returning my calls, I’ll press on valiantly with using TTS.

The first thing you’ll need here is the Microsoft Speech SDK, which can be downloaded from http://www.microsoft.com/downloads/details.aspx?FamilyId=5E86EC97-40A7-453F-B0EE-6583171B4530&displaylang=en. You need both the SDK itself (SpeechSDK51.exe) and the redistributable package (speechsdk51msm.exe), the latter since you’ll be deploying our game to other machines later on.   Upon download completion (warning:  it’s rather a large download), go ahead & launch the downloaded EXEs to install the SDK bits to the appropriate place, and then launch the resulting setup.EXE to install them.

To actually use the TTS functionality, you’ll need to add a reference to the Microsoft Speech Object Library.  To do this, right-click on the project and choose “Add reference…”  This will bring up the Add Reference dialog (which might take some time to come up, depending on how many available references are installed on your machine).  You then click the COM tab and scroll down to the aforementioned library, select it, and click OK.  (Since this is a COM reference, an interop DLL will be generated for you and tossed into your output directory.)  You will now be able to use the SpVoice object as a type.

Once the SDK is installed, using the TTS functionality in the Euchre game is pretty easy.  The machine may have several voices on it, so I’ve given the user the ability to pick a voice for each player.  On my game options dialog (one of my few modal dialogs – I’ve attached a picture of it to this post), I’ve added three combo boxes which allow the user to select a voice for each AI player, and I just need to populate those combo box dropdowns:

            Try

                Voice = New SpVoice

            Catch ex As Exception

            End Try

            Me.LeftVoiceCombo.Enabled = False

            Me.PartnerVoiceCombo.Enabled = False

            Me.RightVoiceCombo.Enabled = False

            If Voice IsNot Nothing Then

                Dim Voices As ISpeechObjectTokens = Voice.GetVoices()

                Dim NumberOfVoices As Integer = Voices.Count

                If Voices.Count > 0 Then

                    For i As Integer = 0 To Voices.Count – 1

                        Dim s As String = Voices.Item(i).GetDescription

                        Me.LeftVoiceCombo.Items.Add(s)

                        Me.PartnerVoiceCombo.Items.Add(s)

                        Me.RightVoiceCombo.Items.Add(s)

                    Next i

                    Me.LeftVoiceCombo.Enabled = True

                    Me.PartnerVoiceCombo.Enabled = True

                    Me.RightVoiceCombo.Enabled = True

                End If

            End If

 

where Voice is a member of the options dialog class defined as follows:

    Private Voice As SpVoice = Nothing

 

Note that I’ve guarded against the case where the user has uninstalled voice capabilities by wrapping the SpVoice creation in a try-catch block.  (I won’t throw up an error or anything, since not having a voice doesn’t make the game useless – I just disable the combo boxes in such a case so that the user can’t choose a voice.)

The voice descriptions I’ve added to the combo boxes read something like “Microsoft Mary,” “Microsoft Sam,” etc., and although that’ll help the user remember which voice he/she prefers in the future, it would also be useful to let the user hear the voice when selecting it to make sure it’s the desired one.  So, here’s where we add the first code that actually says a string.  I’ll create an event handler for each dropdown to handle when the dropdown is closed (i.e., when a selection is made):

    Private Sub LeftVoiceCombo_DropDownClosed(ByVal sender As Object, _

 ByVal e As System.EventArgs) _

 Handles LeftVoiceCombo.DropDownClosed

        Speak(Me.LeftVoiceCombo.SelectedIndex)

    End Sub

 

where Speak is a private method on the options dialog class coded thusly:

    Private Sub Speak(ByVal VoiceIndex As Integer)

        If Voice IsNot Nothing Then

            Dim Voices As ISpeechObjectTokens = Voice.GetVoices()

            Voice.Voice = Voices.Item(VoiceIndex)

            Voice.Speak(My.Resources.SAY_LetsPlayEuchre)

        End If

    End Sub

 

And SAY_LetsPlayEuche is just a string reference in my resource manager which has the value “lets play euchre.”

The speech engine is actually pretty darn clever – given an English string resource, it can usually puzzle out how to say the word; thus, I didn’t even need to spell euchre as “yooker” to get the right pronunciation.  (Note that the Japanese and Chinese speech engines can also be downloaded.) It also pays attention to punctuation, so that things like commas, question marks, and exclamation marks cause the appropriate pauses or emphasis to happen during speech.  There’s even something that TTS can do that a voice actor wouldn’t be able to – it can personalize the speech.  Thus, when the player wins the game, I can have the partner congratulate him or her by using the string “great game {0}!  shall we play again?” – I just use String.Format to replace the {0} with the player’s name (there are edit boxes for each player’s name on the options dialog) before calling Voice.Speak.

Since each AI player in the game will now have a distinct voice, and since I don’t want to have to keep changing the voice all of the time to match the current player, I’ll design a EuchreSpeech class (right-click the project, choose “Add\Class…” etc.), an instance of which will be owned by each AI player.  Each EuchreSpeech instance needs an actual voice, so I need to create an SpVoice object for each one.  The code is very similar to that of the options dialog, except that I already know which voice I want:

    Public Sub SetVoice(ByVal VoiceIndex As Integer)

        Try

            Voice = New SpVoice

        Catch ex As Exception

        End Try

        If Voice IsNot Nothing Then

            Dim Voices As ISpeechObjectTokens = Voice.GetVoices()

            Dim NumberOfVoices As Integer = Voices.Count

            If Voices.Count > 0 Then

                VoiceToUse = VoiceIndex

                Voice.Voice = Voices.Item(VoiceToUse)

            Else

                Voice = Nothing

            End If

        End If

    End Sub

 

where VoiceIndex is the index of the voice I got from the options dialog for this player.  (I could have had the options dialog pass voice references directly to the EuchreVoice objects, but it only has one voice shared by all of the combo boxes.  I suppose I could have created three actual voices, but I didn’t want to get carried away with voice creation or have some sort of dual-ownership of a voice, and iterating the voices is fairly cheap anyway.) Note that I don’t call this code in the constructor for the player.  This is because I don’t destroy the EuchrePlayer/EuchreSpeech objects between the games (which would be wasteful), but I do give the user the option to change voices and other options between games, and so this code needs to be separate from the constructor. 

Since the voice is already known for each player, the code which causes a player to say something is very simple – here’s the functions called when an AI player wishes to pass:

    ‘ Called by the BidFirstRound in EuchreTable for a given player   

    Public Sub SpeakPass(ByVal seat As EuchrePlayer.Seats)

        If seat <> EuchrePlayer.Seats.Player And SoundOn = True Then

            ‘ EVoice is the EuchreSpeech member of EuchrePlayer

            Players(seat).ESpeech.SayPass()

        End If

    End Sub

 

    ‘ Members of EuchreSpeech (the ESpeech from above)  

    Public Sub SayPass()

        Say(My.Resources.SAY_Pass)

    End Sub

    Private Sub Say(ByVal s As String)

        If Voice IsNot Nothing Then

           ‘ Voice is the SpVoice member of EuchreSpeech

            Voice.Speak(s)

        End If

    End Sub

 

And, in the string resource manager, SAY_Pass = “I pass.”  It’s just that easy!

Like I said at the beginning of this session, you may or may not decide to use TTS in a game, but it’s certainly useful in other applications where accessibility is an issue, and so it’s a handy tool to know how to use.  Next time, I’m going to discuss game logic. See you then!

–Matt–*

EuchreOptions.jpg

0 comments

Leave a comment

Feedback usabilla icon