USING SXMS FOR SOUND/BGM

Detailed tutorial on how to use the FMOD music system's Game Maker extension to play, stop, restart and loop background music and sound effects.

  • Pasty
  • 10/11/2009 04:04 AM
  • 4785 views
Using SXMS for Sound/BGM


Update History:

6/12/2011: Added version data and resubmitted to fix spacing errors in the code blocks.

Compatible with:

Game Maker 6.x Full
Game Maker 7 Pro

Table of Contents:

- Intro
- The Basics
- Setting Up
- The SoundBank
- Music Controls
- scrPlayBGM()
- scrStopBGM()
- scrRestartBGM()
- Fader
- Looping BGM
- Sound Controls
- Using an FSB file for storing sounds

Intro

Hello. Let's talk about playing music in Game Maker. This tutorial is intended for people who'd like to use external music with a proven sound engine (FMOD). SXMS is basically an FMOD dll wrapper that was made by a guy named Shaltif (you can find him on the Game Maker Community forums). This allows you to use all the functions of FMOD in Game Maker Pro/Standard with very little transparency. This is also why a lot of people think it's hard to use. Since there's no real documentation to speak of except in the individual scripts, it kind of is. It's a very powerful tool, though, and I think it'd be worth your while to learn the ins and outs of this framework if you want to use Game Maker for anything substantial.

In order to utilize this tutorial without a problem, you'll need to have the Pro/Standard edition of Game Maker, due to the following mechanisms:

- DLL extensions

You'll also need a rudimentary understanding of the Game Maker Language (GML) since this is almost entirely done with code.

The Basics

An important thing to realize about SXMS is that it is almost entirely dependent on the FMOD middleware. SXMS simply allows someone using Game Maker to access FMOD. Another important thing to realize is that SXMS is built to be as transparent to FMOD as possible. It is not built for ease of use; it's built for power. There are sx_easy scripts that distill the process of outputting sound down to its least taxing, but I won't be discussing those, since they don't afford the control that certain things in this tutorial require, and they are frankly an underutilization of such great middleware. Just know they're available if you'd like to use those instead. I also won't be talking about the DSP functions of SXMS, which I'm sure are great. I just have no idea how to use them.

So, let's take a look at some of the common terms:

Sound File - this refers to any sound file that FMOD can utilize, from .au to .xma. Also includes .fsb packs that bundle sounds together for ease of use and protection.
Sound - the sound as loaded into SXMS. A Sound may be loaded into SXMS with any number of properties applied to it. Let's take a look at some of the properties a sound can have:

FMOD_LOOP_OFF

FMOD_LOOP_NORMAL
FMOD_2D
FMOD_SOFTWARE
FMOD_CREATESTREAM
FMOD_ACCURATETIME


Of course, these aren't all the properties you can use, but just a selection that is tailored to our needs.

Channel - a channel is what you use to actually PLAY a sound. It's also where you do all your processing. The sound's volume, position and state (paused/playing) are set through the CHANNEL, not the sound. The only properties that are set through the sound for the purpose of this tutorial are the loop points.

Setting Up

Okay, so the first thing you need to do is download SXMS from here. Read the instructions! You'll need a dummy sxms object in your Game Maker file, and you'll need both the SXMS and FMOD DLLs in your game's working directory.

You'll want to set up your sound system at the beginning of your game (or before you start to play the first sound, obviously). Here's how you do it.

sxms_create(working_directory+"\SXMS-3.dll",8);  

//sxms_create(LOCATION OF DLL, WHETHER ERROR MESSAGES SHOULD DISPLAY)
sxfmod_system_create();
//creates the system
sxfmod_system_setOutput(sxms.FMOD_OUTPUTTYPE_WINMM);
//as of the last release, DirectSound is not supported, so we're using WINMM instead
sxfmod_system_init(8,sxms.FMOD_INIT_NORMAL,0);
//sxfmod_system_init(NUMBER OF CHANNELS, ADDL. FLAGS, PROJECT HANDLE)
sxms.FMOD_CREATESOUNDEXINFO[15] = "abc123";
//key for decrypting an encrypted sound pack file (FSB), ignore for now


