2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 The C64 sound chip (the SID) had 3 channels. Boulder Dash used all 3 of them.
23 Different channels were used for different sounds.
25 Channel 1: other small sounds, ie. diamonds falling, boulders rolling.
27 Channel 2: Walking, diamond collecting and explosion; also time running out
30 Channel 3: amoeba sound, magic wall sound, cave cover & uncover sound, and
31 the crack sound (gate open)
33 Sounds have precedence over each other. Ie. the crack sound is given
34 precedence over other sounds (amoeba, for example.)
35 Different channels also behave differently. Channel 2 sounds are not stopped,
36 ie. walking can not be heard, until the explosion sound is finished playing
39 Explosions are always restarted, though. This is controlled by the array
42 Channel 1 sounds are always stopped, if a new sound is requested.
44 Here we implement this a bit differently. We use samples, instead of
45 synthesizing the sounds. By stopping samples, sometimes small clicks
46 generate. Therefore we do not stop them, rather fade them out quickly.
47 (The SID had filters, which stopped these small clicks.)
49 Also, channel 1 and 2 should be stopped very often. So I decided to use two
50 SDL_Mixer channels to emulate one C64 channel; and they are used alternating.
51 SDL channel 4 is the "backup" channel 1, channel 5 is the backup channel 2.
52 Other channels have the same indexes.
56 #define MAX_CHANNELS 5
58 typedef enum _sound_flag
60 GD_SP_LOOPED = 1 << 0,
61 GD_SP_FORCE = 1 << 1, // force restart, regardless of precedence level
64 typedef struct _sound_property
67 int channel; // channel this sound is played on.
68 int precedence; // greater numbers will have precedence.
72 static SoundProperty sound_flags[] =
74 { 0, GD_S_NONE, 0, 0 },
77 // CHANNEL 1 SOUNDS ARE ALWAYS RESTARTED, so no need for GD_SP_FORCE flag.
78 { GD_S_STONE_PUSHING, 1, 10 },
79 { GD_S_STONE_FALLING, 1, 10 },
80 { GD_S_STONE_IMPACT, 1, 10 },
81 { GD_S_MEGA_STONE_PUSHING, 1, 10 },
82 { GD_S_MEGA_STONE_FALLING, 1, 10 },
83 { GD_S_MEGA_STONE_IMPACT, 1, 10 },
84 { GD_S_FLYING_STONE_PUSHING, 1, 10 },
85 { GD_S_FLYING_STONE_FALLING, 1, 10 },
86 { GD_S_FLYING_STONE_IMPACT, 1, 10 },
87 { GD_S_WAITING_STONE_PUSHING, 1, 10 },
88 { GD_S_CHASING_STONE_PUSHING, 1, 10 },
89 // nut falling is relatively silent, so low precedence.
90 { GD_S_NUT_PUSHING, 1, 8 },
91 { GD_S_NUT_FALLING, 1, 8 },
92 { GD_S_NUT_IMPACT, 1, 8 },
93 // higher precedence than a stone bouncing.
94 { GD_S_NUT_CRACKING, 1, 12 },
95 // sligthly lower precedence, as stones and diamonds should be "louder"
96 { GD_S_DIRT_BALL_FALLING, 1, 8 },
97 { GD_S_DIRT_BALL_IMPACT, 1, 8 },
98 { GD_S_DIRT_LOOSE_FALLING, 1, 8 },
99 { GD_S_DIRT_LOOSE_IMPACT, 1, 8 },
100 { GD_S_NITRO_PACK_PUSHING, 1, 10 },
101 { GD_S_NITRO_PACK_FALLING, 1, 10 },
102 { GD_S_NITRO_PACK_IMPACT, 1, 10 },
103 { GD_S_FALLING_WALL_FALLING, 1, 10 },
104 { GD_S_FALLING_WALL_IMPACT, 1, 10 },
105 { GD_S_EXPANDING_WALL, 1, 10 },
106 { GD_S_WALL_REAPPEARING, 1, 9 },
107 { GD_S_DIAMOND_FALLING_RANDOM, 1, 10 },
108 { GD_S_DIAMOND_FALLING_1, 1, 10 },
109 { GD_S_DIAMOND_FALLING_2, 1, 10 },
110 { GD_S_DIAMOND_FALLING_3, 1, 10 },
111 { GD_S_DIAMOND_FALLING_4, 1, 10 },
112 { GD_S_DIAMOND_FALLING_5, 1, 10 },
113 { GD_S_DIAMOND_FALLING_6, 1, 10 },
114 { GD_S_DIAMOND_FALLING_7, 1, 10 },
115 { GD_S_DIAMOND_FALLING_8, 1, 10 },
116 { GD_S_DIAMOND_IMPACT_RANDOM, 1, 10 },
117 { GD_S_DIAMOND_IMPACT_1, 1, 10 },
118 { GD_S_DIAMOND_IMPACT_2, 1, 10 },
119 { GD_S_DIAMOND_IMPACT_3, 1, 10 },
120 { GD_S_DIAMOND_IMPACT_4, 1, 10 },
121 { GD_S_DIAMOND_IMPACT_5, 1, 10 },
122 { GD_S_DIAMOND_IMPACT_6, 1, 10 },
123 { GD_S_DIAMOND_IMPACT_7, 1, 10 },
124 { GD_S_DIAMOND_IMPACT_8, 1, 10 },
125 { GD_S_FLYING_DIAMOND_FALLING_RANDOM, 1, 10 },
126 { GD_S_FLYING_DIAMOND_FALLING_1, 1, 10 },
127 { GD_S_FLYING_DIAMOND_FALLING_2, 1, 10 },
128 { GD_S_FLYING_DIAMOND_FALLING_3, 1, 10 },
129 { GD_S_FLYING_DIAMOND_FALLING_4, 1, 10 },
130 { GD_S_FLYING_DIAMOND_FALLING_5, 1, 10 },
131 { GD_S_FLYING_DIAMOND_FALLING_6, 1, 10 },
132 { GD_S_FLYING_DIAMOND_FALLING_7, 1, 10 },
133 { GD_S_FLYING_DIAMOND_FALLING_8, 1, 10 },
134 { GD_S_FLYING_DIAMOND_IMPACT_RANDOM, 1, 10 },
135 { GD_S_FLYING_DIAMOND_IMPACT_1, 1, 10 },
136 { GD_S_FLYING_DIAMOND_IMPACT_2, 1, 10 },
137 { GD_S_FLYING_DIAMOND_IMPACT_3, 1, 10 },
138 { GD_S_FLYING_DIAMOND_IMPACT_4, 1, 10 },
139 { GD_S_FLYING_DIAMOND_IMPACT_5, 1, 10 },
140 { GD_S_FLYING_DIAMOND_IMPACT_6, 1, 10 },
141 { GD_S_FLYING_DIAMOND_IMPACT_7, 1, 10 },
142 { GD_S_FLYING_DIAMOND_IMPACT_8, 1, 10 },
143 // diamond collect sound has precedence over everything.
144 { GD_S_DIAMOND_COLLECTING, 1, 100 },
145 { GD_S_FLYING_DIAMOND_COLLECTING, 1, 100 },
147 // collect sounds have higher precedence than falling sounds and the like.
148 { GD_S_SKELETON_COLLECTING, 1, 100 },
149 { GD_S_PNEUMATIC_COLLECTING, 1, 50 },
150 { GD_S_BOMB_COLLECTING, 1, 50 },
151 { GD_S_CLOCK_COLLECTING, 1, 50 },
152 { GD_S_SWEET_COLLECTING, 1, 50 },
153 { GD_S_KEY_COLLECTING, 1, 50 },
154 { GD_S_DIAMOND_KEY_COLLECTING, 1, 50 },
155 // slime has lower precedence than diamond and stone falling sounds.
156 { GD_S_SLIME, 1, 5 },
157 // lava has low precedence, too.
159 { GD_S_REPLICATOR, 1, 5 },
160 // same for acid, even lower.
161 { GD_S_ACID_SPREADING, 1, 3 },
163 { GD_S_BLADDER_MOVING, 1, 5 },
164 { GD_S_BLADDER_PUSHING, 1, 5 },
165 { GD_S_BLADDER_CONVERTING, 1, 8 },
166 { GD_S_BLADDER_SPENDER, 1, 8 },
167 // very low precedence. biters tend to produce too much sound.
168 { GD_S_BITER_EATING, 1, 3 },
171 { GD_S_DOOR_OPENING, 2, 10 },
172 { GD_S_DIRT_WALKING, 2, 10 },
173 { GD_S_EMPTY_WALKING, 2, 10 },
174 { GD_S_STIRRING, 2, 10 },
175 { GD_S_BOX_PUSHING, 2, 10 },
176 { GD_S_TELEPORTER, 2, 10 },
177 // timeout sounds have increasing precedence so they are always started
178 { GD_S_TIMEOUT_10, 2, 20 },
179 // timeout sounds are examples which do not need "force restart" flag.
180 { GD_S_TIMEOUT_9, 2, 21 },
181 { GD_S_TIMEOUT_8, 2, 22 },
182 { GD_S_TIMEOUT_7, 2, 23 },
183 { GD_S_TIMEOUT_6, 2, 24 },
184 { GD_S_TIMEOUT_5, 2, 25 },
185 { GD_S_TIMEOUT_4, 2, 26 },
186 { GD_S_TIMEOUT_3, 2, 27 },
187 { GD_S_TIMEOUT_2, 2, 28 },
188 { GD_S_TIMEOUT_1, 2, 29 },
189 { GD_S_TIMEOUT_0, 2, 150, GD_SP_FORCE },
190 { GD_S_EXPLODING, 2, 100, GD_SP_FORCE },
191 { GD_S_BOMB_EXPLODING, 2, 100, GD_SP_FORCE },
192 { GD_S_GHOST_EXPLODING, 2, 100, GD_SP_FORCE },
193 { GD_S_VOODOO_EXPLODING, 2, 100, GD_SP_FORCE },
194 { GD_S_NITRO_PACK_EXPLODING, 2, 100, GD_SP_FORCE },
195 { GD_S_BOMB_PLACING, 2, 10 },
196 // precedence larger than normal, but smaller than timeout sounds
197 { GD_S_FINISHED, 2, 15, GD_SP_FORCE | GD_SP_LOOPED },
198 { GD_S_SWITCH_BITER, 2, 10 },
199 { GD_S_SWITCH_CREATURES, 2, 10 },
200 { GD_S_SWITCH_GRAVITY, 2, 10 },
201 { GD_S_SWITCH_EXPANDING, 2, 10 },
202 { GD_S_SWITCH_CONVEYOR, 2, 10 },
203 { GD_S_SWITCH_REPLICATOR, 2, 10 },
206 { GD_S_AMOEBA, 3, 30, GD_SP_LOOPED },
207 { GD_S_AMOEBA_MAGIC, 3, 40, GD_SP_LOOPED },
208 { GD_S_MAGIC_WALL, 3, 35, GD_SP_LOOPED },
209 { GD_S_COVERING, 3, 100, GD_SP_LOOPED },
210 { GD_S_PNEUMATIC_HAMMER, 3, 50, GD_SP_LOOPED },
211 { GD_S_WATER, 3, 20, GD_SP_LOOPED },
212 { GD_S_CRACKING, 3, 150 },
213 { GD_S_GRAVITY_CHANGING, 3, 60 },
216 // the bonus life sound has nothing to do with the cave.
217 // playing on channel 4.
218 { GD_S_BONUS_LIFE, 4, 0 },
228 static GdSound snd_playing[MAX_CHANNELS];
229 struct GdSoundInfo sound_info_to_play[MAX_CHANNELS];
230 struct GdSoundInfo sound_info_playing[MAX_CHANNELS];
232 static boolean gd_sound_is_looped(GdSound sound)
234 return (sound_flags[sound].flags & GD_SP_LOOPED) != 0;
237 static boolean gd_sound_force_start(GdSound sound)
239 return (sound_flags[sound].flags & GD_SP_FORCE) != 0;
242 static int gd_sound_get_channel(GdSound sound)
244 return sound_flags[sound].channel;
247 static int gd_sound_get_precedence(GdSound sound)
249 return sound_flags[sound].precedence;
252 static GdSound sound_playing(int channel)
254 struct GdSoundInfo *si = &sound_info_playing[channel];
256 // check if sound has finished playing
257 if (snd_playing[channel] != GD_S_NONE)
258 if (!isSoundPlaying_BD(si->element, si->sound))
259 snd_playing[channel] = GD_S_NONE;
261 return snd_playing[channel];
264 static void halt_channel(int channel)
266 struct GdSoundInfo *si = &sound_info_playing[channel];
269 if (isSoundPlaying_BD(si->element, si->sound))
270 printf("::: stop sound %d\n", si->sound);
273 if (isSoundPlaying_BD(si->element, si->sound))
274 StopSound_BD(si->element, si->sound);
276 snd_playing[channel] = GD_S_NONE;
279 static void play_channel_at_position(int channel)
281 struct GdSoundInfo *si = &sound_info_to_play[channel];
283 PlayLevelSound_BD(si->x, si->y, si->element, si->sound);
285 sound_info_playing[channel] = *si;
288 static void play_sound(int channel, GdSound sound)
290 // channel 1 and channel 4 are used alternating
291 // channel 2 and channel 5 are used alternating
292 static const GdSound diamond_falling_sounds[] =
294 GD_S_DIAMOND_FALLING_1,
295 GD_S_DIAMOND_FALLING_2,
296 GD_S_DIAMOND_FALLING_3,
297 GD_S_DIAMOND_FALLING_4,
298 GD_S_DIAMOND_FALLING_5,
299 GD_S_DIAMOND_FALLING_6,
300 GD_S_DIAMOND_FALLING_7,
301 GD_S_DIAMOND_FALLING_8,
303 static const GdSound diamond_impact_sounds[] =
305 GD_S_DIAMOND_IMPACT_1,
306 GD_S_DIAMOND_IMPACT_2,
307 GD_S_DIAMOND_IMPACT_3,
308 GD_S_DIAMOND_IMPACT_4,
309 GD_S_DIAMOND_IMPACT_5,
310 GD_S_DIAMOND_IMPACT_6,
311 GD_S_DIAMOND_IMPACT_7,
312 GD_S_DIAMOND_IMPACT_8,
314 static const GdSound flying_diamond_falling_sounds[] =
316 GD_S_FLYING_DIAMOND_FALLING_1,
317 GD_S_FLYING_DIAMOND_FALLING_2,
318 GD_S_FLYING_DIAMOND_FALLING_3,
319 GD_S_FLYING_DIAMOND_FALLING_4,
320 GD_S_FLYING_DIAMOND_FALLING_5,
321 GD_S_FLYING_DIAMOND_FALLING_6,
322 GD_S_FLYING_DIAMOND_FALLING_7,
323 GD_S_FLYING_DIAMOND_FALLING_8,
325 static const GdSound flying_diamond_impact_sounds[] =
327 GD_S_FLYING_DIAMOND_IMPACT_1,
328 GD_S_FLYING_DIAMOND_IMPACT_2,
329 GD_S_FLYING_DIAMOND_IMPACT_3,
330 GD_S_FLYING_DIAMOND_IMPACT_4,
331 GD_S_FLYING_DIAMOND_IMPACT_5,
332 GD_S_FLYING_DIAMOND_IMPACT_6,
333 GD_S_FLYING_DIAMOND_IMPACT_7,
334 GD_S_FLYING_DIAMOND_IMPACT_8,
337 if (sound == GD_S_NONE)
340 // change diamond falling random to a selected diamond falling sound.
341 if (sound == GD_S_DIAMOND_FALLING_RANDOM)
342 sound = diamond_falling_sounds[gd_random_int_range(0, 8)];
343 else if (sound == GD_S_DIAMOND_IMPACT_RANDOM)
344 sound = diamond_impact_sounds[gd_random_int_range(0, 8)];
345 else if (sound == GD_S_FLYING_DIAMOND_FALLING_RANDOM)
346 sound = flying_diamond_falling_sounds[gd_random_int_range(0, 8)];
347 else if (sound == GD_S_FLYING_DIAMOND_IMPACT_RANDOM)
348 sound = flying_diamond_impact_sounds[gd_random_int_range(0, 8)];
350 // channel 1 may have been changed to channel 4 above.
352 if (!gd_sound_is_looped(sound))
353 halt_channel(channel);
355 play_channel_at_position(channel);
357 snd_playing[channel] = sound;
360 void gd_sound_init(void)
364 for (i = 0; i < GD_S_MAX; i++)
365 if (sound_flags[i].sound != i)
366 Fail("sound db index mismatch: %d", i);
368 for (i = 0; i < MAX_CHANNELS; i++)
369 snd_playing[i] = GD_S_NONE;
372 void gd_sound_off(void)
377 for (i = 0; i < ARRAY_SIZE(snd_playing); i++)
381 void gd_sound_play_bonus_life(void)
383 // required to set extended sound information for native sound engine
384 gd_sound_play(NULL, GD_S_BONUS_LIFE, O_NONE, -1, -1);
386 // now play the sound directly (on non-standard sound channel)
387 play_sound(gd_sound_get_channel(GD_S_BONUS_LIFE), GD_S_BONUS_LIFE);
390 static void play_sounds(GdSound sound1, GdSound sound2, GdSound sound3)
392 // CHANNEL 1 is for small sounds
393 if (sound1 != GD_S_NONE)
395 // start new sound if higher or same precedence than the one currently playing
396 if (gd_sound_get_precedence(sound1) >= gd_sound_get_precedence(sound_playing(1)))
397 play_sound(1, sound1);
401 // only interrupt looped sounds. non-looped sounds will go away automatically.
402 if (gd_sound_is_looped(sound_playing(1)))
406 // CHANNEL 2 is for walking, explosions
407 // if no sound requested, do nothing.
408 if (sound2 != GD_S_NONE)
410 boolean play = FALSE;
412 // always start if not currently playing a sound.
413 if (sound_playing(2) == GD_S_NONE ||
414 gd_sound_force_start(sound2) ||
415 gd_sound_get_precedence(sound2) > gd_sound_get_precedence(sound_playing(2)))
418 // if figured out to play: do it.
419 // (if the requested sound is looped, this is required to continue playing it)
421 play_sound(2, sound2);
425 // only interrupt looped sounds. non-looped sounds will go away automatically.
426 if (gd_sound_is_looped(sound_playing(2)))
430 // CHANNEL 3 is for crack sound, amoeba and magic wall.
431 if (sound3 != GD_S_NONE)
435 // if requests a non-looped sound, play that immediately.
436 // that can be a crack sound, gravity change, new life, ...
438 // do not interrupt the previous sound, if it is non-looped.
439 // later calls of this function will probably contain the same sound3,
440 // and then it will be set.
441 if (!gd_sound_is_looped(sound_playing(3)) &&
442 gd_sound_is_looped(sound3) &&
443 sound_playing(3) != GD_S_NONE)
446 // if figured out to play: do it.
448 play_sound(3, sound3);
452 // sound3 = none, so interrupt sound requested.
453 // only interrupt looped sounds. non-looped sounds will go away automatically.
454 if (gd_sound_is_looped(sound_playing(3)))
459 void gd_sound_play_cave(GdCave *cave)
461 play_sounds(cave->sound1, cave->sound2, cave->sound3);
464 static void gd_sound_info_to_play(int channel, int x, int y, int element, int sound)
466 struct GdSoundInfo *si = &sound_info_to_play[channel];
470 si->element = element;
474 // plays sound in a cave
475 void gd_sound_play(GdCave *cave, GdSound sound, GdElement element, int x, int y)
477 if (sound == GD_S_NONE)
480 // do not play sounds when fast-forwarding until player hatched
481 if (setup.bd_skip_hatching && !game_bd.game->cave->hatched &&
482 game_bd.game->state_counter == GAME_INT_CAVE_RUNNING)
485 // when using native sound engine or if no position specified, use middle screen position
486 if (game.use_native_bd_sound_engine || (x == -1 && y == -1))
488 x = get_play_area_w() / 2 + get_scroll_x();
489 y = get_play_area_h() / 2 + get_scroll_y();
492 if (!game.use_native_bd_sound_engine)
494 // when not using native sound engine, just play the sound
495 PlayLevelSound_BD(x, y, element, sound);
500 GdSound *s = &sound; // use reliable default value
501 int channel = gd_sound_get_channel(sound);
505 case 1: s = &cave->sound1; break;
506 case 2: s = &cave->sound2; break;
507 case 3: s = &cave->sound3; break;
511 if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(*s))
516 // set extended sound information for native sound engine
517 gd_sound_info_to_play(channel, x, y, element, sound);