Tag Archives: sounds

Chapter 9 application preview

New Chapter 9 about Music and Sound

The new Chapter 9: Music and Sound has been released. It demonstrates how to use the SDL_mixer 2.0 to load music and sound files to use them in your applications.

I was surprised to find that there have been made many important updates to Tim Blume’s SDL 2.0 units since the latest release (version 1.72). This means for everybody who relies on these units, update to the latest master branch release (later than version 1.72)!

Download ZIP button
IMPORTANT: Get the latest master branch release! Download Link

Otherwise you will struggle to get some important sdl features. E.g. key code constants and the code of the new Chapter 9 won’t work. I adapted Chapter 8 about event handling.

An interesting new header translation project was mentioned to me which allows for dynamic loading of SDL 2.0. Check out Chapter 1 to read more about this project by Imants Gulis.

Many smaller updates have been performed, too. Also the color scheme has been updated since it was stated that the contrast of the former color scheme has been too weak. You may also have noticed the amazing new fractal title background image :-)!

Important hint: The Free Pascal compiler got a new stable release on 25th November of 2015, version 3.0.0. Don’t forget to update your favourite compiler :-)!

Music and Sound

Last updated on March 5th, 2023

This chapter will introduce you on how to load music and sounds since these are key features of every game and many applications.

SDL_mixer 2.0 for easy music and sound support

Although SDL 2.0 supports music and sound handling natively, there is an easier way to play music and sound files. The official unit SDL_mixer 2.0 (unit’s name SDL2_mixer) has been created exactly for this purpose and is maintained by the same authors (Sam Lantinga, Stephane Peter, Ryan Gordon) as SDL 2.0 itself. The Pascal translation is fortunately available, too in Tim Blume’s header translations.

Supported music and sound file formats in SDL 2.0

According to the official SDL2_mixer documentation the following music and sound formats are supported:

  • WAVE/RIFF (.wav)
  • AIFF (.aiff)
  • VOC (.voc)
  • MOD (.mod .xm .s3m .669 .it .med and more) requiring libmikmod on system
  • MIDI (.mid) using timidity or native midi hardware
  • OggVorbis (.ogg) requiring ogg/vorbis libraries on system
  • MP3 (.mp3) requiring SMPEG or MAD library on system
  • FLAC (.flac) requiring the FLAC library on system

You’ll need these files:

Software
Version
File name
Link
Description
SDL2_mixer dynamic link library
2.0.1
SDL2_mixer-2.0.1-win32-x86.zip (32-bit Windows)
SDL2_mixer-2.0.1-win32-x64.zip (64-bit Windows)
https://www.libsdl.org/projects/SDL_mixer/
This is the corresponding dynamic link library file for the unit for Windows. Note: Mac OS X and Linux versions are also available.
dial.wav sound file dial.wav
dial.wav
A simple telephone dial sound. Source: pdsounds. License: Public domain.
In my mind.ogg music file In my mind.ogg
In my mind.ogg
A nice music sample. Source/Creator: First. License: CC BY-ND 3.0.
music-menu.bmp image file music-menu.bmp
music-menu.bmp
A simple bitmap image file which displays the menu options for the tutorial program.

You should extract the zip-file and get several files. These are four license text files and a readme text file, furthermore you have eight dll files including the important SDL2_mixer.dll. The other dll files are necessary to play the different sound and music formats. Copy all the files to the Windows system32-folder (or the corresponding place). If you forget this and run the examples below you will get an error with exitcode = 309.

Now that you are prepared, let’s have an overview of the code.

program SDL_MusicSound;
uses SDL2, SDL2_mixer;

var
  sdlWindow: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface: PSDL_Surface;
  sdlTexture: PSDL_Texture;
  sdlKeyboardState: PUInt8;
  Music: PMix_Music;
  Sound: PMix_Chunk;
  Run: Boolean = True;