An important thing to note about sxfmod_system_init() is that it allows you to set the maximum amount of channels. This is the maximum number of channels you can play sounds on simultaneously before channel cutting or stealing occurs. It's why I have my sounds playing through seven channels and my BGM playing through only one.

I have that above part wrapped in an if statement that checks if sound is enabled in my game engine, so I can turn it off by setting a variable to FALSE. It's a good tool to use.

The SoundBank

The SoundBank is an object that I created for the purpose of storing every sound index that my game uses. It also holds various variables that relate to the current BGM, the channel for the BGM 'handle' and the variables that cycle the channels for my sound effects. It also includes a fader, since I don't really like doing gradual stuff through scripts. So, I created the SoundBank.

In the SoundBank Create event:

sxfmod_system_createSound(working_directory+"\sound\sound.fsb",

sxms.FMOD_SOFTWARE|sxms.FMOD_2D,1,1);
//sxfmod_system_createSound(PATH, FLAGS, DOES IT USE CREATESOUNDEXINFO?, THE INDEX TO ASSIGN THE SOUND)

fade = false; //is the current track fading
fader = -1; //fade timer
fade_duration = -1; //fade length

bgmChannel = -1; //this variable stores the BGM channel handle
currentBGM = -1;
//this variable stores the current BGM index that's playing, so you can reference it later if you need to

sxfmod_system_createChannelGroup(0);
//i'll explain this later, it allows you to change the volume of all sound channels instead of just one

se_cycle = 0; //cycle variable for assigning sounds to channels


I loaded all my sound effects here because there's very little overhead for the sounds in my game. The start and end loop points are also stored here for ease of access.

Now, here's a couple of things about sxfmod_system_createSound():

- For the flags, you want sxms.FMOD_SOFTWARE|sxms.FMOD_2D for sounds. You don't want to stream them using sxms.FMOD_CREATESTREAM. Why is this? It's simple: when you access a file multiple times under CREATESTREAM mode, the sound will start over no matter how many channels you have open. This might be acceptable for BGM, but it's the kiss of death for sounds, since they'll restart and make this terrible cutting sound because they're streaming and have to access the file, which starts the file over when using CREATESTREAM. It took me forever to figure this out.
- When you're using the CREATESOUNDEXINFO array for defining certain aspects of the SXMS wrapper, it allows you to set a lot of things. The only thing I really concern myself with in this tutorial is CREATESOUNDEXINFO, which allows you to decrypt an FSB pack of multiple sounds that you've encrypted using the FMOD FSB pack maker by setting the variable to the password you've made for this file. Again, if you're using individual sounds, disregard this.
- sxfmod_system_createSound() does not return the sound you've created! Refer to the sound by the index you gave it when creating the sound (in this instance, it's 1 because I'm storing the music pack at 0).

Then, I defined each sound index I was using in a variable in SoundBank, so I could easily refer to it in the engine by typing "SoundBank.name_of_sound".

titlescreen = working_directory+"\sound\BGM01.mp3";           //01:  Title Screen

mainmenu = working_directory+"\sound\BGM02.mp3"; //02: Main Menu
training = working_directory+"\sound\BGM03.mp3"; //03: Training


...and so on. Remember that you can only store ONE sound file per index. It may be self-explanatory, but you'd be surprised.

Music Controls

Now that you have that all set, it's time to play some music. We'll get to sounds a little later. I like doing this in a script that I can easily call and pass the filename or sound index (in the case of subsounds, another wonderful feature of FSBs) to. For instance, scrPlayBGM(SoundBank.menu_cursor). You can easily work FSB support into this script with the addition of a few lines.

scrPlayBGM(string filename):

var bgm;


bgm = argument0;

