added sound consistency check for 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,                         1, 10   },
81   /* nut falling is relatively silent, so low precedence. */
82   { GD_S_NUT,                           1, 8    },
83   /* higher precedence than a stone bouncing. */
84   { GD_S_NUT_CRACKING,                  1, 12   },
85   /* sligthly lower precedence, as stones and diamonds should be "louder" */
86   { GD_S_DIRT_BALL,                     1, 8    },
87   { GD_S_NITRO_PACK,                    1, 10   },
88   { GD_S_FALLING_WALL,                  1, 10   },
89   { GD_S_EXPANDING_WALL,                1, 10   },
90   { GD_S_WALL_REAPPEARING,              1, 9    },
91   { GD_S_DIAMOND_RANDOM,                1, 10   },
92   { GD_S_DIAMOND_1,                     1, 10   },
93   { GD_S_DIAMOND_2,                     1, 10   },
94   { GD_S_DIAMOND_3,                     1, 10   },
95   { GD_S_DIAMOND_4,                     1, 10   },
96   { GD_S_DIAMOND_5,                     1, 10   },
97   { GD_S_DIAMOND_6,                     1, 10   },
98   { GD_S_DIAMOND_7,                     1, 10   },
99   { GD_S_DIAMOND_8,                     1, 10   },
100   /* diamond collect sound has precedence over everything. */
101   { GD_S_DIAMOND_COLLECTING,            1, 100  },
102
103   /* collect sounds have higher precedence than falling sounds and the like. */
104   { GD_S_SKELETON_COLLECTING,           1, 100  },
105   { GD_S_PNEUMATIC_COLLECTING,          1, 50   },
106   { GD_S_BOMB_COLLECTING,               1, 50   },
107   { GD_S_CLOCK_COLLECTING,              1, 50   },
108   { GD_S_SWEET_COLLECTING,              1, 50   },
109   { GD_S_KEY_COLLECTING,                1, 50   },
110   { GD_S_DIAMOND_KEY_COLLECTING,        1, 50   },
111   { GD_S_SLIME,                         1, 5    }, /* slime has lower precedence than diamond and stone falling sounds. */
112   { GD_S_LAVA,                          1, 5    }, /* lava has low precedence, too. */
113   { GD_S_REPLICATOR,                    1, 5    },
114   { GD_S_ACID_SPREADING,                1, 3    }, /* same for acid, even lower. */
115   { GD_S_BLADDER_MOVING,                1, 5    }, /* same for bladder. */
116   { GD_S_BLADDER_CONVERTING,            1, 8    },
117   { GD_S_BLADDER_SPENDER,               1, 8    },
118   { GD_S_BITER_EATING,                  1, 3    }, /* very low precedence. biters tend to produce too much sound. */
119
120   /* channel2 sounds. */
121   { GD_S_DOOR_OPENING,                  2, 10   },
122   { GD_S_DIRT_WALKING,                  2, 10   },
123   { GD_S_EMPTY_WALKING,                 2, 10   },
124   { GD_S_STIRRING,                      2, 10   },
125   { GD_S_BOX_PUSHING,                   2, 10   },
126   { GD_S_TELEPORTER,                    2, 10   },
127   { GD_S_TIMEOUT_10,                    2, 20   }, /* timeout sounds have increasing precedence so they are always started */
128   { GD_S_TIMEOUT_9,                     2, 21   }, /* timeout sounds are examples which do not need "force restart" flag. */
129   { GD_S_TIMEOUT_8,                     2, 22   },
130   { GD_S_TIMEOUT_7,                     2, 23   },
131   { GD_S_TIMEOUT_6,                     2, 24   },
132   { GD_S_TIMEOUT_5,                     2, 25   },
133   { GD_S_TIMEOUT_4,                     2, 26   },
134   { GD_S_TIMEOUT_3,                     2, 27   },
135   { GD_S_TIMEOUT_2,                     2, 28   },
136   { GD_S_TIMEOUT_1,                     2, 29   },
137   { GD_S_TIMEOUT_0,                     2, 150, GD_SP_FORCE     },
138   { GD_S_EXPLODING,                     2, 100, GD_SP_FORCE     },
139   { GD_S_BOMB_EXPLODING,                2, 100, GD_SP_FORCE     },
140   { GD_S_GHOST_EXPLODING,               2, 100, GD_SP_FORCE     },
141   { GD_S_VOODOO_EXPLODING,              2, 100, GD_SP_FORCE     },
142   { GD_S_NITRO_PACK_EXPLODING,          2, 100, GD_SP_FORCE     },
143   { GD_S_BOMB_PLACING,                  2, 10   },
144   { GD_S_FINISHED,                      2, 15,  GD_SP_FORCE | GD_SP_LOOPED      }, /* precedence larger than normal, but smaller than timeout sounds */
145   { GD_S_SWITCH_BITER,                  2, 10   },
146   { GD_S_SWITCH_CREATURES,              2, 10   },
147   { GD_S_SWITCH_GRAVITY,                2, 10   },
148   { GD_S_SWITCH_EXPANDING,              2, 10   },
149   { GD_S_SWITCH_CONVEYOR,               2, 10   },
150   { GD_S_SWITCH_REPLICATOR,             2, 10   },
151
152   /* channel 3 sounds. */
153   { GD_S_AMOEBA,                        3, 30,  GD_SP_LOOPED    },
154   { GD_S_AMOEBA_MAGIC,                  3, 40,  GD_SP_LOOPED    },
155   { GD_S_MAGIC_WALL,                    3, 35,  GD_SP_LOOPED    },
156   { GD_S_COVERING,                      3, 100, GD_SP_LOOPED    },
157   { GD_S_PNEUMATIC_HAMMER,              3, 50,  GD_SP_LOOPED    },
158   { GD_S_WATER,                         3, 20,  GD_SP_LOOPED    },
159   { GD_S_CRACKING,                      3, 150  },
160   { GD_S_GRAVITY_CHANGING,              3, 60   },
161
162   /* other sounds */
163   /* the bonus life sound has nothing to do with the cave. */
164   /* playing on channel 4. */
165   { GD_S_BONUS_LIFE,                    4, 0    },
166 };
167
168 struct GdSoundInfo
169 {
170   int x, y;
171   int element;
172   int sound;
173 };
174
175 static GdSound snd_playing[MAX_CHANNELS];
176 struct GdSoundInfo sound_info_to_play[MAX_CHANNELS];
177 struct GdSoundInfo sound_info_playing[MAX_CHANNELS];
178
179 static boolean gd_sound_is_looped(GdSound sound)
180 {
181   return (sound_flags[sound].flags & GD_SP_LOOPED) != 0;
182 }
183
184 static boolean gd_sound_force_start(GdSound sound)
185 {
186   return (sound_flags[sound].flags & GD_SP_FORCE) != 0;
187 }
188
189 static int gd_sound_get_channel(GdSound sound)
190 {
191   return sound_flags[sound].channel;
192 }
193
194 static int gd_sound_get_precedence(GdSound sound)
195 {
196   return sound_flags[sound].precedence;
197 }
198
199 static GdSound sound_playing(int channel)
200 {
201   struct GdSoundInfo *si = &sound_info_playing[channel];
202
203   // check if sound has finished playing
204   if (snd_playing[channel] != GD_S_NONE)
205     if (!isSoundPlaying_BD(si->element, si->sound))
206       snd_playing[channel] = GD_S_NONE;
207
208   return snd_playing[channel];
209 }
210
211 static void halt_channel(int channel)
212 {
213   struct GdSoundInfo *si = &sound_info_playing[channel];
214
215 #if 0
216   if (isSoundPlaying_BD(si->element, si->sound))
217     printf("::: stop sound %d\n", si->sound);
218 #endif
219
220   if (isSoundPlaying_BD(si->element, si->sound))
221     StopSound_BD(si->element, si->sound);
222
223   snd_playing[channel] = GD_S_NONE;
224 }
225
226 static void play_channel_at_position(int channel)
227 {
228   struct GdSoundInfo *si = &sound_info_to_play[channel];
229
230   PlayLevelSound_BD(si->x, si->y, si->element, si->sound);
231
232   sound_info_playing[channel] = *si;
233 }
234
235 static void play_sound(int channel, GdSound sound)
236 {
237   /* channel 1 and channel 4 are used alternating */
238   /* channel 2 and channel 5 are used alternating */
239   static const GdSound diamond_sounds[] =
240   {
241     GD_S_DIAMOND_1,
242     GD_S_DIAMOND_2,
243     GD_S_DIAMOND_3,
244     GD_S_DIAMOND_4,
245     GD_S_DIAMOND_5,
246     GD_S_DIAMOND_6,
247     GD_S_DIAMOND_7,
248     GD_S_DIAMOND_8,
249   };
250
251   if (sound == GD_S_NONE)
252     return;
253
254   /* change diamond falling random to a selected diamond falling sound. */
255   if (sound == GD_S_DIAMOND_RANDOM)
256     sound = diamond_sounds[g_random_int_range(0, G_N_ELEMENTS(diamond_sounds))];
257
258   /* channel 1 may have been changed to channel 4 above. */
259
260   if (!gd_sound_is_looped(sound))
261     halt_channel(channel);
262
263   play_channel_at_position(channel);
264
265   snd_playing[channel] = sound;
266 }
267
268 void gd_sound_init(void)
269 {
270   int i;
271
272   for (i = 0; i < GD_S_MAX; i++)
273     if (sound_flags[i].sound != i)
274       Fail("sound db index mismatch: %d", i);
275
276   for (i = 0; i < MAX_CHANNELS; i++)
277     snd_playing[i] = GD_S_NONE;
278 }
279
280 void gd_sound_off(void)
281 {
282   int i;
283
284   /* stop all sounds. */
285   for (i = 0; i < G_N_ELEMENTS(snd_playing); i++)
286     halt_channel(i);
287 }
288
289 void gd_sound_play_bonus_life(void)
290 {
291   // required to set extended sound information for native sound engine
292   gd_sound_play(NULL, GD_S_BONUS_LIFE, O_NONE, -1, -1);
293
294   // now play the sound directly (on non-standard sound channel)
295   play_sound(gd_sound_get_channel(GD_S_BONUS_LIFE), GD_S_BONUS_LIFE);
296 }
297
298 static void play_sounds(GdSound sound1, GdSound sound2, GdSound sound3)
299 {
300   /* CHANNEL 1 is for small sounds */
301   if (sound1 != GD_S_NONE)
302   {
303     /* start new sound if higher or same precedence than the one currently playing */
304     if (gd_sound_get_precedence(sound1) >= gd_sound_get_precedence(sound_playing(1)))
305       play_sound(1, sound1);
306   }
307   else
308   {
309     /* only interrupt looped sounds. non-looped sounds will go away automatically. */
310     if (gd_sound_is_looped(sound_playing(1)))
311       halt_channel(1);
312   }
313
314   /* CHANNEL 2 is for walking, explosions */
315   /* if no sound requested, do nothing. */
316   if (sound2 != GD_S_NONE)
317   {
318     boolean play = FALSE;
319
320     /* always start if not currently playing a sound. */
321     if (sound_playing(2) == GD_S_NONE ||
322         gd_sound_force_start(sound2) ||
323         gd_sound_get_precedence(sound2) > gd_sound_get_precedence(sound_playing(2)))
324       play = TRUE;
325
326     /* if figured out to play: do it. */
327     /* (if the requested sound is looped, this is required to continue playing it) */
328     if (play)
329       play_sound(2, sound2);
330   }
331   else
332   {
333     /* only interrupt looped sounds. non-looped sounds will go away automatically. */
334     if (gd_sound_is_looped(sound_playing(2)))
335       halt_channel(2);
336   }
337
338   /* CHANNEL 3 is for crack sound, amoeba and magic wall. */
339   if (sound3 != GD_S_NONE)
340   {
341     boolean play = TRUE;
342
343     /* if requests a non-looped sound, play that immediately.
344        that can be a crack sound, gravity change, new life, ... */
345     /* do not interrupt the previous sound, if it is non-looped.
346        later calls of this function will probably contain the same sound3,
347        and then it will be set. */
348     if (!gd_sound_is_looped(sound_playing(3)) &&
349         gd_sound_is_looped(sound3) &&
350         sound_playing(3) != GD_S_NONE)
351       play = FALSE;
352
353     /* if figured out to play: do it. */
354     if (play)
355       play_sound(3, sound3);
356   }
357   else
358   {
359     /* sound3 = none, so interrupt sound requested. */
360     /* only interrupt looped sounds. non-looped sounds will go away automatically. */
361     if (gd_sound_is_looped(sound_playing(3)))
362       halt_channel(3);
363   }
364 }
365
366 void gd_sound_play_cave(GdCave *cave)
367 {
368   play_sounds(cave->sound1, cave->sound2, cave->sound3);
369 }
370
371 static void gd_sound_info_to_play(int channel, int x, int y, int element, int sound)
372 {
373   struct GdSoundInfo *si = &sound_info_to_play[channel];
374
375   si->x = x;
376   si->y = y;
377   si->element = element;
378   si->sound = sound;
379 }
380
381 /* plays sound in a cave */
382 void gd_sound_play(GdCave *cave, GdSound sound, GdElement element, int x, int y)
383 {
384   if (sound == GD_S_NONE)
385     return;
386
387   // do not play sounds when fast-forwarding until player hatched
388   if (setup.bd_skip_hatching && !game_bd.game->cave->hatched &&
389       game_bd.game->state_counter == GAME_INT_CAVE_RUNNING)
390     return;
391
392   // when using native sound engine or if no position specified, use middle screen position
393   if (game.use_native_bd_sound_engine || (x == -1 && y == -1))
394   {
395     x = get_play_area_w() / 2 + get_scroll_x();
396     y = get_play_area_h() / 2 + get_scroll_y();
397   }
398
399   if (!game.use_native_bd_sound_engine)
400   {
401     // when not using native sound engine, just play the sound
402     PlayLevelSound_BD(x, y, element, sound);
403
404     return;
405   }
406
407   GdSound *s = &sound;          // use reliable default value
408   int channel = gd_sound_get_channel(sound);
409
410   switch (channel)
411   {
412     case 1: s = &cave->sound1; break;
413     case 2: s = &cave->sound2; break;
414     case 3: s = &cave->sound3; break;
415     default: break;
416   }
417
418   if (gd_sound_get_precedence(sound) >= gd_sound_get_precedence(*s))
419   {
420     // set sound
421     *s = sound;
422
423     // set extended sound information for native sound engine
424     gd_sound_info_to_play(channel, x, y, element, sound);
425   }
426 }