Chapter 15. Playing MIDI Music

Jaws is probably my favorite movie of all time, and few people who have seen the movie will ever forget the nerve-racking music used to communicate that the shark was about to strike. This is just one example of how simple music was able to establish a very specific mood. Video games are capable of using music in the same way. In fact, seeing as how movies are generally capable of being more expressive visually, it becomes more important for games to take advantage of music as a means of reinforcing a mood or enhancing the reality of a virtual environment. MIDI music is one of the best ways to incorporate music into games, and this hour shows you exactly how to play MIDI music with a relatively small amount of code.

In this hour, you’ll learn:

  • About the Media Control Interface (MCI) and how it relates to MIDI music

  • How to use the MCI to play MIDI music

  • How to add MIDI music to a game

Understanding the Windows Media Control Interface

In the previous hour, you found out that the Win32 API provides a single function, PlaySound(), that makes it possible to play wave sounds with very little effort. Unfortunately, there is no such function available for playing MIDI music. The Win32 API groups MIDI music with other kinds of multimedia objects such as video clips. In order to play a MIDI song in a Windows program, you have to work with a special multimedia API known as the Media Control Interface, or MCI. The Media Control Interface is a high-level API that allows you to control multimedia devices such as a MIDI synthesizer or a video player. Because the MCI is so versatile in supporting a lot of different multimedia objects and devices, the MCI API is somewhat complex. The good news is that I’m going to carefully steer you through the MCI API and highlight only the portions you need in order to play MIDI music in games.

The idea behind the MCI is that you communicate with a multimedia device by sending it commands. A command is simply an instruction to carry out a particular action such as playing, pausing, or stopping a song or video clip. Two kinds of commands are actually supported by the MCI: command messages and command strings. Both approaches work equally well, but for the sake of simplicity, I opted to use command messages to communicate with the MIDI synthesizer device throughout this hour. A command message basically allows you to have a conversation with a multimedia device by sending it a message that tells it what to do. So, if you want to play a song, you would send a command message that includes the name of the song file along with the play command. I’m simplifying things a little here, but hopefully you get the idea.

Keep in mind that the Win32 API also supports a low-level programming interface that allows you to dig really deep into the details of multimedia programming. For example, you could use low-level multimedia functions to develop your own music editing software. However, the low-level multimedia API is extremely complex, so I don’t recommend working with it until you have considerable experience with multimedia programming. Fortunately, you can accomplish everything you need for games without having to resort to low-level multimedia programming. Just use the MCI.

Using the MCI to Play MIDI Music

The device used to play MIDI music in a game is called the MIDI synthesizer, and its job is to take notes of music and play them aloud. The MIDI synthesizer isn’t a physical device that you plug in to your computer—it’s built in to your sound card. Just about every sound card these days includes a MIDI synthesizer, so you shouldn’t have any trouble playing MIDI music. A big part of using the MCI to play MIDI music is establishing a line of communication with the MIDI synthesizer device. For example, you must first open the device before you can issue a command such as playing a MIDI song. The next section shows you how to open the MIDI sequencer device for playing MIDI music, and subsequent sections reveal how to play MIDI songs.

Opening the MIDI Device

When working with MIDI devices using the MCI, it’s important to understand that a device is referenced using a unique device identifier. So, when you open a MIDI device to play MIDI music, you’ll want to keep track of the device ID that is returned when you first open the device. This ID is your ticket for communicating with the device from then on. You can think of the device ID as the phone number you need in order to call the device and tell it what you want it to do.

To perform any MIDI tasks with the MCI, you must get comfortable with the mciSendCommand() function, which sends a command string to a MIDI device. This function is described in the Win32 API as follows:

MCIERROR mciSendCommand(MCIDEVICEID IDDevice, UINT uiMsg, DWORD dwCommand,
  DWORD_PTR pdwParam);

