Chapter 8
What about the sounds?
A Sound Theory
Sounds are essential in any game. Our little project should be no exception. Sounds bring life to various elements in the game, such as space ships, weapon systems, rain, sword clashing, car engines, and so forth.
Many games don't need music. For instance, a game with the programmer's or game designer's favorite song may annoy many players who have different tastes in music. Instead, give the player the option to listen to their own music, on their own audio equipment. Not to mention, music can be costly in data size.
Sound effects on the other hand, are quite useful. A word of warning, however, a game with bad sounds (scratchy, annoying, too repetitive, etc.) is worse than a silent game. Take care when engineering sounds for your game.
The Hardware
The Nintendo DS has amazing sound hardware. We will only be able to scratch the surface of it, and even still, we won't leave any scratch marks. The Nintendo DS has 16 channels to play sounds, numbered 0 through 15. Channels 8-15 are special channels. All channels can play ADPCM or PCM sounds, while 8-13 are the only channels that can play PSG sounds and 14-15 the only that can generate noise. We will only be experimenting with PCM sounds.
Making the Sounds
To play our sounds, we will use the unbelievably impressive homebrew audio library, maxmod. maxmod is distributed with libnds, so you should already have it installed. maxmod can play music as well as sound effects, in such formats as mod, it, s3m, xm, and wav, but we'll just be using it as a wav player to play our sound effects. For more information and deeper coverage of the library, visit http://maxmod.org/.
Using the Sounds
The makefile I've included with my manual has a custom rule for creating a
soundbank.bin
file from all audio files in the audio
project folder. The
rule runs mmutil to create the soundbank.bin
and soundbank.h
files. The
bin2o rule turns the .bin
file into something safe to link into your .nds
file and creates the soundbank_bin.h
file. All of these header files will be
located in the build
folder after a build, if you wish to review their
contents.
Getting Down with the Code
Now it's finally time for some code. maxmod simplifies sound playing quite a
bit. All we need to do is initialize the library, let it know where our
soundbank is, load the sound from our soundbank, and call a function to play
the sound. It will serve our purposes for now, as a springboard into more
advanced forms of sound. Enough with the chatter, here's some code for our
main.cpp
.
void handleInput(Ship *ship, MathVector2D<int> *moonPos, SpriteInfo *moonInfo,
touchPosition *touch) {
/* Handle up and down parts of D-Pad. */
if (keysDown() & KEY_UP) {
// Play our sound only when the button is initially pressed
mmEffect(SFX_THRUST);
}
if (keysHeld() & KEY_UP) {
// accelerate ship
ship->accelerate();
} else if (keysHeld() & KEY_DOWN) {
// reverse ship direction
ship->reverseTurn();
}
/* Handle left and right parts of D-Pad. */
if (keysHeld() & KEY_LEFT) {
// rotate counter clockwise
ship->turnCounterClockwise();
} else if (keysHeld() & KEY_RIGHT) {
// rotate clockwise
ship->turnClockwise();
}
/* Handle the touch screen. */
static MathVector2D<int> moonGrip;
if (keysDown() & KEY_TOUCH) {
/* Record the grip */
moonGrip.x = touch->px;
moonGrip.y = touch->py;
} else if (keysHeld() & KEY_TOUCH) {
int newX = moonPos->x + touch->px - moonGrip.x;
int newY = moonPos->y + touch->py - moonGrip.y;
/* Prevent dragging off the screen */
if (newX < 0) {
moonPos->x = 0;
} else if (newX > (SCREEN_WIDTH - moonInfo->width)) {
moonPos->x = SCREEN_WIDTH - moonInfo->width;
} else {
moonPos->x = newX;
}
if (newY < 0) {
moonPos->y = 0;
} else if (newY > (SCREEN_HEIGHT - moonInfo->height)) {
moonPos->y = SCREEN_HEIGHT - moonInfo->height;
} else {
moonPos->y = newY;
}
/* Record the grip again. */
moonGrip.x = touch->px;
moonGrip.y = touch->py;
}
}
int main() {
/* Turn on the 2D graphics core. */
powerOn(POWER_ALL_2D);
/*
* Configure the VRAM and background control registers.
*
* Place the main screen on the bottom physical screen. Then arrange the
* VRAM banks. Next, confiure the background control registers.
*/
lcdMainOnBottom();
initVideo();
initBackgrounds();
/* Initialize maxmod using the memory based soundbank set up. */
mmInitDefaultMem((mm_addr)soundbank_bin);
/* Set up a few sprites. */
SpriteInfo spriteInfo[SPRITE_COUNT];
OAMTable *oam = new OAMTable();
iniOAMTable(oam);
initSprites(oam, spriteInfo);
/* Display the backgrounds. */
displayStarField();
displayPlanet();
displaySplash();
/*************************************************************************/
/* Keep track of the touch screen coordinates */
touchPosition touch;
/* Make the ship object */
static const int SHUTTLE_AFFINE_ID = 0;
SpriteEntry *shipEntry = &oam->oamBuffer[SHUTTLE_AFFINE_ID];
SpriteRotation *shipRotation = &oam->matrixBuffer[SHUTTLE_AFFINE_ID];
Ship *ship = new Ship(&spriteInfo[SHUTTLE_AFFINE_ID]);
/* Make the moon */
static const int MOON_AFFINE_ID = 1;
SpriteEntry *moonEntry = &oam->oamBuffer[MOON_AFFINE_ID];
SpriteInfo *moonInfo = &spriteInfo[MOON_AFFINE_ID];
MathVector2D<int> *moonPos = new MathVector2D<int>();
moonPos->x = moonEntry->x;
moonPos->y = moonEntry->y;
/* Set up sound data.*/
mmLoadEffect(SFX_THRUST);
for (;;) {
/* Update the game state. */
updateInput(&touch);
handleInput(ship, moonPos, moonInfo, &touch);
ship->moveShip();
/* Update ship sprite attributes. */
MathVector2D<float> position = ship->getPosition();
shipEntry->x = (int)position.x;
shipEntry->y = (int)position.y;
rotateSprite(shipRotation, -ship->getAngleDeg());
/* Update moon sprite attributes. */
moonEntry->x = (int)moonPos->x;
moonEntry->y = (int)moonPos->y;
/*
* Update the OAM.
*
* We have to copy our copy of OAM data into the actual OAM during
* VBlank (writes to it are locked during other times).
*/
swiWaitForVBlank();
updateOAM(oam);
}
return 0;
}
In summary, we simply set up our sound in the main()
function, and had the
handleInput()
function play our sound whenever the up key is initially
pressed.
The all powerful maxmod
We are barely touching what maxmod can do in this case study. maxmod can stop sounds after they've started playing, loop sounds, play music, and more without breaking a sweat (because the Nintendo DS doesn't have sweat glands anyway). I highly recommend you read about it on its homepage, or at least run the maxmod demo (featured on said homepage) on a real Nintendo DS. You won't be disappointed.
Compiling
Check your includes.
#include <assert.h>
#include <maxmod9.h>
#include <nds.h>
#include "ship.h"
#include "sprites.h"
/* Backgrounds */
#include "planet.h"
#include "splash.h"
#include "starField.h"
/* Sprites */
#include "moon.h"
#include "orangeShuttle.h"
/* Sounds */
#include "soundbank.h"
#include "soundbank_bin.h"
This is the final iteration of the Orange Spaceship demo that we will cover in this edition of the manual. Compile it and enjoy the fruits of your labors. Mmm, tasty. You should hear a nice sound when you press the thrust button. The game output should now look like the screen shots in Figure 9.1, “Flying around in the Orange Shuttle, with sound!”.