387ea00df8deb95007102e92e5e677ed65953f7a
[rocksndiamonds.git] / src / game_bd / bd_sound.c
1 /*
2  * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
3  *
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.
7  *
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.
15  */
16
17 #include "main_bd.h"
18
19
20 /*
21   The C64 sound chip (the SID) had 3 channels. Boulder Dash used all 3 of them.
22
23   Different channels were used for different sounds.
24
25   Channel 1: other small sounds, ie. diamonds falling, boulders rolling.
26
27   Channel 2: Walking, diamond collecting and explosion; also time running out
28              sound.
29
30   Channel 3: amoeba sound, magic wall sound, cave cover & uncover sound, and
31              the crack sound (gate open)
32
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
37   completely.
38
39   Explosions are always restarted, though. This is controlled by the array
40   defined in cave.c.
41
42   Channel 1 sounds are always stopped, if a new sound is requested.
43
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.)
48
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.
53 */
54
55
56 #define MAX_CHANNELS            5
57
58 typedef enum _sound_flag
59 {
60   GD_SP_LOOPED  = 1 << 0,
61   GD_SP_FORCE   = 1 << 1,  /* force restart, regardless of precedence level */
62 } GdSoundFlag;
63
64 typedef struct _sound_property
65 {
66   GdSound sound;
67   int channel;        /* channel this sound is played on. */
68   int precedence;     /* greater numbers will have precedence. */
69   int flags;
70 } SoundProperty;
71
72 static SoundProperty sound_flags[] =
73 {
74   { 0, GD_S_NONE,                       0, 0    },
75
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  },
146
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. */
164
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   },
196
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   },
206
207   /* other sounds */
208   /* the bonus life sound has nothing to do with the cave. */
209   /* playing on channel 4. */
210   { GD_S_BONUS_LIFE,                    4, 0    },
211 };
212
213 struct GdSoundInfo
214 {
215   int x, y;
216   int element;
217   int sound;
218 };
219
220 static GdSound snd_playing[MAX_CHANNELS];
221 struct GdSoundInfo sound_info_to_play[MAX_CHANNELS];
222 struct GdSoundInfo sound_info_playing[MAX_CHANNELS];
223
224 static boolean gd_sound_is_looped(GdSound sound)
225 {
226   return (sound_flags[sound].flags & GD_SP_LOOPED) != 0;
227 }
228
229 static boolean gd_sound_force_start(GdSound sound)
230 {
231   return (sound_flags[sound].flags & GD_SP_FORCE) != 0;
232 }
233
234 static int gd_sound_get_channel(GdSound sound)
235 {
236   return sound_flags[sound].channel;
237 }
238
239 static int gd_sound_get_precedence(GdSound sound)
240 {
241   return sound_flags[sound].precedence;
242 }
243
244 static GdSound sound_playing(int channel)
245 {
246   struct GdSoundInfo *si = &sound_info_playing[channel];
247
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;
252
253   return snd_playing[channel];
254 }
255
256 static void halt_channel(int channel)
257 {
258   struct GdSoundInfo *si = &sound_info_playing[channel];
259
260 #if 0
261   if (isSoundPlaying_BD(si->element, si->sound))
262     printf("::: stop sound %d\n", si->sound);
263 #endif
264
265   if (isSoundPlaying_BD(si->element, si->sound))
266     StopSound_BD(si->element, si->sound);
267
268   snd_playing[channel] = GD_S_NONE;
269 }
270
271 static void play_channel_at_position(int channel)
272 {
273   struct GdSoundInfo *si = &sound_info_to_play[channel];
274
275   PlayLevelSound_BD(si->x, si->y, si->element, si->sound);
276
277   sound_info_playing[channel] = *si;
278 }
279
280 static void play_sound(int channel, GdSound sound)
281 {
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[] =
285   {
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,
294   };
295   static const GdSound diamond_impact_sounds[] =
296   {
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,
305   };
306   static const GdSound flying_diamond_falling_sounds[] =
307   {
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,
316   };
317   static const GdSound flying_diamond_impact_sounds[] =
318   {
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,
327   };
328
329   if (sound == GD_S_NONE)
330     return;
331
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)];
341
342   /* channel 1 may have been changed to channel 4 above. */
343
344   if (!gd_sound_is_looped(sound))
345     halt_channel(channel);
346
347   play_channel_at_position(channel);
348
349   snd_playing[channel] = sound;
350 }
351
352 void gd_sound_init(void)
353 {
354   int i;
355
356   for (i = 0; i < GD_S_MAX; i++)
357     if (sound_flags[i].sound != i)
358       Fail("sound db index mismatch: %d", i);
359
360   for (i = 0; i < MAX_CHANNELS; i++)
361     snd_playing[i] = GD_S_NONE;
362 }
363
364 void gd_sound_off(void)
365 {
366   int i;
367
368   /* stop all sounds. */
369   for (i = 0; i < ARRAY_SIZE(snd_playing); i++)
370     halt_channel(i);
371 }
372
373 void gd_sound_play_bonus_life(void)
374 {
375   // required to set extended sound information for native sound engine
376   gd_sound_play(NULL, GD_S_BONUS_LIFE, O_NONE, -1, -1);
377
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);
380 }
381
382 static void play_sounds(GdSound sound1, GdSound sound2, GdSound sound3)
383 {
384   /* CHANNEL 1 is for small sounds */
385   if (sound1 != GD_S_NONE)
386   {
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);
390   }
391   else
392   {
393     /* only interrupt looped sounds. non-looped sounds will go away automatically. */
394     if (gd_sound_is_looped(sound_playing(1)))
395       halt_channel(1);
396   }
397
398   /* CHANNEL 2 is for walking, explosions */
399   /* if no sound requested, do nothing. */
400   if (sound2 != GD_S_NONE)
401   {
402     boolean play = FALSE;
403
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)))
408       play = TRUE;
409
410     /* if figured out to play: do it. */
411     /* (if the requested sound is looped, this is required to continue playing it) */
412     if (play)
413       play_sound(2, sound2);
414   }
415   else
416   {
417     /* only interrupt looped sounds. non-looped sounds will go away automatically. */
418     if (gd_sound_is_looped(sound_playing(2)))
419       halt_channel(2);
420   }
421
422   /* CHANNEL 3 is for crack sound, amoeba and magic wall. */
423   if (sound3 != GD_S_NONE)
424   {
425     boolean play = TRUE;
426
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)
435       play = FALSE;
436
437     /* if figured out to play: do it. */
438     if (play)
439       play_sound(3, sound3);
440   }
441   else
442   {
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)))
446       halt_channel(3);
447   }
448 }
449
450 void gd_sound_play_cave(GdCave *cave)
451 {
452   play_sounds(cave->sound1, cave->sound2, cave->sound3);
453 }
454
455 static void gd_sound_info_to_play(int channel, int x, int y, int element, int sound)
456 {
457   struct GdSoundInfo *si = &sound_info_to_play[channel];
458
459   si->x = x;
460   si->y = y;
461   si->element = element;
462   si->sound = sound;
463 }
464
465 /* plays sound in a cave */
466 void gd_sound_play(GdCave *cave, GdSound sound, GdElement element, int x, int y)
467 {
468   if (sound == GD_S_NONE)
469     return;
470
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)
474     return;
475
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))
478   {
479     x = get_play_area_w() / 2 + get_scroll_x();
480     y = get_play_area_h() / 2 + get_scroll_y();
481   }
482
483   if (!game.use_native_bd_sound_engine)
484   {
485     // when not using native sound engine, just play the sound
486     PlayLevelSound_BD(x, y, element, sound);
487
488     return;
489   }
490
491   GdSound *s = &sound;          // use reliable default value
492   int channel = gd_sound_get_channel(sound);
493
494   switch (channel)
495   {
496     case 1: s = &cave->sound1; break;
497     case 2: s = &cave->sound2; break;
498     case 3: s = &cave->sound3; break;
499     default: break;
500   }
501
502   if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(*s))
503   {
504     // set sound
505     *s = sound;
506
507     // set extended sound information for native sound engine
508     gd_sound_info_to_play(channel, x, y, element, sound);
509   }
510 }