The mciSendCommand() function is used to send command messages to a MIDI device. The arguments to this function vary depending on what kind of message you’re sending, so we’ll analyze them on a message-by-message basis. Before you can send a message to play a MIDI song, you must first open a MIDI device by sending an open message using the mciSendCommand() function. The open message is called MCI_OPEN, and it requires the use of a special data structure called MCI_OPEN_PARMS, which is defined as follows:

typedef struct {
  DWORD_PTR    dwCallback;
  MCIDEVICEID  wDeviceID;
  LPCSTR       lpstrDeviceType;
  LPCSTR       lpstrElementName;
  LPCSTR       lpstrAlias;
} MCI_OPEN_PARMS;

The only members of this structure that we’re interested in are the middle three: wDeviceID, lpstrDeviceType, and lpstrElementName. The wDeviceID member stores the device ID for the MIDI sequencer, and in our case will be used to retrieve this ID. In other words, the wDeviceID member gets filled in with the device ID when you open the device. The other two fields have to be specified when you open the device. The lpstrDeviceType field must be set to the string “sequencer” to indicate that you want to open the MIDI sequencer. The lpstrElementName field must contain the name of the MIDI file that you want to play. This reveals an interesting aspect of the MCI—you must specify a multimedia object when you open a device. In other words, you don’t just open a device and then decide what multimedia file you want to play.

Although I could go on and on about how interesting the mciSendCommand() function is, I’d rather just show you the code for opening the MIDI sequencer device, so here goes:

UINT uiMIDIPlayerID;
MCI_OPEN_PARMS mciOpenParms;
mciOpenParms.lpstrDeviceType = "sequencer";
mciOpenParms.lpstrElementName = "Music.mid";
if (mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
  (DWORD_PTR)&mciOpenParms) == 0)
  // Get the ID for the MIDI player
  uiMIDIPlayerID = mciOpenParms.wDeviceID;

Hopefully this code isn’t too terribly shocking considering that I just primed you with a brief explanation of the MCI_OPEN_PARMS data structure. This code initializes the two important fields of this structure (lpstrDeviceType and lpstrElementName), and then passes the entire structure as the last argument to the mciSendCommand() function. Notice that the MIDI music file in this example is named Music.mid. The first argument to the mciSendCommand() function is set to zero because you don’t yet know the ID of the device. The second argument, MCI_OPEN, is the message you’re sending to the MIDI sequencer. Finally, the third argument identifies the fields of the MCI_OPEN_PARMS data structure that are to be taken into consideration for the message.

Note

Opening the MIDI Device

You might have noticed that the MCI approach to playing MIDI songs involves opening and playing a MIDI file, as opposed to using MIDI songs as resources. Unfortunately, there is no good workaround for this problem, so you won’t be able to compile .MID music files into your executable game files as you’ve done with bitmaps and wave sounds. This means that you’ll have to provide separate .MID files with your games when you distribute the games.

If all goes well, the mciSendCommand() function returns 0 to indicate that the MIDI sequencer was successfully opened. You can then store away the device ID since it is now available in the wDeviceID field of the MCI_OPEN_PARMS data structure.

Playing a MIDI Song

Now that you have the MIDI sequencer opened for a specific MIDI song, you’re ready to issue a play command and start the song playing. This is accomplished with the same mciSendCommand() function, but this time you provide a different command. In this case, the command is MCI_PLAY, and its required arguments are somewhat simpler than those for the MCI_OPEN command. However, the MCI_PLAY command does involve another data structure, MCI_PLAY_PARMS, although you can leave it uninitialized when you’re just playing a song from start to finish.

Note

Playing a MIDI Song

The MCI_PLAY_PARMS data structure comes into play whenever you want a finer degree of control over how a MIDI song is played, such as selecting a different starting and ending point for the song.

Unlike the MCI_OPEN command, which doesn’t require a device ID, the MCI_PLAY command requires a device ID in order to successfully play a MIDI song. Following is code to play a song based on the previous code that opened the MIDI sequencer for the Music.mid song:

MCI_PLAY_PARMS mciPlayParms;
mciSendCommand(uiMIDIPlayerID, MCI_PLAY, 0, (DWORD_PTR)&mciPlayParms);

