SDL_Mixer Tutorial

I have a horrible secret to confess: None of my games have music. Absolutely none of them. This is mainly because I've never taken the time to master any of the music libraries available to me - thus any music addition got put off until later. This eventually became 'never'. These games were small affairs, so they were hardly expected to have good graphics or sound.

Since I'm in the process of creating a game I'd actually like to share with the public, however, I figured I should finally pick it up. And now that I have a pretty basic understanding of what's going on, I'm going to share it with you :)


Part 1: Making Music

Some of you may be wondering why I'm putting music before sound effects. Wouldn't sound effects be easier? In a word, no. The SDL_Mixer library has a specialized channel devoted entirely to music, and has a number of functions just for that. It's a slightly easier place to begin.

Here are the steps in using SDL_Mixer in your program - keep in mind these aren't hard rules, this is just what I've done in the examples below:

Here's an example to get started. Note that all the functions from SDL_Mixer start with the prefix Mix_. If you're like me, you got confused and thought that all the Mix_ functions did mixing. It's actually just convention:

#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"
#include "SDL_mixer.h"

/* Mix_Music actually holds the music information.  */
Mix_Music *music = NULL;

void handleKey(SDL_KeyboardEvent key);
void musicDone();

int main(void) {

  SDL_Surface *screen;
  SDL_Event event;
  int done = 0;

  /* We're going to be requesting certain things from our audio
     device, so we set them up beforehand */
  int audio_rate = 22050;
  Uint16 audio_format = AUDIO_S16; /* 16-bit stereo */
  int audio_channels = 2;
  int audio_buffers = 4096;

  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

  /* This is where we open up our audio device.  Mix_OpenAudio takes
     as its parameters the audio format we'd /like/ to have. */
  if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers)) {
    printf("Unable to open audio!\n");
    exit(1);
  }

  /* If we actually care about what we got, we can ask here.  In this
     program we don't, but I'm showing the function call here anyway
     in case we'd want to know later. */
  Mix_QuerySpec(&audio_rate, &audio_format, &audio_channels);

  /* We're going to be using a window onscreen to register keypresses
     in.  We don't really care what it has in it, since we're not
     doing graphics, so we'll just throw something up there. */
  screen = SDL_SetVideoMode(320, 240, 0, 0);

  while(!done) {
    while(SDL_PollEvent(&event)) {
      switch(event.type) {
      case SDL_QUIT:
	done = 1;
	break;
      case SDL_KEYDOWN:
      case SDL_KEYUP:
	handleKey(event.key);
	break;
      }
    }

    /* So we don't hog the CPU */
    SDL_Delay(50);

  }

  /* This is the cleaning up part */
  Mix_CloseAudio();
  SDL_Quit();

}

void handleKey(SDL_KeyboardEvent key) {
  switch(key.keysym.sym) {
  case SDLK_m:
    if(key.state == SDL_PRESSED) {

      /* Here we're going to have the 'm' key toggle the music on and
	 off.  When it's on, it'll be loaded and 'music' will point to
	 something valid.  If it's off, music will be NULL. */

      if(music == NULL) {
	
	/* Actually loads up the music */
	music = Mix_LoadMUS("music.ogg");

	/* This begins playing the music - the first argument is a
	   pointer to Mix_Music structure, and the second is how many
	   times you want it to loop (use -1 for infinite, and 0 to
	   have it just play once) */
	Mix_PlayMusic(music, 0);

	/* We want to know when our music has stopped playing so we
	   can free it up and set 'music' back to NULL.  SDL_Mixer
	   provides us with a callback routine we can use to do
	   exactly that */
	Mix_HookMusicFinished(musicDone);
	
      } else {
	/* Stop the music from playing */
	Mix_HaltMusic();

	/* Unload the music from memory, since we don't need it
	   anymore */
	Mix_FreeMusic(music);
	
	music = NULL;
      }
      break;
    }
  }
}

/* This is the function that we told SDL_Mixer to call when the music
   was finished. In our case, we're going to simply unload the music
   as though the player wanted it stopped.  In other applications, a
   different music file might be loaded and played. */
void musicDone() {
  Mix_HaltMusic();
  Mix_FreeMusic(music);
  music = NULL;
}
(A listing is available here. Honest Bob and the Factory-to-Dealer Incentives was kind enough to donate their song Another person you had sex with for people who wanted an ogg file to test their programs with. Other files are available on the Ogg vorbis downloads page. Just remember to rename whatever you choose to 'music.ogg')

The program can be compiled as follows:

gcc -g -o mixer1 mixer1.c `sdl-config --cflags --libs` -lSDL_mixer
(Two notes: One, the quotes surrounding the sdl-config command are backticks, which you can find on the tilde (~) key. Two, this command will only work if you have SDL_mixer installed with the same prefix (/usr, /usr/local/, etc.) as SDL. If you do not, you will need to add "-I/usr/local/include" and "-L/usr/local/lib" (or wherever SDL_mixer is installed) to the command.)

If everything works, you should see a blank window appear on the screen. Make sure it has focus, and press the 'm' key to hopefully hear some music. Play around with toggling the music on and off. If you don't hear music, make sure you have the music.ogg file in the same directory as the program.

With this information alone, you could probably code some background music for your next game. But you've gone this far - why not add some sound effects while you're at it?

Part 2: Making Noise

Here we're going to blast the heck out of our enemies with a cool-sounding phaser. The code here isn't much different than the music, but there is some important stuff:

#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"
#include "SDL_mixer.h"

/* Mix_Chunk is like Mix_Music, only it's for ordinary sounds. */
Mix_Chunk *phaser = NULL;