with(SoundBank)
{
if(bgm = currentBGM) exit; //do not restart a BGM if it's already playing
scrStopBGM(); //see below

sxfmod_system_createSound(bgm,
sxms.FMOD_ACCURATETIME|sxms.FMOD_LOOP_NORMAL|sxms.FMOD_CREATESTREAM|sxms.FMOD_SOFTWARE|sxms.FMOD_2D,
1,0);

bgmChannel = sxfmod_system_playSound(0, 0, 1); //sxfmod_system_playSound(SOUND INDEX,
CHANNEL TO PLAY SOUND ON, START PLAYING (0) OR PAUSED (1)?);

currentBGM = bgm; //tells you which BGM is currently playing; important for not restarting music

sxfmod_channel_setVolume(bgmChannel, Environment.config_musicVol);
//sxfmod_channel_setVolume(CHANNEL HANDLE, VOLUME LEVEL FROM 0 TO 1)
sxfmod_channel_setPaused(bgmChannel, 0);

//sxfmod_channel_setVolume(CHANNEL HANDLE, PLAY (0) OR PAUSE (1)?)
}


As you can see above, I like to start a sound paused in order to do stuff to it before it actually begins. A couple things:

- if(bgm = currentBGM) exit; . This is a failsafe to make sure you don't restart a BGM if it's already playing. It's best to have an explicit restart script that you call to restart a BGM. I know I have some references to scrPlayBGM in room creation code and I know my player'd get pretty annoyed if I restarted the same BGM every room I went to.
- CHANNEL TO PLAY SOUND ON and CHANNEL HANDLE are two different things. I play the sound on channel 0 because I know that's where I want the sound to play, but I'm never going to directly reference channel 0 after I tell the sound to play. From the point at which I play the sound and onward, I'll refer to the channel by the variable bgmChannel, which contains the channel HANDLE returned from the procedure that actually plays the sound.
- More flags here. sxms.FMOD_ACCURATETIME is used for looping. So is sxms.FMOD_LOOP_NORMAL. If you don't need music to loop (this is pretty unlikely), omit these. We'll get to looping a little later.
- sxms.FMOD_CREATESTREAM creates a stream so you can avoid loading the entire file and thus disrupting the game. Good for large files, and not so much for small files or files that repeat constantly (see the time I created the FSB file for all the sounds back when I made the soundBank for an example).

Now, you'll notice something about scrPlayBGM: that it continually opens sounds without closing them. This is bad news. This leads to memory hogging and even crashes if you're not careful. So we want to stop the BGM and free the memory before playing another one. This is best done with a script.

scrStopBGM():

with(SoundBank)

{
if(bgmChannel > 0) //if bgmChannel references a valid channel handle
{
if(sxfmod_channel_isPlaying(bgmChannel)) //sxfmod_channel_isPlaying(CHANNEL HANDLE)
{
sxfmod_channel_stop(bgmChannel); //sxfmod_channel_stop(CHANNEL HANDLE)
sxfmod_sound_release(0); //sxfmod_channel_stop(SOUND INDEX)
}
}

bgmChannel = -1; //reset these variables
currentBGM = -1;
}


It's important to note that while the sound is started via the internal SXMS system and is considered a separate object in its own right, it is stopped via the channel. Weird, huh?

scrRestartBGM():

with(SoundBank)

{
sxfmod_channel_setPosition(bgmChannel, 0, sxms.FMOD_TIMEUNIT_MS);
//sxfmod_channel_setPosition(CHANNEL HANDLE, POSITION, TIME UNIT);
sxfmod_channel_setVolume(bgmChannel, Environment.config_musicVol);
//sxfmod_channel_setVolume(CHANNEL HANDLE, VOLUME LEVEL);
}

That's a simple one. Let's move on to the fader.

The fader does exactly what it says it does: it fades the BGM. I didn't really feel a need for fading IN music, so it's not explained here. If you're enterprising, you can just reverse this process.

Now, since fading is a gradual event, I don't really want to use a script for the actual fading process, even though I use a script to START it. Instead, I'll place the fading code in the Step event of the SoundBank.

SoundBank:Step

if(fade)