You’re probably thinking that there must be a catch because this code looks way too simple. Unless you’re wanting to do something tricky like skip the first three seconds of a MIDI song, this is all that’s required to play a song from start to finish using the MCI. Notice that the device ID is provided in the first argument to mciSendCommand(), while the MCI_PLAY command is provided as the second argument. The third argument isn’t necessary, so you simply pass 0. And finally, the fourth argument isn’t really necessary either, but you must still pass a legitimate structure, even if it’s just left uninitialized.

Pausing a MIDI Song

There are situations in which you will definitely want to pause a MIDI song while it’s being played. For example, you don’t want the music to continue playing when the main game window is deactivated. So, you need a way to pause a MIDI song. This is accomplished with the MCI_PAUSE command, which is surprisingly simple to use. Following is an example of pausing a MIDI song using the MCI_PAUSE command and the mciSendCommand() function:

mciSendCommand(uiMIDIPlayerID, MCI_PAUSE, 0, NULL);

Notice that the only two arguments of significance in this code are the device ID (the first argument) and the pause message (the second argument). The remaining two arguments have no bearing on the MCI_PAUSE command, so you can pass empty values for them. In order to play a MIDI song that has been paused, you just issue another play command.

Closing the MIDI Device

Of course, all good things must come to an end, and eventually you’ll want to close the MIDI sequencer device because you’re finished with it or because you want to stop playing a song. The MCI_CLOSE command is used to close a MIDI device with the mciSendCommand() function. Following is an example of closing the MIDI sequencer by issuing an MCI_CLOSE command:

mciSendCommand(uiMIDIPlayerID, MCI_CLOSE, 0, NULL);

Similar to the MCI_PAUSE command, the only two arguments of significance in this code are the device ID (the first argument) and the close message (the second argument).

One point I haven’t clarified in regard to playing and closing devices is that it’s possible to encounter a problem when you attempt to play a MIDI song—in which case, the mciSendCommand() function will return a value other than 0 when you issue the MCI_PLAY command. In the earlier play example, I didn’t bother looking at the return value of the mciSendCommand() function. But, it’s a good idea to check and see if the song was successfully played because you should close the device if a problem occurred while playing the song. Following is revised play code that shows how to close the MIDI sequencer device if an error took place while playing the song:

MCI_PLAY_PARMS mciPlayParms;
if (mciSendCommand(uiMIDIPlayerID, MCI_PLAY, 0, (DWORD_PTR)&mciPlayParms) != 0)
{
  mciSendCommand(uiMIDIPlayerID, MCI_CLOSE, 0, NULL);
  uiMIDIPlayerID = 0;
}

This code checks the return value of the mciSendCommand() function for the play command, and then closes the device if an error occurred. Notice that the device ID is also cleared after closing the device. This helps to make sure that you don’t try to send any more commands to the device since the ID is no longer valid.

Adding MIDI Music Support to the Game Engine

Now that you’re an MCI programming expert, you’re no doubt ready to find out how to incorporate MIDI music capabilities into the game engine. You might not realize it, but you’ve already seen the majority of the MIDI code required to add music support to the game engine. It’s mainly just a matter of creating a clean user interface for opening and closing the MIDI sequencer device, as well as playing MIDI songs. Keep in mind that the entire source code for the game engine and all example programs is included on the accompanying CD-ROM.

The first step required for adding MIDI support to the game engine is to keep track of the MIDI sequencer device ID. This is easily accomplished with a new member variable, which looks like this:

UINT m_uiMIDIPlayerID;

The m_uiMIDIPlayerID member variable contains the device ID for the MIDI sequencer. Any time the ID is not 0, you will know that you have the device open and ready for playing music. If this member variable is set to 0, the device is closed. This means that you need to initialize the m_uiMIDIPlayerID member variable to 0 in the GameEngine() constructor, like this:

m_uiMIDIPlayerID = 0;

This is the only change required in the GameEngine() constructor to support MIDI music, and it simply involves setting the m_uiMIDIPlayerID member variable to 0.