begin
  if SDL_Init(SDL_INIT_VIDEO or SDL_INIT_AUDIO) < 0 then Exit;

  // Get window and renderer
  if SDL_CreateWindowAndRenderer(
    640, 640, SDL_WINDOW_SHOWN, @sdlWindow,@sdlRenderer) <> 0 then Exit;

  // Create and render menu texture
  sdlSurface := SDL_LoadBMP('music-menu.bmp');
  if sdlSurface = nil then Exit;
  sdlTexture := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface);
  if sdlTexture = nil then Exit;
  if SDL_RenderCopy(sdlRenderer, sdlTexture, nil, nil) <> 0 then Exit;
  SDL_RenderPresent(sdlRenderer);

  // Prepare mixer
  if Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
    MIX_DEFAULT_CHANNELS, 4096) < 0 then Exit;

  // Load music
  Music := Mix_LoadMUS('In-my-mind-1.ogg');
  if Music = nil then Exit;
  Mix_VolumeMusic(MIX_MAX_VOLUME);

  // Load sound
  Sound := Mix_LoadWAV('dial-1.wav');
  if Sound = nil then Exit;
  Mix_VolumeChunk(Sound, MIX_MAX_VOLUME);

  while Run = True do
  begin

    SDL_PumpEvents;
    sdlKeyboardState := SDL_GetKeyboardState(nil);

    // ESC pressed
    if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
      Run := False;

    // Music effect keys
    if sdlKeyboardState[SDL_SCANCODE_1] = 1 then
      if Mix_PlayMusic(Music, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_2] = 1 then Mix_PauseMusic;
    if sdlKeyboardState[SDL_SCANCODE_3] = 1 then Mix_ResumeMusic;
    if sdlKeyboardState[SDL_SCANCODE_4] = 1 then Mix_RewindMusic;
    if sdlKeyboardState[SDL_SCANCODE_5] = 1 then
      if Mix_FadeInMusic(Music, 10, 3000) = 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_6] = 1 then
      if Mix_FadeOutMusic(3000) = 0 then Writeln(SDL_GetError);

    // Sound effect keys
    if sdlKeyboardState[SDL_SCANCODE_7] = 1 then
      if Mix_PlayChannel(1, Sound, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_8] = 1 then
      if Mix_PlayChannel(-1, Sound, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_9] = 1 then Mix_Pause(-1);
    if sdlKeyboardState[SDL_SCANCODE_0] = 1 then Mix_Resume(-1);
    if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
      if Mix_FadeInChannel(1, sound, 0, 2000) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
      if Mix_FadeOutChannel(1, 1000) < 0 then Writeln(SDL_GetError);

    // Channel effect keys
     if sdlKeyboardState[SDL_SCANCODE_G] = 1 then
       if Mix_SetPanning( 1, 255, 32 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_H] = 1 then
       if Mix_SetPanning( 1, 255, 255 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_J] = 1 then
       if Mix_SetDistance( 1, 223 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_K] = 1 then
       if Mix_SetDistance( 1, 0 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_L] = 1 then
       if Mix_SetPosition( 1, 45, 127 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_M] = 1 then
       if Mix_SetPosition( 1, 0, 0 ) = 0 then Writeln(SDL_GetError);

  end;

  // Clean up
  if Assigned(sdlSurface) then SDL_FreeSurface(sdlSurface);
  if Assigned(sdlTexture) then SDL_DestroyTexture(sdlTexture);
  if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
  if Assigned(sdlWindow) then SDL_DestroyWindow(sdlWindow);
  if Assigned(Music) then Mix_FreeMusic(Music);
  if Assigned(Sound) then Mix_FreeChunk(Sound);

  Mix_CloseAudio;
  SDL_Quit;
end.

When running this program, you’ll get a window with a menue. For this to work, make sure to download the music-menu.bmp file and place it in the program folder.

Let’s start with the first part of the code.


program SDL_MusicSound;
uses SDL2, SDL2_mixer;

var
  sdlWindow: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlSurface: PSDL_Surface;
  sdlTexture: PSDL_Texture;
  sdlKeyboardState: PUInt8;
  Music: PMix_Music;
  Sound: PMix_Chunk;
  Run: Boolean = True;

begin
  if SDL_Init(SDL_INIT_VIDEO or SDL_INIT_AUDIO) < 0 then Exit;

  // Get window and renderer
  if SDL_CreateWindowAndRenderer(
    640, 640, SDL_WINDOW_SHOWN, @sdlWindow,@sdlRenderer) <> 0 then Exit;

  // Create and render menu texture
  sdlSurface := SDL_LoadBMP('music-menu.bmp');
  if sdlSurface = nil then Exit;
  sdlTexture := SDL_CreateTextureFromSurface(sdlRenderer, sdlSurface);
  if sdlTexture = nil then Exit;
  if SDL_RenderCopy(sdlRenderer, sdlTexture, nil, nil) <> 0 then Exit;
  SDL_RenderPresent(sdlRenderer);

