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