Purple Martians
Technical Code Descriptions
Sound
Overview
Sound setup
Mixer volume controls
Theme music
Sound effects
Sample delay
Processing the hiss hound
Overview
Sound is one of the easiest and most straight forward things that I do in the game.
- I create a voice (main output)
- Then a main mixer to attach to the voice
- Then two other mixers to attach to the main mixer
- se_mixer is for the sound effects
- st_mixer is for the sound track
This is done so I can control the volume of the soundtrack and sound effects independantly.
I then attach samples, sample instances, and streams to these mixers.
Sound setup
These are the variables used:
// mwSound.h
class mwSound
{
public:
ALLEGRO_VOICE *voice = NULL;
ALLEGRO_MIXER *mn_mixer = NULL;
ALLEGRO_MIXER *se_mixer = NULL;
ALLEGRO_MIXER *st_mixer = NULL;
ALLEGRO_SAMPLE *snd[20];
ALLEGRO_SAMPLE_INSTANCE *sid_hiss;
ALLEGRO_AUDIO_STREAM *pm_theme_stream;
int fuse_loop_playing;
int sample_delay[8];
int se_scaler=5;
int st_scaler=5;
int lit_item;
int sound_on = 1;
void start_music(int resume);
void stop_sound(void);
void proc_sound(void);
void load_sound(void);
void set_se_scaler(void);
void set_st_scaler(void);
};
extern mwSound mSound;
// these variables are saved in the config file:
mSound.sound_on=1
mSound.se_scaler=2
mSound.st_scaler=1
In initial_setup() I call mSound.load_sound();
void mwSound::load_sound() // load sound driver and samples
{
int err = 0;
if (sound_on)
{
if(!al_install_audio())
{
mInput.m_err("Failed to initialize audio.\n");
err = 1;
}
if ((!err) && (!al_init_acodec_addon()))
{
mInput.m_err("Failed to initialize audio codecs.\n");
err = 1;
}
if (err)
{
sound_on = 0;
mConfig.save();
}
else
{
voice = al_create_voice(44100, ALLEGRO_AUDIO_DEPTH_INT16, ALLEGRO_CHANNEL_CONF_2);
if (voice == NULL) mInput.m_err("Failed to create voice.\n");
al_set_default_voice(voice);
mn_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (mn_mixer == NULL) mInput.m_err("Failed to create mn_mixer\n");
se_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (se_mixer == NULL) mInput.m_err("Failed to create se_mixer\n");
st_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (st_mixer == NULL) mInput.m_err("Failed to create st_mixer\n");
if (!al_attach_mixer_to_voice(mn_mixer, voice)) mInput.m_err("Failed attaching mn_mixer\n");
if (!al_attach_mixer_to_mixer(se_mixer, mn_mixer)) mInput.m_err("Failed attaching se_mixer\n");
if (!al_attach_mixer_to_mixer(st_mixer, mn_mixer)) mInput.m_err("Failed attaching st_mixer\n");
al_set_default_mixer(se_mixer);
al_reserve_samples(20);
char fn[20] = "snd/snd00.wav";
for (int x=0; x<10; x++)
{
fn[8] = 48 + x;
snd[x] = al_load_sample(fn);
}
sid_hiss = al_create_sample_instance(snd[3]);
al_set_sample_instance_playmode(sid_hiss, ALLEGRO_PLAYMODE_LOOP);
al_attach_sample_instance_to_mixer(sid_hiss, se_mixer);
pm_theme_stream = al_load_audio_stream("snd/pm.wav", 8, 1024);
if (pm_theme_stream == NULL) mInput.m_err("Error loading snd/pm.wav\n");
al_set_audio_stream_playmode(pm_theme_stream, ALLEGRO_PLAYMODE_LOOP);
al_set_audio_stream_playing(pm_theme_stream, 0);
al_attach_audio_stream_to_mixer(pm_theme_stream, st_mixer);
set_se_scaler();
set_st_scaler();
}
}
}
Mixer volume controls
I set the volume of se_mixer and st_mixer with se_scaler and st_scaler.
se_scaler and st_scaler are integers that range from 0-9.
They are set by the user in 'Settings' -> 'Main' and are also saved in the config file.
When they are set or changed, these functions apply the values to the mixers:
void mwSound::set_se_scaler(void)
{
if (sound_on) al_set_mixer_gain(se_mixer, (float)se_scaler / 9);
mConfig.save();
}
void mwSound::set_st_scaler(void)
{
if (sound_on) al_set_mixer_gain(st_mixer, (float)st_scaler / 9);
mConfig.save();
}
Theme music
The theme music is started like this when a game is started or resumed.
The only difference is that resume does not rewind before starting.
void mwSound::start_music(int resume)
{
if (sound_on)
{
al_set_mixer_gain(st_mixer, (float)st_scaler / 9);
al_set_mixer_gain(se_mixer, (float)se_scaler / 9);
// reset sound counters
for (int c=0; c<8; c++) sample_delay[c] = mLoop.frame_num;
if (sound_on)
{
if (!resume) al_rewind_audio_stream(pm_theme_stream);
al_set_audio_stream_playing(pm_theme_stream, 1);
}
}
}
Theme music and sound effects are stopped like this when the game is exited:
void mwSound::stop_sound(void)
{
if (sound_on)
{
al_set_audio_stream_playing(pm_theme_stream, 0);
al_set_sample_instance_playing(sid_hiss, 0);
}
}
Sound effects
When an event in the game requires a sound to be played, here is how I do that:
void mwGameEvent::add(int ev, int x, int y, int z1, int z2, int z3, int z4)
{
...
if (mSound.sound_on)
{
/* sample numbers
0 - player shoots
1 - d'OH
2 - bonus
3 - hiss
4 - la dee dah door, key, exit
5 - explosion
6 - grunt 1 shot
7 - grunt 2 hit
8 - enemy killed */
switch (ev)
{
// al_play_sample(ALLEGRO_SAMPLE *spl, float gain, float pan, float speed, ALLEGRO_PLAYMODE loop, ALLEGRO_SAMPLE_ID *ret_id)
case 1: // player shoots
//al_play_sample(snd[0], 0.71, 0, .8, ALLEGRO_PLAYMODE_ONCE, NULL);
al_play_sample(mSound.snd[0], 0.81, 0, .7, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 2: // explosion
al_play_sample(mSound.snd[5], .78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 20: case 22: case 4: // la dee dah (key, door, unlocked exit, warp)
if (mSound.sample_delay[4]+30 < mLoop.frame_num)
{
mSound.sample_delay[4] = mLoop.frame_num;
al_play_sample(mSound.snd[4], 0.78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 24: // sproingy
al_play_sample(mSound.snd[9], 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 27: // purple coin
if (mSound.sample_delay[2]+30 < mLoop.frame_num)
{
mSound.sample_delay[2] = mLoop.frame_num;
al_play_sample(mSound.snd[2], 0.78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 40: case 41: // player got hurt
al_play_sample(mSound.snd[6], 0.5, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 12: // player took damage
if (mSound.sample_delay[7]+14 < mLoop.frame_num)
{
mSound.sample_delay[7] = mLoop.frame_num;
al_play_sample(mSound.snd[7], 0.5, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 42: // enemy killed
al_play_sample(mSound.snd[8], 0.5, 0, 1.2, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 8: // d'Oh (player died)
al_play_sample(mSound.snd[1], 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
}
Sample delay
I have implemented a simple system to prevent re-triggering a sample while it is still playing.
int sample_delay[20] keeps track of the frame_num when a sample was played.
Won't allow the same sample to be re-played until a specified number of frames has passed.
Processing the hiss hound
To process the hiss sound (when a bomb fuse is burning or a rocket is lit), I call this function once per frame:
void mwSound::proc_sound() // called once per frame
{
if (sound_on)
{
if (lit_item) al_set_sample_instance_playing(sid_hiss, 1);
else al_set_sample_instance_playing(sid_hiss, 0);
lit_item = 0;
}
}
lit_item is set to 0 at the end of this function.
When bombs and rockets are processed, they will set lit_item if they are lit.