The program is called “SDL”. It uses the SDL2 and the new SDL2_mixer unit.

We’re going to use event handling as discussed in Chapter – Keyboard State and Key States to check what keys have been pressed. Consequently we need a variable “sdlWindow” for the SDL 2.0 window and a keyboard state variable “sdlKeyboardState”.

The pointer variable “Music” is of the new type PMix_Music and points at the loaded music file data later. The “Sound” pointer variable is of type PMix_Chunk and points at the loaded sound file data later. Note: Each 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.

What is the difference between music and sound?

Music is associated with PMix_Music pointers and sounds are associated with PMix_Chunk pointers, but why distinguish them at all? – Well, it is convenient to have them separated since they have rather different properties. Music is usually several minutes long, sounds are usually just a few seconds short. It is usually just one song played at a time, sounds need to mix up if necessary. For music it usually doesn’t plays a role if it is sligthly delayed to when it should start, for sounds a larger delay usually means a strange feeling for the player. So, for music there is exactly one channel reserved where the music is played. For sounds eight sound channels are available to make it possible for them to mix up. Now let’s get back to the code.

The variable “Run” is a boolean variable to determine when the main loop of the program should be exited.

The program is initialized by SDL_Init as known. Note though that not only SDL_INIT_VIDEO but additionally SDL_INIT_AUDIO is set. They are combined by the or operator (not the and operator :-)!).  The window is set up afterwards by SDL_CreateWindow as known. Also the menu texture file is created and renderered as known. Check Chapter – Loading and rendering a Bitmap file as a reminder, in case.


  // Prepare mixer
  if Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
    MIX_DEFAULT_CHANNELS, 4096) < 0 then Exit;

  // Load music
  Music := Mix_LoadMUS('In-my-mind-1.ogg');
  if Music = nil then Exit;
  Mix_VolumeMusic(MIX_MAX_VOLUME);

  // Load sound
  Sound := Mix_LoadWAV('dial-1.wav');
  if Sound = nil then Exit;
  Mix_VolumeChunk(Sound, MIX_MAX_VOLUME);

To initialize SDL2_mixer

function Mix_OpenAudio(frequency: cint; format: cuint16; channels: cint; chunksize: cint): cint

has to be called. It requests four parameters. These are the frequency, the audio format, the channels (mono or audio) and the chunksize. All these parameters are of Integer type. Function Mix_OpenAudio returns 0 on success and -1 on errors.

The frequency in Hertz (1/s) usually is 22050 Hz or MIX_DEFAULT_FREQUENCY in games but double as high for CD quality sound (44100 Hz). The higher the frequency, the more CPU power is needed.

Next we have to define the audio format. It determines how the audio data is stored. MIX_DEFAULT_FORMAT equals AUDIO_S16SYS. Different values are given in the following list (from official SDL2_mixer documentation):

  • Format constant name 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 (for backwards compatability probably)
  • AUDIO_S16 same as AUDIO_S16LSB (for backwards compatability probably)
  • 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 channel type, which means either stereo or mono. For stereo sound we choose 2 and for mono we choose 1 as value. MIX_DEFAULT_CHANNELS equals stereo output.

Finally the chunksize has to be set, where 4096 bytes per sample is a good and default value. Too low values may lead to skipping samples. Too high values may lead to delayed playing.

Loading music and sounds from files in SDL 2.0

We will load the music by

function Mix_LoadMUS(_file: PAnsiChar): PMix_Music.

_file can be the absolute path. If the Pascal file and the music file are in the same folder the file name is sufficient (as shown in the example). If the loading was unsuccessful, the function returns nil. The volume can be set by

function Mix_VolumeMusic(volume: cint): cint.

The volume can be set between 0 (silence) to 128 (maximum volume). The latter equals MIX_MAX_VOLUME. By the way, if you set -1 as argument, the return value corresponds to the set volume.

Quite similar sound files are loaded by

function Mix_LoadWAV(_file: PAnsiChar): PMix_Chunk.

Although the function’s name doesn’t indicate this, use this function to load the sound files of any format (Wave, Aiff, Riff, Ogg, Voc), not only for .wav sound files. It return nil on error. There is alittle difference when setting the volume by

function Mix_VolumeChunk(chunk: PMix_Chunk; volume: cint): cint.