/* Every sound that gets played is assigned to a channel.  Note that
   this is different from the number of channels you request when you
   open the audio device; a channel in SDL_mixer holds information
   about a sound sample that is playing, while the number of channels
   you request when opening the device is dependant on what sort of
   sound you want (1 channel = mono, 2 = stereo, etc) */
int phaserChannel = -1;

void handleKey(SDL_KeyboardEvent key);

int main(void) {

  SDL_Surface *screen;
  SDL_Event event;
  int done = 0;

  /* Same setup as before */
  int audio_rate = 22050;
  Uint16 audio_format = AUDIO_S16; 
  int audio_channels = 2;
  int audio_buffers = 4096;

  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

  if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers)) {
    printf("Unable to open audio!\n");
    exit(1);
  }

  /* We're going to pre-load the sound effects that we need right here
   */
  phaser = Mix_LoadWAV("phaser.wav");

  screen = SDL_SetVideoMode(320, 240, 0, 0);

  while(!done) {
    while(SDL_PollEvent(&event)) {
      switch(event.type) {
      case SDL_QUIT:
	done = 1;
	break;
      case SDL_KEYDOWN:
      case SDL_KEYUP:
	handleKey(event.key);
	break;
      }
    }
    SDL_Delay(50);

  }

  Mix_CloseAudio();
  SDL_Quit();

}

void handleKey(SDL_KeyboardEvent key) {
  switch(key.keysym.sym) {
  case SDLK_p:

    /* We're going to have the phaser continually fire as long as
       the user is holding the button down */
    if(key.type == SDL_KEYDOWN) {

      if(phaserChannel < 0) {

	/* Mix_PlayChannel takes, as its arguments, the channel that
	   the given sound should be played on, the sound itself, and
	   the number of times it should be looped.  If you don't care
	   what channel the sound plays on, just pass in -1.  Looping
	   works like Mix_PlayMusic. This function returns the channel
	   that the sound was assigned to, which you'll need later. */
	phaserChannel = Mix_PlayChannel(-1, phaser, -1);
	
      }
    } else {
      /* Mix_HaltChannel stops a certain channel from playing - this
	 is one of the reasons we kept track of which channel the
	 phaser had been assigned to */
      Mix_HaltChannel(phaserChannel);
      
      phaserChannel = -1;
    }
    break;
  }
}
(A listing is available here. Here's a phaser noise which comes with the OpenAL library.)

The program can be compiled as follows:

gcc -g -o mixer2 mixer2.c `sdl-config --cflags --libs` -lSDL_mixer

Once again, you'll see a blank window appear - pressing and holding the 'p' key results in a phaser noise if everything worked well. Don't forget to have a phaser.wav file in your directory!

Part 3: Mixing It All Together

Of course, it wouldn't be a very good mixer if it didn't mix sounds together. What do you need to do in order to have your music and sound effects mixed? Nothing - the Mix_PlayChannel and Mix_PlayMusic functions handle the mixing for you, you just need to call them. Below is a program that's really not much more than the other two programs put together:

#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"
#include "SDL_mixer.h"

Mix_Chunk *phaser = NULL;
Mix_Music *music = NULL;

int phaserChannel = -1;

void handleKey(SDL_KeyboardEvent key);
void musicDone();

int main(void) {

  SDL_Surface *screen;
  SDL_Event event;
  int done = 0;

  /* Same setup as before */
  int audio_rate = 22050;
  Uint16 audio_format = AUDIO_S16; 
  int audio_channels = 2;
  int audio_buffers = 4096;

  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

  if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers)) {
    printf("Unable to open audio!\n");
    exit(1);
  }

  /* Pre-load sound effects */
  phaser = Mix_LoadWAV("phaser.wav");

  screen = SDL_SetVideoMode(320, 240, 0, 0);

  while(!done) {
    while(SDL_PollEvent(&event)) {
      switch(event.type) {
      case SDL_QUIT:
	done = 1;
	break;
      case SDL_KEYDOWN:
      case SDL_KEYUP:
	handleKey(event.key);
	break;
      }
    }
    SDL_Delay(50);

  }

  Mix_CloseAudio();
  SDL_Quit();

}

void handleKey(SDL_KeyboardEvent key) {
  switch(key.keysym.sym) {
  case SDLK_p:

    if(key.type == SDL_KEYDOWN) {

      if(phaserChannel < 0) {
	phaserChannel = Mix_PlayChannel(-1, phaser, -1);
      }
    } else {
      Mix_HaltChannel(phaserChannel);
      
      phaserChannel = -1;
    }
    break;
  case SDLK_m:
    if(key.state == SDL_PRESSED) {

      if(music == NULL) {
	
	music = Mix_LoadMUS("music.ogg");
	Mix_PlayMusic(music, 0);
	Mix_HookMusicFinished(musicDone);
	
      } else {
	Mix_HaltMusic();
	Mix_FreeMusic(music);
	music = NULL;
      }
    }
    break;
  }
}

void musicDone() {
  Mix_HaltMusic();
  Mix_FreeMusic(music);
  music = NULL;
}
(And here's your side of listing)

And of course you can compile like this:

gcc -g -o mixer3 mixer3.c `sdl-config --cflags --libs` -lSDL_mixer

You'll see the ever-popular perfectly blank window. Try pressing 'm' to start the music, then press and hold 'p' to hear the phaser play atop it. With just a bit of code, you've got a working sound system. It wouldn't take many modifications to make it part of a game.


Back to the tutorial index

Copyright 2001, Roger Ostrander