{
fader -= 1/fade_duration; //May not be time-accurate, but it gets the job done.
At 60fps, this fades reliably based on the duration.

if(bgmChannel > 0)sxfmod_channel_setVolume(bgmChannel,fader);
//sxfmod_channel_setVolume(CHANNEL HANDLE, VOLUME LEVEL);

if(fader <= 0) //when we're done fading, close the track. if you want to be good,
you can pass the next track you want to play and play it in this decision statement
{
scrStopBGM();
fader = 0; //let's reset all the variables
if(bgmChannel > 0)sxfmod_channel_setVolume(bgmChannel,Environment.config_musicVol);
//sxfmod_channel_setVolume(CHANNEL HANDLE, VOLUME LEVEL);
fade_duration = -1;
fade = false;
}
}


Now, all we need is something to start the fading process.

scrFadeBGM():

var duration;


duration = argument0;

with(SoundBank)
{
fade_duration = duration;
fader = Environment.config_musicVol;
fade = true;
}


Okay, so we're done with the fader. The last thing to do is include the volume control.

scrSetVolume(double volume):

var vol;


vol = argument0;

if(!SoundBank.fade)
{
if(sxfmod_channel_isPlaying(SoundBank.bgmChannel))
sxfmod_channel_setVolume(SoundBank.bgmChannel,vol);
}
else return -1;


As you can see, fading overrides the volume control.

Looping BGM

We all want to loop BGM, right? So, let's do that. SXMS lets you loop from any start point or end point in the sound. It's very useful.

What I did was define my loop points in the SoundBank when I defined the sound variables. For example:

startPt = 0;

endPt = 1;

bgm_titlescreen = working_directory+"\sound\BGM01.mp3";

loop[0, startPt] = 0; loop[0, endPt] = 9999999;


It's simple enough. Then you add code in scrPlayBGM() that sets the loop points using these variables. In mine, a startPt of -1 means the song does not loop. You'll need a bgmid variable; this is basically where your BGM is in the loop array. Find a way to get it. You could probably set it in the SoundBank.

scrPlayBGM(string filename):

var bgm, bgmid, loopend;


bgm = argument0;

with(SoundBank)
{
if(bgm = currentBGM) exit; //do not restart a BGM if it's already playing
scrStopBGM(); //see below

sxfmod_system_createSound(bgm,
sxms.FMOD_ACCURATETIME|sxms.FMOD_LOOP_NORMAL|
sxms.FMOD_CREATESTREAM|sxms.FMOD_SOFTWARE|sxms.FMOD_2D,
1,0);

bgmChannel = sxfmod_system_playSound(0, 0, 1); //sxfmod_system_playSound(SOUND INDEX,
CHANNEL TO PLAY SOUND ON, START PLAYING (0) OR PAUSED (1)?);

//*NEW*

if(loop[bgmid, startPt] = -1)
{
//song does not loop
sxfmod_sound_setLoopCount(0,0);
//sxfmod_sound_setLoopCount(SOUND INDEX,NUMBER OF TIMES TO LOOP);
}
else
{
if(loop[bgmid, endPt] = 9999999) loopend = sxfmod_sound_getLength(2,sxms.FMOD_TIMEUNIT_MS) - 1;
//this grabs the length in the instance that the sound loops at the end
else loopend = loop[bgmid, endPt];

sxfmod_sound_setLoopPoints(2,loop[bgmid, startPt],sxms.FMOD_TIMEUNIT_MS,
loopend,sxms.FMOD_TIMEUNIT_MS);

//sxfmod_sound_setLoopPoints(SOUND INDEX,START LOOP POINT,START LOOP TIME UNIT,
END LOOP POINT,END LOOP TIME UNIT);
}

//*END OF NEW*

currentBGM = bgm; //tells you which BGM is currently playing; important for not restarting music

sxfmod_channel_setVolume(bgmChannel, Environment.config_musicVol);
//sxfmod_channel_setVolume(CHANNEL HANDLE, VOLUME LEVEL FROM 0 TO 1)
sxfmod_channel_setPaused(bgmChannel, 0);
//sxfmod_channel_setVolume(CHANNEL HANDLE, PLAY (0) OR PAUSE (1)?)
}


Sound Controls

