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.
23 The C64 sound chip (the SID) had 3 channels. Boulder Dash used all 3 of them.
25 Different channels were used for different sounds.
27 Channel 1: other small sounds, ie. diamonds falling, boulders rolling.
29 Channel 2: Walking, diamond collecting and explosion; also time running out
32 Channel 3: amoeba sound, magic wall sound, cave cover & uncover sound, and
33 the crack sound (gate open)
35 Sounds have precedence over each other. Ie. the crack sound is given
36 precedence over other sounds (amoeba, for example.)
37 Different channels also behave differently. Channel 2 sounds are not stopped,
38 ie. walking can not be heard, until the explosion sound is finished playing
41 Explosions are always restarted, though. This is controlled by the array
44 Channel 1 sounds are always stopped, if a new sound is requested.
46 Here we implement this a bit differently. We use samples, instead of
47 synthesizing the sounds. By stopping samples, sometimes small clicks
48 generate. Therefore we do not stop them, rather fade them out quickly.
49 (The SID had filters, which stopped these small clicks.)
51 Also, channel 1 and 2 should be stopped very often. So I decided to use two
52 SDL_Mixer channels to emulate one C64 channel; and they are used alternating.
53 SDL channel 4 is the "backup" channel 1, channel 5 is the backup channel 2.
54 Other channels have the same indexes.
58 #define MAX_CHANNELS 5
60 typedef enum _sound_flag
62 GD_SP_LOOPED = 1 << 0,
63 GD_SP_FORCE = 1 << 1, /* force restart, regardless of precedence level */
66 typedef struct _sound_property
69 int channel; /* channel this sound is played on. */
70 int precedence; /* greater numbers will have precedence. */
74 static SoundProperty sound_flags[] =
76 { 0, GD_S_NONE, 0, 0 },
78 /* channel 1 sounds. */
79 /* CHANNEL 1 SOUNDS ARE ALWAYS RESTARTED, so no need for GD_SP_FORCE flag. */
80 { GD_S_STONE_PUSHING, 1, 10 },
81 { GD_S_STONE_FALLING, 1, 10 },
82 { GD_S_STONE_IMPACT, 1, 10 },
83 { GD_S_MEGA_STONE_PUSHING, 1, 10 },
84 { GD_S_MEGA_STONE_FALLING, 1, 10 },
85 { GD_S_MEGA_STONE_IMPACT, 1, 10 },
86 { GD_S_FLYING_STONE_PUSHING, 1, 10 },
87 { GD_S_FLYING_STONE_FALLING, 1, 10 },
88 { GD_S_FLYING_STONE_IMPACT, 1, 10 },
89 { GD_S_WAITING_STONE_PUSHING, 1, 10 },
90 { GD_S_CHASING_STONE_PUSHING, 1, 10 },
91 /* nut falling is relatively silent, so low precedence. */
92 { GD_S_NUT_PUSHING, 1, 8 },
93 { GD_S_NUT_FALLING, 1, 8 },
94 { GD_S_NUT_IMPACT, 1, 8 },
95 /* higher precedence than a stone bouncing. */
96 { GD_S_NUT_CRACKING, 1, 12 },
97 /* sligthly lower precedence, as stones and diamonds should be "louder" */
98 { GD_S_DIRT_BALL_FALLING, 1, 8 },
99 { GD_S_DIRT_BALL_IMPACT, 1, 8 },
100 { GD_S_DIRT_LOOSE_FALLING, 1, 8 },
101 { GD_S_DIRT_LOOSE_IMPACT, 1, 8 },
102 { GD_S_NITRO_PACK_PUSHING, 1, 10 },
103 { GD_S_NITRO_PACK_FALLING, 1, 10 },
104 { GD_S_NITRO_PACK_IMPACT, 1, 10 },
105 { GD_S_FALLING_WALL_FALLING, 1, 10 },
106 { GD_S_FALLING_WALL_IMPACT, 1, 10 },
107 { GD_S_EXPANDING_WALL, 1, 10 },
108 { GD_S_WALL_REAPPEARING, 1, 9 },
109 { GD_S_DIAMOND_FALLING_RANDOM, 1, 10 },
110 { GD_S_DIAMOND_FALLING_1, 1, 10 },
111 { GD_S_DIAMOND_FALLING_2, 1, 10 },
112 { GD_S_DIAMOND_FALLING_3, 1, 10 },
113 { GD_S_DIAMOND_FALLING_4, 1, 10 },
114 { GD_S_DIAMOND_FALLING_5, 1, 10 },
115 { GD_S_DIAMOND_FALLING_6, 1, 10 },
116 { GD_S_DIAMOND_FALLING_7, 1, 10 },
117 { GD_S_DIAMOND_FALLING_8, 1, 10 },
118 { GD_S_DIAMOND_IMPACT_RANDOM, 1, 10 },
119 { GD_S_DIAMOND_IMPACT_1, 1, 10 },
120 { GD_S_DIAMOND_IMPACT_2, 1, 10 },
121 { GD_S_DIAMOND_IMPACT_3, 1, 10 },
122 { GD_S_DIAMOND_IMPACT_4, 1, 10 },
123 { GD_S_DIAMOND_IMPACT_5, 1, 10 },
124 { GD_S_DIAMOND_IMPACT_6, 1, 10 },
125 { GD_S_DIAMOND_IMPACT_7, 1, 10 },
126 { GD_S_DIAMOND_IMPACT_8, 1, 10 },
127 { GD_S_FLYING_DIAMOND_FALLING_RANDOM, 1, 10 },
128 { GD_S_FLYING_DIAMOND_FALLING_1, 1, 10 },
129 { GD_S_FLYING_DIAMOND_FALLING_2, 1, 10 },
130 { GD_S_FLYING_DIAMOND_FALLING_3, 1, 10 },
131 { GD_S_FLYING_DIAMOND_FALLING_4, 1, 10 },
132 { GD_S_FLYING_DIAMOND_FALLING_5, 1, 10 },
133 { GD_S_FLYING_DIAMOND_FALLING_6, 1, 10 },
134 { GD_S_FLYING_DIAMOND_FALLING_7, 1, 10 },
135 { GD_S_FLYING_DIAMOND_FALLING_8, 1, 10 },
136 { GD_S_FLYING_DIAMOND_IMPACT_RANDOM, 1, 10 },
137 { GD_S_FLYING_DIAMOND_IMPACT_1, 1, 10 },
138 { GD_S_FLYING_DIAMOND_IMPACT_2, 1, 10 },
139 { GD_S_FLYING_DIAMOND_IMPACT_3, 1, 10 },
140 { GD_S_FLYING_DIAMOND_IMPACT_4, 1, 10 },
141 { GD_S_FLYING_DIAMOND_IMPACT_5, 1, 10 },
142 { GD_S_FLYING_DIAMOND_IMPACT_6, 1, 10 },
143 { GD_S_FLYING_DIAMOND_IMPACT_7, 1, 10 },
144 { GD_S_FLYING_DIAMOND_IMPACT_8, 1, 10 },
145 /* diamond collect sound has precedence over everything. */
146 { GD_S_DIAMOND_COLLECTING, 1, 100 },
147 { GD_S_FLYING_DIAMOND_COLLECTING, 1, 100 },
149 /* collect sounds have higher precedence than falling sounds and the like. */
150 { GD_S_SKELETON_COLLECTING, 1, 100 },
151 { GD_S_PNEUMATIC_COLLECTING, 1, 50 },
152 { GD_S_BOMB_COLLECTING, 1, 50 },
153 { GD_S_CLOCK_COLLECTING, 1, 50 },
154 { GD_S_SWEET_COLLECTING, 1, 50 },
155 { GD_S_KEY_COLLECTING, 1, 50 },
156 { GD_S_DIAMOND_KEY_COLLECTING, 1, 50 },
157 { GD_S_SLIME, 1, 5 }, /* slime has lower precedence than diamond and stone falling sounds. */
158 { GD_S_LAVA, 1, 5 }, /* lava has low precedence, too. */
159 { GD_S_REPLICATOR, 1, 5 },
160 { GD_S_ACID_SPREADING, 1, 3 }, /* same for acid, even lower. */
161 { GD_S_BLADDER_MOVING, 1, 5 }, /* same for bladder. */
162 { GD_S_BLADDER_PUSHING, 1, 5 },
163 { GD_S_BLADDER_CONVERTING, 1, 8 },
164 { GD_S_BLADDER_SPENDER, 1, 8 },
165 { GD_S_BITER_EATING, 1, 3 }, /* very low precedence. biters tend to produce too much sound. */
167 /* channel2 sounds. */
168 { GD_S_DOOR_OPENING, 2, 10 },
169 { GD_S_DIRT_WALKING, 2, 10 },
170 { GD_S_EMPTY_WALKING, 2, 10 },
171 { GD_S_STIRRING, 2, 10 },
172 { GD_S_BOX_PUSHING, 2, 10 },
173 { GD_S_TELEPORTER, 2, 10 },
174 { GD_S_TIMEOUT_10, 2, 20 }, /* timeout sounds have increasing precedence so they are always started */
175 { GD_S_TIMEOUT_9, 2, 21 }, /* timeout sounds are examples which do not need "force restart" flag. */
176 { GD_S_TIMEOUT_8, 2, 22 },
177 { GD_S_TIMEOUT_7, 2, 23 },
178 { GD_S_TIMEOUT_6, 2, 24 },
179 { GD_S_TIMEOUT_5, 2, 25 },
180 { GD_S_TIMEOUT_4, 2, 26 },
181 { GD_S_TIMEOUT_3, 2, 27 },
182 { GD_S_TIMEOUT_2, 2, 28 },
183 { GD_S_TIMEOUT_1, 2, 29 },
184 { GD_S_TIMEOUT_0, 2, 150, GD_SP_FORCE },
185 { GD_S_EXPLODING, 2, 100, GD_SP_FORCE },
186 { GD_S_BOMB_EXPLODING, 2, 100, GD_SP_FORCE },
187 { GD_S_GHOST_EXPLODING, 2, 100, GD_SP_FORCE },
188 { GD_S_VOODOO_EXPLODING, 2, 100, GD_SP_FORCE },
189 { GD_S_NITRO_PACK_EXPLODING, 2, 100, GD_SP_FORCE },
190 { GD_S_BOMB_PLACING, 2, 10 },
191 { GD_S_FINISHED, 2, 15, GD_SP_FORCE | GD_SP_LOOPED }, /* precedence larger than normal, but smaller than timeout sounds */
192 { GD_S_SWITCH_BITER, 2, 10 },
193 { GD_S_SWITCH_CREATURES, 2, 10 },
194 { GD_S_SWITCH_GRAVITY, 2, 10 },
195 { GD_S_SWITCH_EXPANDING, 2, 10 },
196 { GD_S_SWITCH_CONVEYOR, 2, 10 },
197 { GD_S_SWITCH_REPLICATOR, 2, 10 },
199 /* channel 3 sounds. */
200 { GD_S_AMOEBA, 3, 30, GD_SP_LOOPED },
201 { GD_S_AMOEBA_MAGIC, 3, 40, GD_SP_LOOPED },
202 { GD_S_MAGIC_WALL, 3, 35, GD_SP_LOOPED },
203 { GD_S_COVERING, 3, 100, GD_SP_LOOPED },
204 { GD_S_PNEUMATIC_HAMMER, 3, 50, GD_SP_LOOPED },
205 { GD_S_WATER, 3, 20, GD_SP_LOOPED },
206 { GD_S_CRACKING, 3, 150 },
207 { GD_S_GRAVITY_CHANGING, 3, 60 },
210 /* the bonus life sound has nothing to do with the cave. */
211 /* playing on channel 4. */
212 { GD_S_BONUS_LIFE, 4, 0 },
222 static GdSound snd_playing[MAX_CHANNELS];
223 struct GdSoundInfo sound_info_to_play[MAX_CHANNELS];
224 struct GdSoundInfo sound_info_playing[MAX_CHANNELS];
226 static boolean gd_sound_is_looped(GdSound sound)
228 return (sound_flags[sound].flags & GD_SP_LOOPED) != 0;
231 static boolean gd_sound_force_start(GdSound sound)
233 return (sound_flags[sound].flags & GD_SP_FORCE) != 0;
236 static int gd_sound_get_channel(GdSound sound)
238 return sound_flags[sound].channel;
241 static int gd_sound_get_precedence(GdSound sound)
243 return sound_flags[sound].precedence;
246 static GdSound sound_playing(int channel)
248 struct GdSoundInfo *si = &sound_info_playing[channel];
250 // check if sound has finished playing
251 if (snd_playing[channel] != GD_S_NONE)
252 if (!isSoundPlaying_BD(si->element, si->sound))
253 snd_playing[channel] = GD_S_NONE;
255 return snd_playing[channel];
258 static void halt_channel(int channel)
260 struct GdSoundInfo *si = &sound_info_playing[channel];
263 if (isSoundPlaying_BD(si->element, si->sound))
264 printf("::: stop sound %d\n", si->sound);
267 if (isSoundPlaying_BD(si->element, si->sound))
268 StopSound_BD(si->element, si->sound);
270 snd_playing[channel] = GD_S_NONE;
273 static void play_channel_at_position(int channel)
275 struct GdSoundInfo *si = &sound_info_to_play[channel];
277 PlayLevelSound_BD(si->x, si->y, si->element, si->sound);
279 sound_info_playing[channel] = *si;
282 static void play_sound(int channel, GdSound sound)
284 /* channel 1 and channel 4 are used alternating */
285 /* channel 2 and channel 5 are used alternating */
286 static const GdSound diamond_falling_sounds[] =
288 GD_S_DIAMOND_FALLING_1,
289 GD_S_DIAMOND_FALLING_2,
290 GD_S_DIAMOND_FALLING_3,
291 GD_S_DIAMOND_FALLING_4,
292 GD_S_DIAMOND_FALLING_5,
293 GD_S_DIAMOND_FALLING_6,
294 GD_S_DIAMOND_FALLING_7,
295 GD_S_DIAMOND_FALLING_8,
297 static const GdSound diamond_impact_sounds[] =
299 GD_S_DIAMOND_IMPACT_1,
300 GD_S_DIAMOND_IMPACT_2,
301 GD_S_DIAMOND_IMPACT_3,
302 GD_S_DIAMOND_IMPACT_4,
303 GD_S_DIAMOND_IMPACT_5,
304 GD_S_DIAMOND_IMPACT_6,
305 GD_S_DIAMOND_IMPACT_7,
306 GD_S_DIAMOND_IMPACT_8,
308 static const GdSound flying_diamond_falling_sounds[] =
310 GD_S_FLYING_DIAMOND_FALLING_1,
311 GD_S_FLYING_DIAMOND_FALLING_2,
312 GD_S_FLYING_DIAMOND_FALLING_3,
313 GD_S_FLYING_DIAMOND_FALLING_4,
314 GD_S_FLYING_DIAMOND_FALLING_5,
315 GD_S_FLYING_DIAMOND_FALLING_6,
316 GD_S_FLYING_DIAMOND_FALLING_7,
317 GD_S_FLYING_DIAMOND_FALLING_8,
319 static const GdSound flying_diamond_impact_sounds[] =
321 GD_S_FLYING_DIAMOND_IMPACT_1,
322 GD_S_FLYING_DIAMOND_IMPACT_2,
323 GD_S_FLYING_DIAMOND_IMPACT_3,
324 GD_S_FLYING_DIAMOND_IMPACT_4,
325 GD_S_FLYING_DIAMOND_IMPACT_5,
326 GD_S_FLYING_DIAMOND_IMPACT_6,
327 GD_S_FLYING_DIAMOND_IMPACT_7,
328 GD_S_FLYING_DIAMOND_IMPACT_8,
331 if (sound == GD_S_NONE)
334 /* change diamond falling random to a selected diamond falling sound. */
335 if (sound == GD_S_DIAMOND_FALLING_RANDOM)
336 sound = diamond_falling_sounds[g_random_int_range(0, 8)];
337 else if (sound == GD_S_DIAMOND_IMPACT_RANDOM)
338 sound = diamond_impact_sounds[g_random_int_range(0, 8)];
339 else if (sound == GD_S_FLYING_DIAMOND_FALLING_RANDOM)
340 sound = flying_diamond_falling_sounds[g_random_int_range(0, 8)];
341 else if (sound == GD_S_FLYING_DIAMOND_IMPACT_RANDOM)
342 sound = flying_diamond_impact_sounds[g_random_int_range(0, 8)];
344 /* channel 1 may have been changed to channel 4 above. */
346 if (!gd_sound_is_looped(sound))
347 halt_channel(channel);
349 play_channel_at_position(channel);
351 snd_playing[channel] = sound;
354 void gd_sound_init(void)
358 for (i = 0; i < GD_S_MAX; i++)
359 if (sound_flags[i].sound != i)
360 Fail("sound db index mismatch: %d", i);
362 for (i = 0; i < MAX_CHANNELS; i++)
363 snd_playing[i] = GD_S_NONE;
366 void gd_sound_off(void)
370 /* stop all sounds. */
371 for (i = 0; i < G_N_ELEMENTS(snd_playing); i++)
375 void gd_sound_play_bonus_life(void)
377 // required to set extended sound information for native sound engine
378 gd_sound_play(NULL, GD_S_BONUS_LIFE, O_NONE, -1, -1);
380 // now play the sound directly (on non-standard sound channel)
381 play_sound(gd_sound_get_channel(GD_S_BONUS_LIFE), GD_S_BONUS_LIFE);
384 static void play_sounds(GdSound sound1, GdSound sound2, GdSound sound3)
386 /* CHANNEL 1 is for small sounds */
387 if (sound1 != GD_S_NONE)
389 /* start new sound if higher or same precedence than the one currently playing */
390 if (gd_sound_get_precedence(sound1) >= gd_sound_get_precedence(sound_playing(1)))
391 play_sound(1, sound1);
395 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
396 if (gd_sound_is_looped(sound_playing(1)))
400 /* CHANNEL 2 is for walking, explosions */
401 /* if no sound requested, do nothing. */
402 if (sound2 != GD_S_NONE)
404 boolean play = FALSE;
406 /* always start if not currently playing a sound. */
407 if (sound_playing(2) == GD_S_NONE ||
408 gd_sound_force_start(sound2) ||
409 gd_sound_get_precedence(sound2) > gd_sound_get_precedence(sound_playing(2)))
412 /* if figured out to play: do it. */
413 /* (if the requested sound is looped, this is required to continue playing it) */
415 play_sound(2, sound2);
419 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
420 if (gd_sound_is_looped(sound_playing(2)))
424 /* CHANNEL 3 is for crack sound, amoeba and magic wall. */
425 if (sound3 != GD_S_NONE)
429 /* if requests a non-looped sound, play that immediately.
430 that can be a crack sound, gravity change, new life, ... */
431 /* do not interrupt the previous sound, if it is non-looped.
432 later calls of this function will probably contain the same sound3,
433 and then it will be set. */
434 if (!gd_sound_is_looped(sound_playing(3)) &&
435 gd_sound_is_looped(sound3) &&
436 sound_playing(3) != GD_S_NONE)
439 /* if figured out to play: do it. */
441 play_sound(3, sound3);
445 /* sound3 = none, so interrupt sound requested. */
446 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
447 if (gd_sound_is_looped(sound_playing(3)))
452 void gd_sound_play_cave(GdCave *cave)
454 play_sounds(cave->sound1, cave->sound2, cave->sound3);
457 static void gd_sound_info_to_play(int channel, int x, int y, int element, int sound)
459 struct GdSoundInfo *si = &sound_info_to_play[channel];
463 si->element = element;
467 /* plays sound in a cave */
468 void gd_sound_play(GdCave *cave, GdSound sound, GdElement element, int x, int y)
470 if (sound == GD_S_NONE)
473 // do not play sounds when fast-forwarding until player hatched
474 if (setup.bd_skip_hatching && !game_bd.game->cave->hatched &&
475 game_bd.game->state_counter == GAME_INT_CAVE_RUNNING)
478 // when using native sound engine or if no position specified, use middle screen position
479 if (game.use_native_bd_sound_engine || (x == -1 && y == -1))
481 x = get_play_area_w() / 2 + get_scroll_x();
482 y = get_play_area_h() / 2 + get_scroll_y();
485 if (!game.use_native_bd_sound_engine)
487 // when not using native sound engine, just play the sound
488 PlayLevelSound_BD(x, y, element, sound);
493 GdSound *s = &sound; // use reliable default value
494 int channel = gd_sound_get_channel(sound);
498 case 1: s = &cave->sound1; break;
499 case 2: s = &cave->sound2; break;
500 case 3: s = &cave->sound3; break;
504 if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(*s))
509 // set extended sound information for native sound engine
510 gd_sound_info_to_play(channel, x, y, element, sound);