Chapter 7: Sound and Music (JEDI-SDL)

This is an SDL 1.2 chapter. SDL 1.2 is obsolete since it has been replaced by SDL 2.0. Unless you have good reasons to stay here you may prefer to go for the modern SDL 2.0 :-).

This chapter will introduce you on how to load music and sounds since these are key features of every game and many applications. An easy way to play music (.wav, .mod, .mid, .ogg, .mp3) and sound files (.wav, .aif, .ogg, .voc) is the usage of an additional unit called SDL_MIXER. This unit is maintained by the same author which also provides the core SDL library, Sam Lantinga and the co-authors Stephane Peter and Ryan Gordon. Fortunately the JEDI-SDL team translated this unit so it is available for Pascal programmers.

You need this .dll file and a music and sound file:

Software Version Source Description
SDL_mixer-1.2.8-win32.zip 1.2.8 http://www.libsdl.org/projects/SDL_mixer/ This is the corresponding dynamic link library file.
In my mind.ogg In-my-mind.ogg A music file. “In my mind” by First. Of course you could also use any other music file you desire.
dial.wav dial.wav Public domain sounds: http://www.pdsounds.org. Of course you could also use any other sound file you desire.

You should extract the zip-file (SDL_mixer-1.2.8-win32.zip) and get six files. A readme text file, the important SDL_mixer.dll and several further .dll files. Copy all of them to the system32-folder. If you forget this and run the examples below you will get an error with exitcode = 309.

Here we go now, the complete code at once. Go through it an look if you get what the program will do when you run it.

ATTENTION: I experience a strange runtime error (216: General Protection Fault) with some curious symbols in popup window when trying this example program from IDE. The compiled program runs well though. In other projects I don’t get this behaviour. Probably it has to do with the call of MIX_VOLUMECHUNK and you may comment it out. The music and sound can’t be heard when run from IDE. You have to compile the program and execute the compiled program. This by the way isn’t needed for other projects. Not sure on the reasons for this strange behaviour here.

PROGRAM chap7;
USES CRT, SDL, SDL_MIXER;

CONST
AUDIO_FREQUENCY:INTEGER=22050;
AUDIO_FORMAT:WORD=AUDIO_S16;
AUDIO_CHANNELS:INTEGER=2;
AUDIO_CHUNKSIZE:INTEGER=4096;

VAR
userkey:CHAR;
music:pMIX_MUSIC=NIL;
sound:pMIX_CHUNK=NIL;
soundchannel:INTEGER;

BEGIN
SDL_INIT(SDL_INIT_AUDIO);


IF MIX_OPENAUDIO(AUDIO_FREQUENCY, AUDIO_FORMAT,
                 AUDIO_CHANNELS, AUDIO_CHUNKSIZE)<>0 THEN HALT;

music:=MIX_LOADMUS('In my mind.ogg');
MIX_VOLUMEMUSIC(20);

sound:=MIX_LOADWAV('dial.wav');
MIX_VOLUMECHUNK(sound,50);

writeln('Music is playing now...');
MIX_PLAYMUSIC(music,0); //-1 = infinite, 0 = once, 1 = twice,...

writeln('"s" - play sound effect');
writeln('"z" - pause sound effect');
writeln('"t" - resume sound effect');
writeln('"p" - pause music');
writeln('"o" - resume music');
writeln('"q" - quit');
REPEAT
  REPEAT
  SDL_DELAY(20);
  UNTIL KEYPRESSED;
  userkey:=READKEY;
  CASE userkey OF
    's': soundchannel:=MIX_PLAYCHANNEL(-1,sound,0);
    'z','y': MIX_PAUSE(soundchannel);
    't': MIX_RESUME(soundchannel);
    'p': MIX_PAUSEMUSIC;
    'o': MIX_RESUMEMUSIC;
  END;
UNTIL userkey='q';


MIX_HALTMUSIC;
MIX_HALTCHANNEL(soundchannel);

MIX_FREEMUSIC(music);
MIX_FREECHUNK(sound);

MIX_CLOSEAUDIO;
SDL_QUIT;
END.