Let's tackle sound effects now. This is essentially an addendum to the scripts we made for music, except we need to provide a means to switch between channels. Now, you use one channel for BGM, and you've got seven left, since we'd defined eight when we instanced the SXMS engine. So, what do you do with these remaining channels? The way I have it set up in my game engine is that I use five channels for sound effects and two channels for ambient sound effects, and I roll the ambient sound effects in based on the area, since I don't really have a need for two ambient sound effects to be playing at the same time. I'm going to cover the route you would take if you decided to dedicate all seven remaining channels to regular sound effects.

This requires that we use the Channel Group we defined way back when we created the SoundBank. A Channel Group is (predictably) a way to group channels so that you can affect all channels in the group with one command. This is how we're going to do our volume control for sound effects, since they're all on separate channels.

var sound, index;


sound = argument0;
index = argument1; //this is for deciding what index to store the sound in.
since you're dealing with multiple sounds, you need to store each of them in a separate index

with(SoundBank)
{
sxfmod_system_createSound(sound, sxms.FMOD_SOFTWARE|sxms.FMOD_2D,1,index);

se[se_cycle] = sxfmod_system_playSound(index, se_cycle + 1, 1);

sxfmod_channel_setChannelGroup(se[se_cycle], 0); //sxfmod_channel_setChannelGroup(CHANNEL HANDLE, CHANNEL GROUP NUMBER);
sxfmod_channelGroup_setVolume(0, Environment.config_soundVol);
//works the same way as the one used for individual channels

sxfmod_channel_setPaused(se[se_cycle], 0);

se_cycle += 1;

if(se_cycle >= 7) //we'll rotate through the channels
{
se_cycle = 0;
}
}


A couple notes:

- This code requires that you pass an index with the sound. When you use an FSB, you can just pass the sound ID and use that to offset the index.
- We set the channel to the channel group immediately after we start playing. That channel belongs to that group now, and any command that modifies that group will modify the sound.
- We'll rotate through the channels so that it is highly unlikely that there will be two sound effects played on a channel at one time. This prevents cutting across channels.

Now, when you modify the volume control, you can pass two arguments to our scrSetVolume() script instead of one. You can pass a value for music and a value for sound. Then, put this code in the script:

if(sxfmod_channelGroup_getNumChannels(0) > 0)

sxfmod_channelGroup_setVolume(0,soundVolume);


Using an FSB file for storing sounds

This last segment has to do with FSB files, or sound 'packages'. For my game, I have two FSB files, one for music (about 100MB) and one for sound effects (about 4MB). The SXMS zip that comes with FMOD should come with FSBank, a program that will allow you to compile an FSB. There's a basic tutorial included with FSBank on how to use it; I don't really want to cover it here. The only thing I want to note is that there's a box for an encryption key in FSBank.

The value in the encryption key box and the value you defined in CREATESOUNDEXINFO have to match or the file will be unusable! Also, you have to make sure when you create a sound that you set the USE CREATESOUNDEXINFO argument to 1.

Now that that's in order, let's begin. You use an FSB in the same manner as an unpacked sound file:

sxfmod_system_createSound(working_directory+"\sound\music.fsb",

sxms.FMOD_SOFTWARE|sxms.FMOD_CREATESTREAM|sxms.FMOD_2D,1,0);


Once you've created the sound, you'll need a subsound from it. However many sounds you packed into the file is how many subsounds you have. They're all in the order they were in when you created the FSB, so keep a list handy. You'll want to add this to your BGM/Sound scripts if you're using FSBs:

sxfmod_sound_getSubSound(MASTER SOUND INDEX, SUBSOUND INDEX, INDEX IN WHICH TO STORE THE SUBSOUND);


Then you just play the subsound using sxfmod_system_playSound(). When you want to stop (as in scrStopBGM()), you can release the entire pack if you want. In fact, a disadvantage of using an FSB is that you CANNOT CLOSE the FSB and expect to still be able to access the subsound.

That's pretty much it. If you have any questions or corrections, please let me know and I'll get right on it. I hope this helped some people!
Pages: 1