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 },
76 /* channel 1 sounds. */
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 { GD_S_SLIME, 1, 5 }, /* slime has lower precedence than diamond and stone falling sounds. */
156 { GD_S_LAVA, 1, 5 }, /* lava has low precedence, too. */
157 { GD_S_REPLICATOR, 1, 5 },
158 { GD_S_ACID_SPREADING, 1, 3 }, /* same for acid, even lower. */
159 { GD_S_BLADDER_MOVING, 1, 5 }, /* same for bladder. */
160 { GD_S_BLADDER_PUSHING, 1, 5 },
161 { GD_S_BLADDER_CONVERTING, 1, 8 },
162 { GD_S_BLADDER_SPENDER, 1, 8 },
163 { GD_S_BITER_EATING, 1, 3 }, /* very low precedence. biters tend to produce too much sound. */
165 /* channel2 sounds. */
166 { GD_S_DOOR_OPENING, 2, 10 },
167 { GD_S_DIRT_WALKING, 2, 10 },
168 { GD_S_EMPTY_WALKING, 2, 10 },
169 { GD_S_STIRRING, 2, 10 },
170 { GD_S_BOX_PUSHING, 2, 10 },
171 { GD_S_TELEPORTER, 2, 10 },
172 { GD_S_TIMEOUT_10, 2, 20 }, /* timeout sounds have increasing precedence so they are always started */
173 { GD_S_TIMEOUT_9, 2, 21 }, /* timeout sounds are examples which do not need "force restart" flag. */
174 { GD_S_TIMEOUT_8, 2, 22 },
175 { GD_S_TIMEOUT_7, 2, 23 },
176 { GD_S_TIMEOUT_6, 2, 24 },
177 { GD_S_TIMEOUT_5, 2, 25 },
178 { GD_S_TIMEOUT_4, 2, 26 },
179 { GD_S_TIMEOUT_3, 2, 27 },
180 { GD_S_TIMEOUT_2, 2, 28 },
181 { GD_S_TIMEOUT_1, 2, 29 },
182 { GD_S_TIMEOUT_0, 2, 150, GD_SP_FORCE },
183 { GD_S_EXPLODING, 2, 100, GD_SP_FORCE },
184 { GD_S_BOMB_EXPLODING, 2, 100, GD_SP_FORCE },
185 { GD_S_GHOST_EXPLODING, 2, 100, GD_SP_FORCE },
186 { GD_S_VOODOO_EXPLODING, 2, 100, GD_SP_FORCE },
187 { GD_S_NITRO_PACK_EXPLODING, 2, 100, GD_SP_FORCE },
188 { GD_S_BOMB_PLACING, 2, 10 },
189 { GD_S_FINISHED, 2, 15, GD_SP_FORCE | GD_SP_LOOPED }, /* precedence larger than normal, but smaller than timeout sounds */
190 { GD_S_SWITCH_BITER, 2, 10 },
191 { GD_S_SWITCH_CREATURES, 2, 10 },
192 { GD_S_SWITCH_GRAVITY, 2, 10 },
193 { GD_S_SWITCH_EXPANDING, 2, 10 },
194 { GD_S_SWITCH_CONVEYOR, 2, 10 },
195 { GD_S_SWITCH_REPLICATOR, 2, 10 },
197 /* channel 3 sounds. */
198 { GD_S_AMOEBA, 3, 30, GD_SP_LOOPED },
199 { GD_S_AMOEBA_MAGIC, 3, 40, GD_SP_LOOPED },
200 { GD_S_MAGIC_WALL, 3, 35, GD_SP_LOOPED },
201 { GD_S_COVERING, 3, 100, GD_SP_LOOPED },
202 { GD_S_PNEUMATIC_HAMMER, 3, 50, GD_SP_LOOPED },
203 { GD_S_WATER, 3, 20, GD_SP_LOOPED },
204 { GD_S_CRACKING, 3, 150 },
205 { GD_S_GRAVITY_CHANGING, 3, 60 },
208 /* the bonus life sound has nothing to do with the cave. */
209 /* playing on channel 4. */
210 { GD_S_BONUS_LIFE, 4, 0 },
220 static GdSound snd_playing[MAX_CHANNELS];
221 struct GdSoundInfo sound_info_to_play[MAX_CHANNELS];
222 struct GdSoundInfo sound_info_playing[MAX_CHANNELS];
224 static boolean gd_sound_is_looped(GdSound sound)
226 return (sound_flags[sound].flags & GD_SP_LOOPED) != 0;
229 static boolean gd_sound_force_start(GdSound sound)
231 return (sound_flags[sound].flags & GD_SP_FORCE) != 0;
234 static int gd_sound_get_channel(GdSound sound)
236 return sound_flags[sound].channel;
239 static int gd_sound_get_precedence(GdSound sound)
241 return sound_flags[sound].precedence;
244 static GdSound sound_playing(int channel)
246 struct GdSoundInfo *si = &sound_info_playing[channel];
248 // check if sound has finished playing
249 if (snd_playing[channel] != GD_S_NONE)
250 if (!isSoundPlaying_BD(si->element, si->sound))
251 snd_playing[channel] = GD_S_NONE;
253 return snd_playing[channel];
256 static void halt_channel(int channel)
258 struct GdSoundInfo *si = &sound_info_playing[channel];
261 if (isSoundPlaying_BD(si->element, si->sound))
262 printf("::: stop sound %d\n", si->sound);
265 if (isSoundPlaying_BD(si->element, si->sound))
266 StopSound_BD(si->element, si->sound);
268 snd_playing[channel] = GD_S_NONE;
271 static void play_channel_at_position(int channel)
273 struct GdSoundInfo *si = &sound_info_to_play[channel];
275 PlayLevelSound_BD(si->x, si->y, si->element, si->sound);
277 sound_info_playing[channel] = *si;
280 static void play_sound(int channel, GdSound sound)
282 /* channel 1 and channel 4 are used alternating */
283 /* channel 2 and channel 5 are used alternating */
284 static const GdSound diamond_falling_sounds[] =
286 GD_S_DIAMOND_FALLING_1,
287 GD_S_DIAMOND_FALLING_2,
288 GD_S_DIAMOND_FALLING_3,
289 GD_S_DIAMOND_FALLING_4,
290 GD_S_DIAMOND_FALLING_5,
291 GD_S_DIAMOND_FALLING_6,
292 GD_S_DIAMOND_FALLING_7,
293 GD_S_DIAMOND_FALLING_8,
295 static const GdSound diamond_impact_sounds[] =
297 GD_S_DIAMOND_IMPACT_1,
298 GD_S_DIAMOND_IMPACT_2,
299 GD_S_DIAMOND_IMPACT_3,
300 GD_S_DIAMOND_IMPACT_4,
301 GD_S_DIAMOND_IMPACT_5,
302 GD_S_DIAMOND_IMPACT_6,
303 GD_S_DIAMOND_IMPACT_7,
304 GD_S_DIAMOND_IMPACT_8,
306 static const GdSound flying_diamond_falling_sounds[] =
308 GD_S_FLYING_DIAMOND_FALLING_1,
309 GD_S_FLYING_DIAMOND_FALLING_2,
310 GD_S_FLYING_DIAMOND_FALLING_3,
311 GD_S_FLYING_DIAMOND_FALLING_4,
312 GD_S_FLYING_DIAMOND_FALLING_5,
313 GD_S_FLYING_DIAMOND_FALLING_6,
314 GD_S_FLYING_DIAMOND_FALLING_7,
315 GD_S_FLYING_DIAMOND_FALLING_8,
317 static const GdSound flying_diamond_impact_sounds[] =
319 GD_S_FLYING_DIAMOND_IMPACT_1,
320 GD_S_FLYING_DIAMOND_IMPACT_2,
321 GD_S_FLYING_DIAMOND_IMPACT_3,
322 GD_S_FLYING_DIAMOND_IMPACT_4,
323 GD_S_FLYING_DIAMOND_IMPACT_5,
324 GD_S_FLYING_DIAMOND_IMPACT_6,
325 GD_S_FLYING_DIAMOND_IMPACT_7,
326 GD_S_FLYING_DIAMOND_IMPACT_8,
329 if (sound == GD_S_NONE)
332 /* change diamond falling random to a selected diamond falling sound. */
333 if (sound == GD_S_DIAMOND_FALLING_RANDOM)
334 sound = diamond_falling_sounds[gd_random_int_range(0, 8)];
335 else if (sound == GD_S_DIAMOND_IMPACT_RANDOM)
336 sound = diamond_impact_sounds[gd_random_int_range(0, 8)];
337 else if (sound == GD_S_FLYING_DIAMOND_FALLING_RANDOM)
338 sound = flying_diamond_falling_sounds[gd_random_int_range(0, 8)];
339 else if (sound == GD_S_FLYING_DIAMOND_IMPACT_RANDOM)
340 sound = flying_diamond_impact_sounds[gd_random_int_range(0, 8)];
342 /* channel 1 may have been changed to channel 4 above. */
344 if (!gd_sound_is_looped(sound))
345 halt_channel(channel);
347 play_channel_at_position(channel);
349 snd_playing[channel] = sound;
352 void gd_sound_init(void)
356 for (i = 0; i < GD_S_MAX; i++)
357 if (sound_flags[i].sound != i)
358 Fail("sound db index mismatch: %d", i);
360 for (i = 0; i < MAX_CHANNELS; i++)
361 snd_playing[i] = GD_S_NONE;
364 void gd_sound_off(void)
368 /* stop all sounds. */
369 for (i = 0; i < ARRAY_SIZE(snd_playing); i++)
373 void gd_sound_play_bonus_life(void)
375 // required to set extended sound information for native sound engine
376 gd_sound_play(NULL, GD_S_BONUS_LIFE, O_NONE, -1, -1);
378 // now play the sound directly (on non-standard sound channel)
379 play_sound(gd_sound_get_channel(GD_S_BONUS_LIFE), GD_S_BONUS_LIFE);
382 static void play_sounds(GdSound sound1, GdSound sound2, GdSound sound3)
384 /* CHANNEL 1 is for small sounds */
385 if (sound1 != GD_S_NONE)
387 /* start new sound if higher or same precedence than the one currently playing */
388 if (gd_sound_get_precedence(sound1) >= gd_sound_get_precedence(sound_playing(1)))
389 play_sound(1, sound1);
393 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
394 if (gd_sound_is_looped(sound_playing(1)))
398 /* CHANNEL 2 is for walking, explosions */
399 /* if no sound requested, do nothing. */
400 if (sound2 != GD_S_NONE)
402 boolean play = FALSE;
404 /* always start if not currently playing a sound. */
405 if (sound_playing(2) == GD_S_NONE ||
406 gd_sound_force_start(sound2) ||
407 gd_sound_get_precedence(sound2) > gd_sound_get_precedence(sound_playing(2)))
410 /* if figured out to play: do it. */
411 /* (if the requested sound is looped, this is required to continue playing it) */
413 play_sound(2, sound2);
417 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
418 if (gd_sound_is_looped(sound_playing(2)))
422 /* CHANNEL 3 is for crack sound, amoeba and magic wall. */
423 if (sound3 != GD_S_NONE)
427 /* if requests a non-looped sound, play that immediately.
428 that can be a crack sound, gravity change, new life, ... */
429 /* do not interrupt the previous sound, if it is non-looped.
430 later calls of this function will probably contain the same sound3,
431 and then it will be set. */
432 if (!gd_sound_is_looped(sound_playing(3)) &&
433 gd_sound_is_looped(sound3) &&
434 sound_playing(3) != GD_S_NONE)
437 /* if figured out to play: do it. */
439 play_sound(3, sound3);
443 /* sound3 = none, so interrupt sound requested. */
444 /* only interrupt looped sounds. non-looped sounds will go away automatically. */
445 if (gd_sound_is_looped(sound_playing(3)))
450 void gd_sound_play_cave(GdCave *cave)
452 play_sounds(cave->sound1, cave->sound2, cave->sound3);
455 static void gd_sound_info_to_play(int channel, int x, int y, int element, int sound)
457 struct GdSoundInfo *si = &sound_info_to_play[channel];
461 si->element = element;
465 /* plays sound in a cave */
466 void gd_sound_play(GdCave *cave, GdSound sound, GdElement element, int x, int y)
468 if (sound == GD_S_NONE)
471 // do not play sounds when fast-forwarding until player hatched
472 if (setup.bd_skip_hatching && !game_bd.game->cave->hatched &&
473 game_bd.game->state_counter == GAME_INT_CAVE_RUNNING)
476 // when using native sound engine or if no position specified, use middle screen position
477 if (game.use_native_bd_sound_engine || (x == -1 && y == -1))
479 x = get_play_area_w() / 2 + get_scroll_x();
480 y = get_play_area_h() / 2 + get_scroll_y();
483 if (!game.use_native_bd_sound_engine)
485 // when not using native sound engine, just play the sound
486 PlayLevelSound_BD(x, y, element, sound);
491 GdSound *s = &sound; // use reliable default value
492 int channel = gd_sound_get_channel(sound);
496 case 1: s = &cave->sound1; break;
497 case 2: s = &cave->sound2; break;
498 case 3: s = &cave->sound3; break;
502 if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(*s))
507 // set extended sound information for native sound engine
508 gd_sound_info_to_play(channel, x, y, element, sound);