I mentioned earlier that the most important requirement for the game engine is to establish an interface for carrying out MIDI music tasks. Following are three new methods in the game engine that carry out important MIDI music playback tasks:

void PlayMIDISong(LPTSTR szMIDIFileName = TEXT(""), BOOL bRestart = TRUE);
void PauseMIDISong();
void CloseMIDIPlayer();

The roles of these methods hopefully are somewhat self-explanatory in that they are used to play a MIDI song, pause a MIDI song, and close the MIDI player (sequencer). You might be wondering why there isn’t a method for opening the MIDI player. The opening of the player is actually handled within the PlayMIDISong() method. In fact, you’ll notice that the PlayMIDISong() method has a string parameter that is the filename of the MIDI file to be played. This filename is used as the basis for opening the MIDI player. It might seem strange that the MIDI filename has a default value, meaning that you don’t have to provide it if you don’t want to. Calling the PlayMIDISong() method with no filename only works if you’ve already begun playing a song and it is now paused.

The purpose for allowing you to pass an empty filename to the PlayMIDISong() method is to allow you to restart or resume the playback of a song that has already started playing. In this case, the second parameter to the method, bRestart, is used to determine how the song is played. Resuming playback would be useful if you had simply paused the music in a game, whereas restarting the playback would be useful if you were starting a new game and wanted to start the music over.

The PlayMIDISong() method is responsible for opening the MIDI player if it isn’t already open. The method first checks to see if the player isn’t yet open, and if it isn’t, an open command is issued to open the MIDI sequencer device. The device ID for the MIDI sequencer is stored in the m_uiMIDIPlayerID variable. The PlayMIDISong() method continues by restarting the song if the bRestart argument is TRUE. The MIDI song is then played by issuing a play command via MCI. If the play command fails, the MIDI sequencer device is closed.

It’s likely that you might at some point want to pause a MIDI song once it has started playing. This is accomplished with the PauseMIDISong() method, which simply issues a pause command to the MIDI player, providing the device ID isn’t set to 0.

The CloseMIDIPlayer() method is used to close the MIDI device and clear the device ID. This method first checks to make sure that the device is indeed open by checking to see if the device ID is not equal to 0. If the device is open, the CloseMIDIPlayer() method proceeds to close the device by issuing a close command, and then clearing the device ID member variable.

Building the Henway 2 Program Example

You’ve spent the better part of this hour adding MIDI music support to the game engine. By adding the code to the game engine, you’ve made it possible to put a very small burden on the code for a game. In other words, the new and improved game engine now makes it painfully easy to add MIDI music support to any game. The remainder of this hour proves my point by showing you how to add MIDI music to the Henway game that you developed back in Hour 12, “Example Game: Henway.” In fact, the Henway 2 game that you’re about to develop not only includes MIDI music, but it also uses wave sounds to incorporate some sound effects into the game. The MIDI music added to the game simply serves as background music to make the game a little more interesting. You’ll no doubt find the game to be a major improvement over its earlier version.

Writing the Program Code

The code for the audio supercharged Henway 2 game begins with the GameStart() function, which now includes a single line of code near the end that plays the MIDI song stored in the file Music.mid:

_pGame->PlayMIDISong(TEXT("Music.mid"));

There isn’t too much to say about this code because it simply passes the name of the MIDI song file to the PlayMIDISong() method in the game engine.

Just as the GameStart() function opens the MIDI player by starting the playback of a MIDI song, the GameEnd() function is responsible for cleaning up by closing the MIDI player. The following line of code appears near the beginning of the GameStart() function:

_pGame->CloseMIDIPlayer();

The GameEnd() function performs the necessary MIDI music cleanup by calling the CloseMIDIPlayer() method to close the MIDI player.

Earlier in the hour, I mentioned how it wouldn’t be good for a MIDI song to continue playing if the game window is deactivated. In order to keep this from happening, it’s important to pause the MIDI song when a window deactivation occurs, and then play it again when the window regains activation. Listing 15.1 contains the code for the GameActivate() and GameDeactivate() functions, which are responsible for carrying out these tasks.