Probabably you looked through the code now. Now we can go step by step through the code from top to bottom.

PROGRAM chap7;
USES CRT, SDL, SDL_MIXER;

The program is called “chap7” (quite imaginative, isn’t it?). Now we include three units. The common SDL unit already known. The CRT unit is known from chapters 3 and 4 where we needed it to check when the user pressed a key. We need it here for the very same purpose. New is the SDL_MIXER unit which is needed to allow us to use the sound subsystem in an easy way.

You may ask why we didn’t use the event handling structure we know from chapter 6 to recognize when the user presses a key instead of the keypresed command from CRT unit. Well, I think it isn’t of an advantage if you mix up the different parts of SDL for learning purposes. Furthermore this “raw” way the source code stays tighter and therefore more readable.

CONST
AUDIO_FREQUENCY:INTEGER=22050;
AUDIO_FORMAT:WORD=AUDIO_S16;
AUDIO_CHANNELS:INTEGER=2;
AUDIO_CHUNKSIZE:INTEGER=4096;

Next we define some constants which will be used to intilize the sound system. Of course you also can add the different values directly when inilizing the sound system. We need the sample frequency in Hertz (1/s) which usually is 22050 Hz or MIX_DEFAULT_FREQUENCY in games but double as high for CD quality sound (44100 Hz).

Next we have to define the audio format. It determines how the audio data is stored. Different values are given in the following table (from official SDL Mixer documentation):

audio format Description
AUDIO_U8 Unsigned 8-bit samples
AUDIO_S8 Signed 8-bit samples
AUDIO_u16LSB Unsigned 16-bit samples, in little-endian byte order
AUDIO_S16LSB Signed 16-bit samples, in little-endian byte order
AUDIO_U16MSB Unsigned 16-bit samples, in big-endian byte order
AUDIO_S16MSB Signed 16-bit samples, in big-endian byte order
AUDIO_U16 same as AUDIO_U16LSB
AUDIO_S16 same as AUDIO_S16LSB
AUDIO_U16SYS Unsigned 16-bit samples, in system byte order
AUDIO_S16SYS Signed 16-bit samples, in system byte order

Then we decide for a sound type, which means either stereo or mono. For stereo sound we choose 2 and for mono we choose 1 as value. Finally the chunksize has to be set, where 4096 bytes per sample is a default value. The lower this value the worse the quality.

VAR
userkey:CHAR;
music:pMIX_MUSIC=NIL;
sound:pMIX_CHUNK=NIL;
soundchannel:INTEGER;

Now we define some variables. First we define a CHAR varibale which later reads the keyboard input of the user. This variable is just for this purpose and has no direct connection to the sound system or SDL. Variable music which is a pMIX_MUSIC pointer is very important. It points at the loaded music file later! Same applies for sound which is a pMIX_CHUNK pointer. Both pointers are initilized as NIL pointers here. Note: Each music song is referenced by an own pMIX_MUSIC pointer and each sound effect (e.g. explosions, shots, …) is referenced by an own pMIX_CHUNK pointer.

BEGIN
SDL_INIT(SDL_INIT_AUDIO);


IF MIX_OPENAUDIO(AUDIO_FREQUENCY, AUDIO_FORMAT,
                 AUDIO_CHANNELS, AUDIO_CHUNKSIZE)<>0 THEN HALT;

music:=MIX_LOADMUS('In my mind.ogg');
MIX_VOLUMEMUSIC(20);

sound:=MIX_LOADWAV('dial.wav');
MIX_VOLUMECHUNK(sound,50);

In this demo we just want to demonstrate the music/sound abilities of SDL so we only initialize the audio subsystem by SDL_INIT_AUDIO. Similar to the initilization of the graphic system we then have to set up the audio system by the function MIX_OPENAUDIO(parameters). The four paramteters needed we already defined and discussed above. The function will return 0 on error.

