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