Instead of just setting a general volume, the volume is bound to a certain sound. In the example case the sound is in variable “sound”. Just to mention it here, there is a third possibility, to set the volume for a certain channel:

function Mix_Volume(channel: cint; volume: cint): cint.

This can be quite handy, anyway, channels are discussed a little bit later below. If your argument is -1 the average volume will be returned.

Playing, pausing, resuming music and sounds


  while Run = True do
  begin

    SDL_PumpEvents;
    sdlKeyboardState := SDL_GetKeyboardState(nil);

    // ESC pressed
    if sdlKeyboardState[SDL_SCANCODE_ESCAPE] = 1 then
      Run := False;

    // Music effect keys
    if sdlKeyboardState[SDL_SCANCODE_1] = 1 then
      if Mix_PlayMusic(Music, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_2] = 1 then Mix_PauseMusic;
    if sdlKeyboardState[SDL_SCANCODE_3] = 1 then Mix_ResumeMusic;
    if sdlKeyboardState[SDL_SCANCODE_4] = 1 then Mix_RewindMusic;
    if sdlKeyboardState[SDL_SCANCODE_5] = 1 then
      if Mix_FadeInMusic(Music, 10, 3000) = 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_6] = 1 then
      if Mix_FadeOutMusic(3000) = 0 then Writeln(SDL_GetError);

    // Sound effect keys
    if sdlKeyboardState[SDL_SCANCODE_7] = 1 then
      if Mix_PlayChannel(1, Sound, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_8] = 1 then
      if Mix_PlayChannel(-1, Sound, 0) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_9] = 1 then Mix_Pause(-1);
    if sdlKeyboardState[SDL_SCANCODE_0] = 1 then Mix_Resume(-1);
    if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
      if Mix_FadeInChannel(1, sound, 0, 2000) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
      if Mix_FadeOutChannel(1, 1000) < 0 then Writeln(SDL_GetError);

The main application loop is entered and should be left if variable “Run” is false which is achieved by pressing the ESCAPE key.

For each key state a different music or sound related function is used to do something. Let’s discuss them now in detail. We start with the functions triggered by the keys 1 – 4:

function Mix_PlayMusic(music: PMix_Music; loops: cint): cint 
procedure Mix_PauseMusic 
procedure Mix_ResumeMusic 
procedure Mix_RewindMusic

Mix_PlayMusic plays music. The first argument is the music itself which we previously got from the example .ogg music file and associated with the “Music” pointer of PMix_Music type. The second argument determines how often the music is played. Additionally -1 means infinite repetition. This function returns 0 on success and -1 on error.

The meaning of the three procedures Mix_PauseMusic, Mix_ResumeMusic and Mix_RewindMusic ist obvious from their names. They are used to pause, resume (if paused before) and rewind the played music.

The analogous function and procedures for sound channels triggered by the keys 7 – 9 and 0 are shown now:

function Mix_PlayChannel(channel: cint; chunk: PMix_Chunk; loops: cint): cint
procedure Mix_Pause(channel: cint) 
procedure Mix_Resume(channel: cint)

As you can see the function Mix_PlayChannel has three parameters. The first parameter asks for a channel with which the sound should be associated. SDL2_mixer provides eight sound channels. Usually you should use -1 which means the first free channel will be used to play the sound. Anyway, you could specifiy a certain channel here if you like. The second parameter asks for a sound pointer of PMix_Chunk type. The  third parameter is the same as for music, -1 means an infinite loop. Attention though for specific repetitions. The value for loops is increased by 1. So, if you want a sound to be played once, you need the argument to be 0. The return value corresponds to the sound channel the sound is played on, it is -1 on error.

The procedures Mix_Pause and Mix_Resume are self-explanatory. Use them to pause or resume a certain channel, which is the argument. If the argument is -1 (as in the example code) you pause or resume all channels. By the way, you may have noticed that there is not a rewind analogue, because sounds are not expected to be rewound.

Fading effects


...

    if sdlKeyboardState[SDL_SCANCODE_5] = 1 then
      if Mix_FadeInMusic(Music, 10, 3000) = 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_6] = 1 then
      if Mix_FadeOutMusic(3000) = 0 then Writeln(SDL_GetError);

    // Sound effect keys
    