We will load the music to the music pointer by MIX_LOADMUS(absolute path to music file). If the Pascal file and the music file are in the same folder you can give the music file’s name without a path (as shown in the example). The same way you load up sound files with MIX_LOADWAV function. The same function is used to load other file formats instead of .wav sound files even though the function’s name doesn’t indicate this. Both functions return NIL on error (which we don’t check in the example though). The functions MIX_VOLUMEMUSIC(integer) and MIX_VOLUMECHUNK(sound pointer, integer) set the volume for the music sound file. Note: If you have more than one music and sound file you have to set the volume for each sound file individually but the music volume is set for all music files together. The volume values are integers between 0 (silence) and 128 (highest volume). The constant MIX_MAX_VOLUME corresponds to 128.

writeln('Music is playing now...');
MIX_PLAYMUSIC(music,0); //-1 = infinite, 0 = once, 1 = twice,...

writeln('"s" - play sound effect');
writeln('"z" - pause sound effect');
writeln('"t" - resume sound effect');
writeln('"p" - pause music');
writeln('"o" - resume music');
writeln('"q" - quit');
REPEAT
  REPEAT
  SDL_DELAY(20);
  UNTIL KEYPRESSED;
  userkey:=READKEY;
  CASE userkey OF
    's': soundchannel:=MIX_PLAYCHANNEL(-1,sound,0);
    'z','y': MIX_PAUSE(soundchannel);
    't': MIX_RESUME(soundchannel);
    'p': MIX_PAUSEMUSIC;
    'o': MIX_RESUMEMUSIC;
  END;
UNTIL userkey='q';

The writeln commands for displaying text at the shell should be known. The MIX_PLAYMUSIC(music pointer, integer) is used to play music files. Therefore we give the music pointer which we called “music” and we give the number of loops this music file should be played. As the comment states -1 means the music is replayed infinitly, 0 means the music is played once, 1 means the music is played twice and so on. The function returns -1 on error and 0 on success (which we don’t check in the example).

Similarly sounds are played. Contrary to music which has its own seperated channel, sounds have up to eight channels (channel numbers 0 to 7) where they can be played at. This means you can have up to eight sounds that play at the same time and can be heard at the same time! As for music files you have to say which sound you want to play and how often but additionally you have to say at which channel you want it to play. The function is MIX_PLAYCHANNEL(paramters) which returns the channel number the sound is played on or -1 on error. The first parameter is the channel you want the music to be played. -1 means the first free channel will be chosen automatically. What will happen when all channels are used but a further sound is going to be played? One playing sound will be stopped and the new sound will be played on this channel. The next two parameters are the same as for music, the sound pointer and the integer how often the sound will be played.

The repeat-until loops, the commands KEYPRESSED, READKEY and the variable userkey aren’t SDL specific but common Pascal commands and they will hold the user in the loop until he decides to leave it by pressing the “q” button. This will not be explained here in detail.

The different cases in the case block show the usage of playing a sound (which is discussed already) and different functions to pause/resume music and sounds. The procedures MIX_PAUSEMUSIC and MIX_RESUMEMUSIC pause and resume the actively playing music. Similar for sounds but you actually don’t pause/resume the sounds themselves but the channels they are played on. This channel we don’t know because we said MIX_PLAYCHANNEL should find the first free channel automatically but we returned the channel number in variable “soundchannel”. MIX_PAUSE(channel number) and MIX_RESUME(channel number) will pause and resume the desired channel when using “soundchannel” as parameter.

MIX_HALTMUSIC;
MIX_HALTCHANNEL(soundchannel);

MIX_FREEMUSIC(music);
MIX_FREECHUNK(sound);

MIX_CLOSEAUDIO;
SDL_QUIT;
END.

The commands MIX_HALTMUSIC and MIX_HALTCHANNEL(channel number) are used to stop playing the music/sound immediatly. As for surfaces the msuic/sound files have to be freed when they are not used anymore. The functions MIX_FREEMUSIC(music pointer) and MIX_FREECHUNK(sound pointer) are used for this. Finally the audiosystem and SDL have to be shut down by MIX_CLOSEAUDIO and SDL_QUIT.

This file contains the source code: chap7.pas (right click and “save as”)
This file is the executable: chap7.exe (right click and “save as”)

Leave a Reply

Your email address will not be published. Required fields are marked *