Example 15.1. The GameActivate() and GameDeactivate() Functions Are Used to Pause and Play the MIDI Song Based on the State of the Game Window

 1: void GameActivate(HWND hWindow)
 2: {
 3:   // Capture the joystick
 4:   _pGame->CaptureJoystick();
 5:
 6:   // Resume the background music
 7:   _pGame->PlayMIDISong(TEXT(""), FALSE);
 8: }
 9:
10: void GameDeactivate(HWND hWindow)
11: {
12:   // Release the joystick
13:   _pGame->ReleaseJoystick();
14:
15:   // Pause the background music
16:   _pGame->PauseMIDISong();
17: }

The GameActivate() function is responsible for continuing the playback of a paused MIDI song, and it does so by calling the PlayMIDISong() method and specifying FALSE as the second argument (line 7). If you recall from earlier in the hour, the second argument determines whether the MIDI song is rewound before it is played. So, passing FALSE indicates that the song shouldn’t be rewound, which has the effect of continuing playback from the previously paused position. The GameDeactivate() function performs an opposite task by pausing the MIDI song with a call to the PauseMIDISong() method (line 16).

The GameCycle() function doesn’t have any MIDI-related code, but it does include some new sound effects code. More specifically, the GameCycle() function now plays car horn sounds at random intervals to help add some realism to the game (see Listing 15.2).

Example 15.2. The GameCycle() Function Randomly Plays Car Horn Sounds

 1: void GameCycle()
 2: {
 3:   if (!_bGameOver)
 4:   {
 5:     // Play a random car sound randomly
 6:     if (rand() % 100 == 0)
 7:       if (rand() % 2 == 0)
 8:         PlaySound((LPCSTR)IDW_CARHORN1, _hInstance, SND_ASYNC |
 9:           SND_RESOURCE);
10:       else
11:         PlaySound((LPCSTR)IDW_CARHORN2, _hInstance, SND_ASYNC |
12:           SND_RESOURCE);
13:
14:     // Update the sprites
15:     _pGame->UpdateSprites();
16:
17:     // Obtain a device context for repainting the game
18:     HWND  hWindow = _pGame->GetWindow();
19:     HDC   hDC = GetDC(hWindow);
20:
21:     // Paint the game to the offscreen device context
22:     GamePaint(_hOffscreenDC);
23:
24:     // Blit the offscreen bitmap to the game screen
25:     BitBlt(hDC, 0, 0, _pGame->GetWidth(), _pGame->GetHeight(),
26:       _hOffscreenDC, 0, 0, SRCCOPY);
27:
28:     // Cleanup
29:     ReleaseDC(hWindow, hDC);
30:   }
31: }

The modified GameCycle() function establishes a 1 in 100 chance of playing a car horn in every game cycle (line 6). Because the game cycles are flying by at 30 per second, these really aren’t as bad odds as they sound. Whenever the odds do work out and a car horn is played, another random number is selected to see which car horn is played (lines 7–12). You could have just as easily played a single car horn, but having two horns with different sounds makes the game much more interesting. It’s the little touches like this that make a game more intriguing to players.

If you recall from the earlier design of the Henway game, you can start a new game by clicking the mouse anywhere on the game screen after the game is over. Listing 15.3 contains code for a new MouseButtonDown() function that restarts the MIDI song as part of starting a new game.

Example 15.3. The MouseButtonDown() Function Restarts the MIDI Song to Coincide with a New Game

 1: void MouseButtonDown(int x, int y, BOOL bLeft)
 2: {
 3:   // Start a new game, if necessary
 4:   if (_bGameOver)
 5:   {
 6:     // Restart the background music
 7:     _pGame->PlayMIDISong();
 8:
 9:     // Initialize the game variables
10:     _iNumLives = 3;
11:     _iScore = 0;
12:     _bGameOver = FALSE;
13:   }
14: }