...
    
    if sdlKeyboardState[SDL_SCANCODE_A] = 1 then
      if Mix_FadeInChannel(1, sound, 0, 2000) < 0 then Writeln(SDL_GetError);
    if sdlKeyboardState[SDL_SCANCODE_S] = 1 then
      if Mix_FadeOutChannel(1, 1000) < 0 then Writeln(SDL_GetError);

    // Channel effect keys
     if sdlKeyboardState[SDL_SCANCODE_G] = 1 then
       if Mix_SetPanning( 1, 255, 32 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_H] = 1 then
       if Mix_SetPanning( 1, 255, 255 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_J] = 1 then
       if Mix_SetDistance( 1, 223 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_K] = 1 then
       if Mix_SetDistance( 1, 0 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_L] = 1 then
       if Mix_SetPosition( 1, 45, 127 ) = 0 then Writeln(SDL_GetError);
     if sdlKeyboardState[SDL_SCANCODE_M] = 1 then
       if Mix_SetPosition( 1, 0, 0 ) = 0 then Writeln(SDL_GetError);

  end;

Important to know about effects like fading. They are performed on the fly to your data, so the more effects you use, the more computational power they require. It is important to unregister effects you don’t need anymore.

These are the four fade functions triggered by the keys 5, 6, A and S :

function Mix_FadeInMusic(music: PMix_Music; loops: cint; ms: cint): cint 
function Mix_FadeOutMusic(ms: cint): Integer 
function Mix_FadeInChannel(channel: cint; chunk: PMix_Chunk; loops: cint; ms: cint): cint
function Mix_FadeOutChannel(which: cint; ms: cint): cint

The function Mix_FadeInMusic works the very same way as Mix_PlayMusic above, but there is a further parameter which allows for a fade in effect in milliseconds. 3000 milliseconds equals 3 seconds. It returns 0 on success and -1 on error.

Mix_FadeOutMusic fades out the playing music within the given time in milliseconds in the moment the function is called (hence the key is pressed in the example program). Attention, it returns 1 on success and 0 on error.

Quite similar the functions Mix_FadeInChannel and Mix_FadeOutChannel work. Instead of the music pointer, the sound pointer and the channel has to be given. By the way, also here the number of times the sound is played is increased by 1, so if you want to play a sound with fade in effect once, the argument has to be 0. The fade effect applies only for the first run. Both functions return 0 on success and -1 on error.

Panning and distance effects

The functions triggered by the keys G – M are:

function Mix_SetPanning(channel: cint; left: cuint8; right: cuint8): cint

For setting the panning you need the channel to which panning should be applied and the volume values which may vary from 0 (quiet) to 255 (loudest). For a consistent panning effect you may substract the left value from the right value (left: volume, right: 255-volume). To unregister this effect, you should set both values to 255, which is recommended to be done if you don’t need panning anymore. The function returns 0 on errors.

function Mix_SetDistance(channel: cint; distance: cuint8): cint

The higher the distance, the quieter the sound appears. The value can range between 0 (near) and 255 (far). If distance is set to 0 the effect is unregistered. It returns 0 on errors.

function Mix_SetPosition(channel: cint; angle: cint16; distance: cuint8): cint

This function adjusts volumes according to an angle and a distance value, hence it combines the effects of the two functions discussed before for panning and distance. The distance value works the same way as discussed for Mix_SetDistance. Angle of 0 (degree) means in front of you, 90 means directly to the right, 180 means directly behind, 270 means directly to the left.

Closing the SDL2 mixer and clean up


  // Clean up
  if Assigned(sdlSurface) then SDL_FreeSurface(sdlSurface);
  if Assigned(sdlTexture) then SDL_DestroyTexture(sdlTexture);
  if Assigned(sdlRenderer) then SDL_DestroyRenderer(sdlRenderer);
  if Assigned(sdlWindow) then SDL_DestroyWindow(sdlWindow);
  if Assigned(Music) then Mix_FreeMusic(Music);
  if Assigned(Sound) then Mix_FreeChunk(Sound);

  Mix_CloseAudio;
  SDL_Quit;
end.

For the clean up the surface, textre renderer and the window are destroyed as known. New are the procedures Mix_FreeMusic and Mix_FreeChunk to free the music and sound data.

procedure Mix_FreeMusic(music: PMix_Music)
procedure Mix_FreeChunk(chunk: PMix_Chunk)

The arguments have to be the music and sound pointer respectively.

SDL_mixer 2.0 has to be closed by

procedure Mix_CloseAudio.

Finally SDL 2.0 and the application are shut down as known.

← previous Chapter | next Chapter →

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”)