It obviously makes sense to start the background music over when a new game starts. Because the default action of the PlayMIDISong() method is to rewind a song before playing it, it isn’t necessary to pass any arguments to the method in this particular case (line 7).

Speaking of restarting the MIDI song, Listing 15.4 contains the code for the HandleJoystick() function, which also restarts the song as part of starting a new game.

Example 15.4. The HandleJoystick() Function Also Restarts the MIDI Song to Signal a New Game

 1: void HandleJoystick(JOYSTATE jsJoystickState)
 2: {
 3:   if (!_bGameOver && (++_iInputDelay > 2))
 4:   {
 5:     // Check horizontal movement
 6:     if (jsJoystickState & JOY_LEFT)
 7:         MoveChicken(-20, 0);
 8:     else if (jsJoystickState & JOY_RIGHT)
 9:         MoveChicken(20, 0);
10:
11:     // Check vertical movement
12:     if (jsJoystickState & JOY_UP)
13:         MoveChicken(0, -20);
14:     else if (jsJoystickState & JOY_DOWN)
15:         MoveChicken(0, 20);
16:
17:     // Reset the input delay
18:     _iInputDelay = 0;
19:   }
20:
21:   // Check the joystick button and start a new game, if necessary
22:   if (_bGameOver && (jsJoystickState & JOY_FIRE1))
23:   {
24:     // Play the background music
25:     _pGame->PlayMIDISong();
26:
27:     // Initialize the game variables
28:     _iNumLives = 3;
29:     _iScore = 0;
30:     _bGameOver = FALSE;
31:   }
32: }

The call to the PlayMIDISong() method again occurs with no arguments, which results in the MIDI song being started over at the beginning (line 25).

You’ve now seen all the MIDI music-related code in the Henway 2 game, but there are still some wave sound effects left to be played. Two of these sound effects are played in the SpriteCollision() function, which is shown in Listing 15.5.

Example 15.5. The SpriteCollision() Function Plays Sound Effects in Response to the Chicken Getting Run Over and the Game Ending

 1: BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee)
 2: {
 3:   // See if the chicken was hit
 4:   if (pSpriteHittee == _pChickenSprite)
 5:   {
 6:     // Move the chicken back to the start
 7:     _pChickenSprite->SetPosition(4, 175);
 8:
 9:     // See if the game is over
10:     if (--_iNumLives > 0)
11:       // Play a sound for the chicken getting hit
12:       PlaySound((LPCSTR)IDW_SQUISH, _hInstance, SND_ASYNC |
13:         SND_RESOURCE);
14:     else
15:     {
16:       // Play a sound for the game ending
17:       PlaySound((LPCSTR)IDW_GAMEOVER, _hInstance, SND_ASYNC |
18:         SND_RESOURCE);
19:
20:       // Display game over message
21:       TCHAR szText[64];
22:       wsprintf(szText, "Game Over! You scored %d points.", _iScore);
23:       MessageBox(_pGame->GetWindow(), szText, TEXT("Henway 2"), MB_OK);
24:       _bGameOver = TRUE;
25:
26:       // Pause the background music
27:       _pGame->PauseMIDISong();
28:     }
29:
30:     return FALSE;
31:   }
32:
33:   return TRUE;
34: }

If you remember, the SpriteCollision() function is where you detect a collision between the chicken and a car. This makes it an ideal place to play a squish sound when the chicken is run over (lines 12 and 13). Similarly, the SpriteCollision() function also knows when the game ends, so it only makes sense to play a sound whenever the player runs out of chickens (lines 17 and 18).

The final sound effect in the Henway 2 game occurs in the MoveChicken() function, which is where the game determines when the chicken has made it across the road. Listing 15.6 shows how a celebration sound is played each time the chicken makes it across.

Example 15.6. The MoveChicken() Helper Function Plays a Celebration Sound Whenever the Chicken Makes It Safely Across the Road

 1: void MoveChicken(int iXDistance, int iYDistance)
 2: {
 3:   // Move the chicken to its new position
 4:   _pChickenSprite->OffsetPosition(iXDistance, iYDistance);
 5:
 6:   // See if the chicken made it across
 7:   if (_pChickenSprite->GetPosition().left > 400)
 8:   {
 9:     // Play a sound for the chicken making it safely across
10:     PlaySound((LPCSTR)IDW_CELEBRATE, _hInstance, SND_ASYNC | SND_RESOURCE);
11:
12:     // Move the chicken back to the start and add to the score
13:     _pChickenSprite->SetPosition(4, 175);
14:     _iScore += 150;
15:   }
16: }

This function makes sure that a celebration wave sound is played whenever the chicken successfully crosses the road (line 10). This might be a small concession for working so hard to get the chicken across, but keep in mind that the previous version of the game offered no audible reward at all!

Testing the Finished Product

You’ll find that testing sounds and music in games is initially one of the most exciting test phases of a game, but eventually becomes quite monotonous. The music in the Henway 2 game will no doubt haunt me for years when you consider that I had to listen to it over and over as I developed and debugged the game. You will likely experience the same joy and frustration when developing and testing your own games that take advantage of wave sounds and music.

To test the Henway 2 game, just launch the game and listen. The music will immediately start playing, and you’ll quickly begin hearing random car horns that help create the atmosphere of busy traffic buzzing by the chicken. You will notice the new sound effects as you safely navigate the chicken across the road, as well as when you get run over. More important from the perspective of the MIDI music is to test the game as it is deactivated and then reactivated. Just minimize the game or activate a different program to see how the game responds. The music should immediately stop playing. Reactivating the game results in the music continuing to play from where it left off.

Summary

Just as sound effects add interest and excitement to specific events that take place in a game, music can be extremely effective in establishing a mood or simply making a game more fun. This hour explored MIDI music and how to work with it at the programming level. You learned about the Media Control Interface, or MCI, which is used to play MIDI songs without having to get into a bunch of messy low-level code. You then took your new MCI programming knowledge and used it to add MIDI music support to the game engine. Finally, you revisited the Henway game from Hour 12, and added both MIDI music and wave sound effects to it.

The next hour pulls together everything you’ve learned throughout the book thus far in the creation of another complete game. The game is called Battle Office, and I think you’ll find it to be a pretty neat little game.

Q&A

Q1:

Why isn’t it possible to store MIDI songs as resources in the executable game file like I’ve done with bitmaps and wave sounds?

A1:

This limitation has to do with the fact that the MCI simply doesn’t support the playback of MIDI songs from any source other than a file. So, although you could certainly include a MIDI song as a resource in a game, you wouldn’t be able to play it using the MCI. You could, however, play it using low-level multimedia functions, assuming that you had the desire to learn low-level Windows multimedia programming. Assuming that you don’t, being limited to playing a MIDI song from a file is a relatively small concession when you consider how easy it makes it to play songs.

Q2:

Can the MCI be used to do other things with MIDI files?

A2:

Yes, the MCI is flexible in allowing you to carry out other tasks related to the playback of MIDI files. For example, you can get more detailed about how you want a song played, such as playing only the beginning or end of a song. However, there are certainly still limitations when it comes to using the MCI to play MIDI songs because it is a high-level API. For complete control over the playback of MIDI music, you’ll have to use the low-level Windows multimedia API, which is considerably more complex than the MCI.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you’ve learned, and begin learning how to put your knowledge into practice. The answers to the quiz can be found in Appendix A, “Quiz Answers.”

Quiz

1:

What is the purpose of an MCI command message?

2:

What is the device in your computer that is responsible for playing MIDI music?

3:

When is it important to pause a MIDI song in a game?

Exercises

  1. Find a different MIDI song and use it in place of the Music.mid song that is currently being used in the Henway 2 game.

  2. Add some more wave sound effects to the Henway 2 game that are played randomly along with the car horn sounds. Sounds you might consider adding include a chicken sound (Bok!), the sound of a car passing by, and maybe the sound of tires screeching.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.85.221