1 //********************************************************************************************
2 //*
3 //* This file is part of Egoboo.
4 //*
5 //* Egoboo is free software: you can redistribute it and/or modify it
6 //* under the terms of the GNU General Public License as published by
7 //* the Free Software Foundation, either version 3 of the License, or
8 //* (at your option) any later version.
9 //*
10 //* Egoboo is distributed in the hope that it will be useful, but
11 //* WITHOUT ANY WARRANTY; without even the implied warranty of
12 //* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 //* General Public License for more details.
14 //*
15 //* You should have received a copy of the GNU General Public License
16 //* along with Egoboo. If not, see <http://www.gnu.org/licenses/>.
17 //*
18 //********************************************************************************************
19
20 /// @file sound.c
21 /// @brief Sound code in Egoboo is implemented using SDL_mixer.
22 /// @details
23
24 #include "sound.h"
25
26 #include "camera.h"
27 #include "log.h"
28 #include "game.h"
29 #include "graphic.h"
30
31 #include "egoboo_vfs.h"
32 #include "egoboo_fileutil.h"
33 #include "egoboo_setup.h"
34 #include "egoboo_strutil.h"
35
36 #include "egoboo_math.inl"
37
38 #include <SDL.h>
39
40 #include "char.inl"
41
42 //--------------------------------------------------------------------------------------------
43 //--------------------------------------------------------------------------------------------
44 #define LOOPED_COUNT 256
45
46 /// Data needed to store and manipulate a looped sound
47 struct s_looped_sound_data
48 {
49 int channel;
50 Mix_Chunk * chunk;
51 CHR_REF object;
52 };
53 typedef struct s_looped_sound_data looped_sound_data_t;
54
55 INSTANTIATE_LIST_STATIC( looped_sound_data_t, LoopedList, LOOPED_COUNT );
56
57 static void LoopedList_init();
58 static void LoopedList_clear();
59 static bool_t LoopedList_free_one( size_t index );
60 static size_t LoopedList_get_free();
61
62 static bool_t LoopedList_validate();
63 static size_t LoopedList_add( Mix_Chunk * sound, int loops, const CHR_REF object );
64
65 //--------------------------------------------------------------------------------------------
66 //--------------------------------------------------------------------------------------------
67
68 // Sound using SDL_Mixer
69 static bool_t mixeron = bfalse;
70
71 snd_config_t snd;
72
73 // music
74 bool_t musicinmemory = bfalse;
75 Mix_Music * musictracksloaded[MAXPLAYLISTLENGTH];
76 Sint8 songplaying = INVALID_SOUND;
77
78 Mix_Chunk * g_wavelist[GSND_COUNT];
79
80 // text filenames for the global sounds
81 static const char * wavenames[GSND_COUNT] =
82 {
83 "coinget",
84 "defend",
85 "weather1",
86 "weather2",
87 "coinfall",
88 "lvlup",
89 "pitfall",
90 "shieldblock"
91 };
92
93 static bool_t sound_atexit_registered = bfalse;
94
95 //--------------------------------------------------------------------------------------------
96 //--------------------------------------------------------------------------------------------
97 static bool_t sdl_audio_initialize();
98 static bool_t sdl_mixer_initialize();
99 static void sdl_mixer_quit( void );
100
101 int _calculate_volume( fvec3_t diff, renderlist_t * prlist );
102 bool_t _update_stereo_channel( int channel, fvec3_t diff, renderlist_t * prlist );
103 bool_t _update_channel_volume( int channel, int volume, fvec3_t diff );
104
105 //--------------------------------------------------------------------------------------------
106 //--------------------------------------------------------------------------------------------
107 // define a little stack for interrupting music sounds with other music
108
109 #define MUSIC_STACK_COUNT 20
110 static int music_stack_depth = 0;
111
112 /// The data needed to store a dsingle music track on the music_stack[]
113 struct s_music_stack_element
114 {
115 Mix_Music * mus;
116 int number;
117 };
118
119 typedef struct s_music_stack_element music_stack_element_t;
120
121 static music_stack_element_t music_stack[MUSIC_STACK_COUNT];
122
123 static bool_t music_stack_pop( Mix_Music ** mus, int * song );
124
125 //--------------------------------------------------------------------------------------------
music_stack_finished_callback(void)126 static void music_stack_finished_callback( void )
127 {
128 // this function is only called when a music function is finished playing
129 // pop the saved music off the stack
130
131 // unfortunately, it seems that SDL_mixer does not support saving the position of
132 // the music stream, so the music track will restart from the beginning
133
134 Mix_Music * mus;
135 int song;
136
137 // grab the next song
138 if ( music_stack_pop( &mus, &song ) )
139 {
140 // play the music
141 Mix_PlayMusic( mus, 0 );
142
143 songplaying = song;
144
145 // set the volume
146 Mix_VolumeMusic( snd.musicvolume );
147 }
148 }
149
150 //--------------------------------------------------------------------------------------------
music_stack_push(Mix_Music * mus,int song)151 bool_t music_stack_push( Mix_Music * mus, int song )
152 {
153 if ( music_stack_depth >= MUSIC_STACK_COUNT - 1 )
154 {
155 music_stack_depth = MUSIC_STACK_COUNT - 1;
156 return bfalse;
157 }
158
159 music_stack[music_stack_depth].mus = mus;
160 music_stack[music_stack_depth].number = song;
161
162 music_stack_depth++;
163
164 return btrue;
165 }
166
167 //--------------------------------------------------------------------------------------------
music_stack_pop(Mix_Music ** mus,int * song)168 bool_t music_stack_pop( Mix_Music ** mus, int * song )
169 {
170 if ( NULL == mus || NULL == song ) return bfalse;
171
172 // fail if music isn't loaded
173 if ( !musicinmemory )
174 {
175 *mus = NULL;
176 *song = INVALID_SOUND;
177 return bfalse;
178 }
179
180 // set the default to be song 0
181 *song = 0;
182 *mus = musictracksloaded[*song];
183
184 // pop the stack, if possible
185 if ( music_stack_depth > 0 )
186 {
187 music_stack_depth--;
188
189 *mus = music_stack[music_stack_depth].mus;
190 *song = music_stack[music_stack_depth].number;
191 }
192
193 return btrue;
194 }
195
196 //--------------------------------------------------------------------------------------------
music_stack_init()197 void music_stack_init()
198 {
199 // push on the default music value
200 music_stack_push( musictracksloaded[0], 0 );
201
202 // register the callback
203 Mix_HookMusicFinished( music_stack_finished_callback );
204 }
205
206 //--------------------------------------------------------------------------------------------
207 //--------------------------------------------------------------------------------------------
sdl_audio_initialize()208 bool_t sdl_audio_initialize()
209 {
210 bool_t retval = btrue;
211
212 // make sure that SDL audio is turned on
213 if ( 0 == SDL_WasInit( SDL_INIT_AUDIO ) )
214 {
215 log_info( "Intializing SDL Audio... " );
216 if ( SDL_InitSubSystem( SDL_INIT_AUDIO ) < 0 )
217 {
218 log_message( "Failed!\n" );
219 log_warning( "SDL error == \"%s\"\n", SDL_GetError() );
220
221 retval = bfalse;
222 snd.musicvalid = bfalse;
223 snd.soundvalid = bfalse;
224 }
225 else
226 {
227 retval = btrue;
228 log_message( "Success!\n" );
229 }
230 }
231
232 return retval;
233 }
234
235 //--------------------------------------------------------------------------------------------
236 //--------------------------------------------------------------------------------------------
sdl_mixer_initialize()237 bool_t sdl_mixer_initialize()
238 {
239 /// @details ZF@> This intitializes the SDL_mixer services
240
241 if ( !mixeron && ( snd.musicvalid || snd.soundvalid ) )
242 {
243 const SDL_version* link_version = Mix_Linked_Version();
244 log_info( "Initializing SDL_mixer audio services version %d.%d.%d... ", link_version->major, link_version->minor, link_version->patch );
245 if ( Mix_OpenAudio( cfg.sound_highquality_base ? MIX_HIGH_QUALITY : MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, snd.buffersize ) < 0 )
246 {
247 mixeron = bfalse;
248 log_message( "Failure!\n" );
249 log_warning( "Unable to initialize audio: %s\n", Mix_GetError() );
250 }
251 else
252 {
253 Mix_VolumeMusic( snd.musicvolume );
254 Mix_AllocateChannels( snd.maxsoundchannel );
255
256 // initialize the music stack
257 music_stack_init();
258
259 mixeron = btrue;
260
261 atexit( sdl_mixer_quit );
262 sound_atexit_registered = btrue;
263
264 log_message( "Success!\n" );
265 }
266 }
267
268 return mixeron;
269 }
270
271 //--------------------------------------------------------------------------------------------
sdl_mixer_quit(void)272 void sdl_mixer_quit( void )
273 {
274 if ( mixeron && ( 0 != SDL_WasInit( SDL_INIT_AUDIO ) ) )
275 {
276 Mix_CloseAudio();
277 mixeron = bfalse;
278 }
279 }
280
281 //--------------------------------------------------------------------------------------------
282 //--------------------------------------------------------------------------------------------
283 // This function enables the use of SDL_Audio and SDL_Mixer functions, returns btrue if success
sound_initialize()284 bool_t sound_initialize()
285 {
286 bool_t retval = bfalse;
287 if ( sdl_audio_initialize() )
288 {
289 retval = sdl_mixer_initialize();
290 }
291
292 if ( retval )
293 {
294 LoopedList_init();
295 }
296
297 return retval;
298 }
299
300 //--------------------------------------------------------------------------------------------
sound_load_chunk_vfs(const char * szFileName)301 Mix_Chunk * sound_load_chunk_vfs( const char * szFileName )
302 {
303 STRING full_file_name;
304 Mix_Chunk * tmp_chunk;
305 bool_t file_exists = bfalse;
306
307 if ( !mixeron ) return NULL;
308 if ( INVALID_CSTR( szFileName ) ) return NULL;
309
310 // blank out the data
311 tmp_chunk = NULL;
312
313 // try an ogg file
314 snprintf( full_file_name, SDL_arraysize( full_file_name ), "%s.%s", szFileName, "ogg" );
315 if ( vfs_exists( full_file_name ) )
316 {
317 file_exists = btrue;
318 tmp_chunk = Mix_LoadWAV( vfs_resolveReadFilename( full_file_name ) );
319 }
320
321 if ( NULL == tmp_chunk )
322 {
323 // try a wav file
324 snprintf( full_file_name, SDL_arraysize( full_file_name ), "%s.%s", szFileName, "wav" );
325 if ( vfs_exists( full_file_name ) )
326 {
327 file_exists = btrue;
328 tmp_chunk = Mix_LoadWAV( vfs_resolveReadFilename( full_file_name ) );
329 }
330 }
331
332 if ( file_exists && NULL == tmp_chunk )
333 {
334 // there is an error only if the file exists and can't be loaded
335 log_warning( "Sound file not found/loaded %s.\n", szFileName );
336 }
337
338 return tmp_chunk;
339 }
340
341 //--------------------------------------------------------------------------------------------
sound_load_music(const char * szFileName)342 Mix_Music * sound_load_music( const char * szFileName )
343 {
344 STRING full_file_name;
345 Mix_Music * tmp_music;
346 bool_t file_exists = bfalse;
347
348 if ( !mixeron ) return NULL;
349 if ( INVALID_CSTR( szFileName ) ) return NULL;
350
351 // blank out the data
352 tmp_music = NULL;
353
354 // try a wav file
355 snprintf( full_file_name, SDL_arraysize( full_file_name ), "%s.%s", szFileName, "wav" );
356 if ( vfs_exists( full_file_name ) )
357 {
358 file_exists = btrue;
359 tmp_music = Mix_LoadMUS( full_file_name );
360 }
361
362 if ( NULL == tmp_music )
363 {
364 // try an ogg file
365 tmp_music = NULL;
366 snprintf( full_file_name, SDL_arraysize( full_file_name ), "%s.%s", szFileName, "ogg" );
367 if ( vfs_exists( full_file_name ) )
368 {
369 file_exists = btrue;
370 tmp_music = Mix_LoadMUS( full_file_name );
371 }
372 }
373
374 if ( file_exists && NULL == tmp_music )
375 {
376 // there is an error only if the file exists and can't be loaded
377 log_warning( "Music file not found/loaded %s.\n", szFileName );
378 }
379
380 return tmp_music;
381 }
382
383 //--------------------------------------------------------------------------------------------
sound_load(mix_ptr_t * pptr,const char * szFileName,mix_type_t type)384 bool_t sound_load( mix_ptr_t * pptr, const char * szFileName, mix_type_t type )
385 {
386
387 if ( !mixeron ) return bfalse;
388 if ( NULL == pptr ) return bfalse;
389
390 // clear out the data
391 pptr->ptr.unk = NULL;
392 pptr->type = MIX_UNKNOWN;
393
394 if ( INVALID_CSTR( szFileName ) ) return bfalse;
395
396 switch ( type )
397 {
398 case MIX_MUS:
399 pptr->ptr.mus = sound_load_music( szFileName );
400 if ( NULL != pptr->ptr.mus )
401 {
402 pptr->type = MIX_MUS;
403 }
404 break;
405
406 case MIX_SND:
407 pptr->ptr.snd = sound_load_chunk_vfs( szFileName );
408 if ( NULL != pptr->ptr.snd )
409 {
410 pptr->type = MIX_SND;
411 }
412 break;
413
414 case MIX_UNKNOWN:
415 /* do nothing */
416 break;
417
418 default:
419 // there is an error only if the file exists and can't be loaded
420 log_debug( "sound_load() - Mix type recognized %d.\n", type );
421 break;
422 };
423
424 return MIX_UNKNOWN != pptr->type;
425 }
426
427 //--------------------------------------------------------------------------------------------
sound_play_mix(fvec3_t pos,mix_ptr_t * pptr)428 int sound_play_mix( fvec3_t pos, mix_ptr_t * pptr )
429 {
430 int retval = INVALID_SOUND_CHANNEL;
431 if ( !snd.soundvalid || !mixeron )
432 {
433 return INVALID_SOUND_CHANNEL;
434 }
435 if ( NULL == pptr || MIX_UNKNOWN == pptr->type || NULL == pptr->ptr.unk )
436 {
437 log_debug( "Unable to load sound. (%s)\n", Mix_GetError() );
438 return INVALID_SOUND_CHANNEL;
439 }
440
441 retval = INVALID_SOUND_CHANNEL;
442 if ( MIX_SND == pptr->type )
443 {
444 retval = sound_play_chunk( pos, pptr->ptr.snd );
445 }
446 else if ( MIX_MUS == pptr->type )
447 {
448 // !!!!this will override the music!!!!
449
450 // add the old stream to the stack
451 music_stack_push( musictracksloaded[songplaying], songplaying );
452
453 // push on a new stream, play only once
454 retval = Mix_PlayMusic( pptr->ptr.mus, 1 );
455
456 // invalidate the song
457 songplaying = INVALID_SOUND;
458
459 // since music_stack_finished_callback() is registered using Mix_HookMusicFinished(),
460 // it will resume when pptr->ptr.mus is finished playing
461 }
462
463 return retval;
464 }
465
466 //--------------------------------------------------------------------------------------------
sound_restart()467 void sound_restart()
468 {
469 if ( mixeron )
470 {
471 Mix_CloseAudio();
472 mixeron = bfalse;
473 }
474
475 // loose the info on the currently playing song
476 if ( snd.musicvalid || snd.soundvalid )
477 {
478 if ( 0 != Mix_OpenAudio( cfg.sound_highquality_base ? MIX_HIGH_QUALITY : MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, snd.buffersize ) )
479 {
480 mixeron = btrue;
481 Mix_AllocateChannels( snd.maxsoundchannel );
482 Mix_VolumeMusic( snd.musicvolume );
483
484 // initialize the music stack
485 music_stack_init();
486
487 if ( !sound_atexit_registered )
488 {
489 atexit( sdl_mixer_quit );
490 sound_atexit_registered = btrue;
491 }
492 }
493 else
494 {
495 log_warning( "sound_restart() - Cannot get the sound module to restart. (%s)\n", Mix_GetError() );
496 }
497 }
498 }
499
500 //------------------------------------
501 // Mix_Chunk stuff -------------------
502 //------------------------------------
503
_calculate_volume(fvec3_t diff,renderlist_t * prlist)504 int _calculate_volume( fvec3_t diff, renderlist_t * prlist )
505 {
506 /// @details BB@> This calculates the volume a sound should have depending on
507 // the distance from the camera
508
509 float dist2;
510 int volume;
511 float render_size;
512
513 // approximate the radius of the area that the camera sees
514 render_size = prlist->all_count * ( GRID_FSIZE / 2 * GRID_FSIZE / 2 ) / 4;
515
516 dist2 = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
517
518 // adjust for the listen skill
519 if ( local_stats.listening_level ) dist2 *= 0.66f * 0.66f;
520
521 volume = 255 * render_size / ( render_size + dist2 );
522 volume = ( volume * snd.soundvolume ) / 100;
523
524 return volume;
525 }
526
_update_channel_volume(int channel,int volume,fvec3_t diff)527 bool_t _update_channel_volume( int channel, int volume, fvec3_t diff )
528 {
529 float pan;
530 float cosval;
531 int leftvol, rightvol;
532
533 // determine the angle away from "forward"
534 pan = ATAN2( diff.y, diff.x ) - PCamera->turn_z_rad;
535 volume *= ( 2.0f + cos( pan ) ) / 3.0f;
536
537 // determine the angle from the left ear
538 pan += 1.5f * PI;
539
540 // determine the panning
541 cosval = cos( pan );
542 cosval *= cosval;
543
544 leftvol = cosval * 128;
545 rightvol = 128 - leftvol;
546
547 leftvol = (( 127 + leftvol ) * volume ) >> 8;
548 rightvol = (( 127 + rightvol ) * volume ) >> 8;
549
550 // apply the volume adjustments
551 Mix_SetPanning( channel, leftvol, rightvol );
552
553 return btrue;
554 }
555
556 //--------------------------------------------------------------------------------------------
sound_play_chunk_looped(fvec3_t pos,Mix_Chunk * pchunk,int loops,const CHR_REF owner,renderlist_t * prlist)557 int sound_play_chunk_looped( fvec3_t pos, Mix_Chunk * pchunk, int loops, const CHR_REF owner, renderlist_t * prlist )
558 {
559 /// ZF@> This function plays a specified sound and returns which channel it's using
560 int channel = INVALID_SOUND_CHANNEL;
561 fvec3_t diff;
562 int volume;
563
564 if ( !snd.soundvalid || !mixeron || NULL == pchunk ) return INVALID_SOUND_CHANNEL;
565
566 // only play sound effects if the game is running
567 if ( !process_running( PROC_PBASE( GProc ) ) ) return INVALID_SOUND_CHANNEL;
568
569 // measure the distance in tiles
570 diff = fvec3_sub( pos.v, PCamera->track_pos.v );
571 volume = _calculate_volume( diff, prlist );
572
573 // play the sound
574 if ( volume > 0 )
575 {
576 // play the sound
577 channel = Mix_PlayChannel( -1, pchunk, loops );
578
579 if ( INVALID_SOUND_CHANNEL == channel )
580 {
581 /// @note ZF@> disabled this warning because this happens really often
582 //log_debug( "Unable to play sound. (%s)\n", Mix_GetError() );
583 }
584 else
585 {
586 if ( 0 != loops )
587 {
588 // add the sound to the LoopedList
589 LoopedList_add( pchunk, channel, owner );
590 }
591
592 //Set left/right panning
593 _update_channel_volume( channel, volume, diff );
594 }
595 }
596
597 return channel;
598 }
599
600 //--------------------------------------------------------------------------------------------
sound_play_chunk_full(Mix_Chunk * pchunk)601 int sound_play_chunk_full( Mix_Chunk * pchunk )
602 {
603 /// ZF@> This function plays a specified sound at full possible volume and returns which channel it's using
604 int channel = INVALID_SOUND_CHANNEL;
605
606 if ( !snd.soundvalid || !mixeron || NULL == pchunk ) return INVALID_SOUND_CHANNEL;
607
608 // only play sound effects if the game is running
609 if ( !process_running( PROC_PBASE( GProc ) ) ) return INVALID_SOUND_CHANNEL;
610
611 // play the sound
612 channel = Mix_PlayChannel( -1, pchunk, 0 );
613
614 // we are still limited by the global sound volume
615 Mix_Volume( channel, ( 128*snd.soundvolume ) / 100 );
616
617 return channel;
618 }
619
620 //--------------------------------------------------------------------------------------------
sound_stop_channel(int whichchannel)621 void sound_stop_channel( int whichchannel )
622 {
623 /// @details ZF@> Stops a sound effect playing in the specified channel
624 if ( mixeron && snd.soundvalid )
625 {
626 Mix_HaltChannel( whichchannel );
627 }
628 }
629
630 //------------------------------------
631 // Mix_Music stuff -------------------
632 //------------------------------------
sound_play_song(int songnumber,Uint16 fadetime,int loops)633 void sound_play_song( int songnumber, Uint16 fadetime, int loops )
634 {
635 /// @details ZF@> This functions plays a specified track loaded into memory
636 if ( !snd.musicvalid || !mixeron ) return;
637
638 if ( songplaying != songnumber )
639 {
640 // Mix_FadeOutMusic(fadetime); // Stops the game too
641
642 if ( loops != 0 )
643 {
644 if ( INVALID_SOUND != songplaying )
645 {
646 music_stack_push( musictracksloaded[songplaying], songplaying );
647 }
648 }
649
650 Mix_FadeInMusic( musictracksloaded[songnumber], loops, fadetime );
651
652 songplaying = songnumber;
653 }
654 }
655
656 //--------------------------------------------------------------------------------------------
sound_finish_song(Uint16 fadetime)657 void sound_finish_song( Uint16 fadetime )
658 {
659 Mix_Music * mus;
660 int song;
661
662 if ( !snd.musicvalid || !mixeron || songplaying == MENU_SONG ) return;
663
664 if ( !musicinmemory )
665 {
666 Mix_HaltMusic();
667 return;
668 }
669
670 // set the defaults
671 mus = musictracksloaded[MENU_SONG];
672 song = MENU_SONG;
673
674 // try to grab the last song playing
675 music_stack_pop( &mus, &song );
676
677 if ( INVALID_SOUND == song )
678 {
679 // some wierd error
680 Mix_HaltMusic();
681 }
682 else
683 {
684 if ( INVALID_SOUND != songplaying )
685 {
686 Mix_FadeOutMusic( fadetime );
687 }
688
689 // play the music
690 Mix_FadeInMusic( mus, -1, fadetime );
691
692 // set the volume
693 Mix_VolumeMusic( snd.musicvolume );
694
695 songplaying = song;
696 }
697 }
698
699 //--------------------------------------------------------------------------------------------
sound_stop_song()700 void sound_stop_song()
701 {
702 /// ZF@> This function sets music track to pause
703 if ( mixeron && snd.musicvalid )
704 {
705 Mix_HaltMusic();
706 }
707 }
708
709 //--------------------------------------------------------------------------------------------
710 //--------------------------------------------------------------------------------------------
load_global_waves()711 void load_global_waves()
712 {
713 /// @details ZZ@> This function loads the global waves
714
715 STRING wavename;
716 int cnt;
717
718 // Grab these sounds from the basicdat dir
719 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_GETCOIN] );
720 g_wavelist[GSND_GETCOIN] = sound_load_chunk_vfs( wavename );
721
722 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_DEFEND] );
723 g_wavelist[GSND_DEFEND] = sound_load_chunk_vfs( wavename );
724
725 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_COINFALL] );
726 g_wavelist[GSND_COINFALL] = sound_load_chunk_vfs( wavename );
727
728 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_LEVELUP] );
729 g_wavelist[GSND_LEVELUP] = sound_load_chunk_vfs( wavename );
730
731 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_PITFALL] );
732 g_wavelist[GSND_PITFALL] = sound_load_chunk_vfs( wavename );
733
734 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/%s", wavenames[GSND_SHIELDBLOCK] );
735 g_wavelist[GSND_SHIELDBLOCK] = sound_load_chunk_vfs( wavename );
736
737 /*
738 // These new values todo should determine global sound and particle effects
739 Weather Type: DROPS, RAIN, SNOW
740 Water Type: LAVA, WATER, DARK
741 */
742
743 for ( cnt = 0; cnt < GSND_COUNT; cnt++ )
744 {
745 Mix_Chunk * ptmp;
746
747 snprintf( wavename, SDL_arraysize( wavename ), "mp_data/sound%d", cnt );
748 ptmp = sound_load_chunk_vfs( wavename );
749
750 // only overwrite with a valid sound file
751 if ( NULL != ptmp )
752 {
753 g_wavelist[cnt] = ptmp;
754 }
755 }
756 }
757
758 //--------------------------------------------------------------------------------------------
load_all_music_sounds_vfs()759 void load_all_music_sounds_vfs()
760 {
761 /// ZF@> This function loads all of the music sounds
762 STRING loadpath;
763 STRING songname;
764 vfs_FILE *playlist;
765 Uint8 cnt;
766
767 if ( musicinmemory || !snd.musicvalid ) return;
768
769 // Open the playlist listing all music files
770 snprintf( loadpath, SDL_arraysize( loadpath ), "mp_data/music/playlist.txt" );
771 playlist = vfs_openRead( loadpath );
772 if ( NULL == playlist )
773 {
774 log_warning( "Error reading music list. (%s)\n", loadpath );
775 return;
776 }
777
778 // Load the music data into memory
779 for ( cnt = 0; cnt < MAXPLAYLISTLENGTH && !vfs_eof( playlist ); cnt++ )
780 {
781 if ( goto_colon( NULL, playlist, btrue ) )
782 {
783 fget_string( playlist, songname, SDL_arraysize( songname ) );
784
785 snprintf( loadpath, SDL_arraysize( loadpath ), ( "mp_data/music/%s" ), songname );
786 musictracksloaded[cnt] = Mix_LoadMUS( vfs_resolveReadFilename( loadpath ) );
787 }
788 }
789 musicinmemory = btrue;
790
791 //Special xmas theme, override the default menu theme song
792 if ( check_time( SEASON_CHRISTMAS ) )
793 {
794 snprintf( loadpath, SDL_arraysize( loadpath ), ( "mp_data/music/special/xmas.ogg" ), songname );
795 musictracksloaded[MENU_SONG] = Mix_LoadMUS( vfs_resolveReadFilename( loadpath ) );
796 }
797 else if ( check_time( SEASON_HALLOWEEN ) )
798 {
799 snprintf( loadpath, SDL_arraysize( loadpath ), ( "mp_data/music/special/halloween.ogg" ), songname );
800 musictracksloaded[MENU_SONG] = Mix_LoadMUS( vfs_resolveReadFilename( loadpath ) );
801 }
802
803 // A small helper for us developers
804 if ( cnt == MAXPLAYLISTLENGTH )
805 {
806 log_debug( "Play list is full. Consider increasing MAXPLAYLISTLENGTH (currently %i).", MAXPLAYLISTLENGTH );
807 }
808
809 vfs_close( playlist );
810 }
811
812 //--------------------------------------------------------------------------------------------
813 //--------------------------------------------------------------------------------------------
814
snd_config_init(snd_config_t * psnd)815 bool_t snd_config_init( snd_config_t * psnd )
816 {
817 // Initialize the sound settings and set all values to default
818 if ( NULL == psnd ) return bfalse;
819
820 psnd->soundvalid = bfalse;
821 psnd->musicvalid = bfalse;
822 psnd->musicvolume = 50; // The sound volume of music
823 psnd->soundvolume = 75; // Volume of sounds played
824 psnd->maxsoundchannel = 16; // Max number of sounds playing at the same time
825 psnd->buffersize = 2048;
826 psnd->highquality = bfalse;
827
828 return btrue;
829 }
830
831 //--------------------------------------------------------------------------------------------
snd_config_synch(snd_config_t * psnd,egoboo_config_t * pcfg)832 bool_t snd_config_synch( snd_config_t * psnd, egoboo_config_t * pcfg )
833 {
834 if ( NULL == psnd && NULL == pcfg ) return bfalse;
835
836 if ( NULL == pcfg )
837 {
838 return snd_config_init( psnd );
839 }
840
841 // coerce pcfg to have valid values
842 pcfg->sound_channel_count = CLIP( pcfg->sound_channel_count, 8, 128 );
843 pcfg->sound_buffer_size = CLIP( pcfg->sound_buffer_size, 512, 8196 );
844
845 if ( NULL != psnd )
846 {
847 psnd->soundvalid = pcfg->sound_allowed;
848 psnd->soundvolume = pcfg->sound_volume;
849 psnd->musicvalid = pcfg->music_allowed;
850 psnd->musicvolume = pcfg->music_volume;
851 psnd->maxsoundchannel = pcfg->sound_channel_count;
852 psnd->buffersize = pcfg->sound_buffer_size;
853 psnd->highquality = pcfg->sound_highquality;
854 }
855
856 return btrue;
857 }
858
859 //--------------------------------------------------------------------------------------------
sound_fade_all()860 void sound_fade_all()
861 {
862 if ( mixeron )
863 {
864 Mix_FadeOutChannel( -1, 500 ); // Stop all sounds that are playing
865 }
866 }
867
868 //--------------------------------------------------------------------------------------------
fade_in_music(Mix_Music * music)869 void fade_in_music( Mix_Music * music )
870 {
871 if ( mixeron )
872 {
873 Mix_FadeInMusic( music, -1, 500 );
874 }
875 }
876
877 //--------------------------------------------------------------------------------------------
878 //--------------------------------------------------------------------------------------------
LoopedList_init()879 void LoopedList_init()
880 {
881 /// @details BB@> setup the looped sound list
882 LOOP_REF cnt;
883 size_t tnc;
884
885 for ( cnt = 0; cnt < LOOPED_COUNT; cnt++ )
886 {
887 // clear out all of the data
888 memset( LoopedList.lst + cnt, 0, sizeof( looped_sound_data_t ) );
889
890 LoopedList.lst[cnt].channel = INVALID_SOUND_CHANNEL;
891 LoopedList.lst[cnt].chunk = NULL;
892 LoopedList.lst[cnt].object = ( CHR_REF )MAX_CHR;
893
894 tnc = REF_TO_INT( cnt );
895 LoopedList.used_ref[tnc] = LOOPED_COUNT;
896 LoopedList.free_ref[tnc] = tnc;
897 }
898
899 LoopedList.used_count = 0;
900 LoopedList.free_count = LOOPED_COUNT;
901 }
902
903 //--------------------------------------------------------------------------------------------
LoopedList_validate()904 bool_t LoopedList_validate()
905 {
906 /// @details BB@> do the free and used indices have valid values?
907
908 bool_t retval;
909
910 retval = btrue;
911 if ( LOOPED_COUNT != LoopedList.free_count + LoopedList.used_count )
912 {
913 // punt!
914 LoopedList_clear();
915 retval = bfalse;
916 }
917
918 return retval;
919 }
920
921 //--------------------------------------------------------------------------------------------
LoopedList_free_one(size_t index)922 bool_t LoopedList_free_one( size_t index )
923 {
924 /// @details BB@> free a looped sound only if it is actually being used
925 int cnt;
926 LOOP_REF ref;
927
928 if ( !LoopedList_validate() ) return bfalse;
929
930 // is the index actually free?
931 for ( cnt = 0; cnt < LoopedList.used_count; cnt++ )
932 {
933 if ( index == LoopedList.used_ref[cnt] ) break;
934 }
935
936 // was anything found?
937 if ( cnt >= LoopedList.used_count ) return bfalse;
938
939 // swap the value with the one on the top of the stack
940 SWAP( size_t, LoopedList.used_ref[cnt], LoopedList.used_ref[LoopedList.used_count-1] );
941
942 LoopedList.used_count--;
943 LoopedList.update_guid++;
944
945 // push the value onto the free stack
946 LoopedList.free_ref[LoopedList.free_count] = index;
947
948 LoopedList.free_count++;
949 LoopedList.update_guid++;
950
951 // clear out the data
952 ref = ( LOOP_REF )index;
953 LoopedList.lst[ref].channel = INVALID_SOUND_CHANNEL;
954 LoopedList.lst[ref].chunk = NULL;
955 LoopedList.lst[ref].object = ( CHR_REF )MAX_CHR;
956
957 return btrue;
958 }
959
960 //--------------------------------------------------------------------------------------------
LoopedList_get_free()961 size_t LoopedList_get_free()
962 {
963 size_t index;
964
965 if ( !LoopedList_validate() ) return bfalse;
966
967 LoopedList.free_count--;
968 LoopedList.update_guid++;
969
970 index = LoopedList.free_ref[LoopedList.free_count];
971
972 // push the value onto the used stack
973 LoopedList.used_ref[LoopedList.used_count] = index;
974
975 LoopedList.used_count++;
976 LoopedList.update_guid++;
977
978 return index;
979 }
980
981 //--------------------------------------------------------------------------------------------
LoopedList_clear()982 void LoopedList_clear()
983 {
984 /// @details BB@> shut off all the looped sounds
985
986 LOOP_REF cnt;
987
988 for ( cnt = 0; cnt < LOOPED_COUNT; cnt++ )
989 {
990 if ( INVALID_SOUND_CHANNEL != LoopedList.lst[cnt].channel )
991 {
992 Mix_FadeOutChannel( LoopedList.lst[cnt].channel, 500 );
993
994 // clear out the data
995 LoopedList.lst[cnt].channel = INVALID_SOUND_CHANNEL;
996 LoopedList.lst[cnt].chunk = NULL;
997 LoopedList.lst[cnt].object = ( CHR_REF )MAX_CHR;
998 }
999 }
1000
1001 LoopedList_init();
1002 }
1003
1004 //--------------------------------------------------------------------------------------------
LoopedList_add(Mix_Chunk * sound,int channel,const CHR_REF ichr)1005 size_t LoopedList_add( Mix_Chunk * sound, int channel, const CHR_REF ichr )
1006 {
1007 /// @details BB@> add a looped sound to the list
1008
1009 size_t index;
1010
1011 if ( NULL == sound || INVALID_SOUND_CHANNEL == channel || !INGAME_CHR( ichr ) ) return LOOPED_COUNT;
1012
1013 if ( LoopedList.used_count >= LOOPED_COUNT ) return LOOPED_COUNT;
1014 if ( !LoopedList_validate() ) return LOOPED_COUNT;
1015
1016 index = LoopedList_get_free();
1017 if ( index != LOOPED_COUNT )
1018 {
1019 // set up the LoopedList entry at the empty index
1020 LOOP_REF ref = ( LOOP_REF )index;
1021
1022 LoopedList.lst[ref].chunk = sound;
1023 LoopedList.lst[ref].channel = channel;
1024 LoopedList.lst[ref].object = ichr;
1025 }
1026
1027 return index;
1028 }
1029
1030 //--------------------------------------------------------------------------------------------
LoopedList_remove(int channel)1031 bool_t LoopedList_remove( int channel )
1032 {
1033 /// @details BB@> remove a looped sound from the used list
1034
1035 int cnt;
1036 bool_t retval;
1037
1038 if ( 0 == LoopedList.used_count ) return bfalse;
1039
1040 if ( !LoopedList_validate() ) return bfalse;
1041
1042 retval = bfalse;
1043 for ( cnt = 0; cnt < LoopedList.used_count; cnt++ )
1044 {
1045 size_t index = LoopedList.used_ref[cnt];
1046
1047 if ( index != LOOPED_COUNT )
1048 {
1049 LOOP_REF ref = ( LOOP_REF ) index;
1050
1051 if ( channel == LoopedList.lst[ref].channel )
1052 {
1053 retval = LoopedList_free_one( cnt );
1054 break;
1055 }
1056 }
1057 }
1058
1059 return retval;
1060 }
1061
1062 //--------------------------------------------------------------------------------------------
_update_stereo_channel(int channel,fvec3_t diff,renderlist_t * prlist)1063 bool_t _update_stereo_channel( int channel, fvec3_t diff, renderlist_t * prlist )
1064 {
1065 /// @details BB@> This updates the stereo image of a looped sound
1066
1067 int volume;
1068
1069 if ( INVALID_SOUND_CHANNEL == channel ) return bfalse;
1070
1071 volume = _calculate_volume( diff, prlist );
1072 return _update_channel_volume( channel, volume, diff );
1073 }
1074
1075 //--------------------------------------------------------------------------------------------
looped_update_all_sound(renderlist_t * prlist)1076 void looped_update_all_sound( renderlist_t * prlist )
1077 {
1078 int cnt;
1079
1080 for ( cnt = 0; cnt < LoopedList.used_count; cnt++ )
1081 {
1082 fvec3_t diff;
1083 size_t index;
1084 LOOP_REF ref;
1085 looped_sound_data_t * plooped;
1086
1087 index = LoopedList.used_ref[cnt];
1088 if ( index < 0 || index >= LOOPED_COUNT ) continue;
1089
1090 ref = ( LOOP_REF )index;
1091
1092 if ( INVALID_SOUND_CHANNEL == LoopedList.lst[ref].channel ) continue;
1093 plooped = LoopedList.lst + ref;
1094
1095 if ( !INGAME_CHR( plooped->object ) )
1096 {
1097 // not a valid object
1098 fvec3_t diff = VECT3( 0, 0, 0 );
1099
1100 _update_stereo_channel( plooped->channel, diff, prlist );
1101 }
1102 else
1103 {
1104 // make the sound stick to the object
1105 diff = fvec3_sub( ChrList.lst[plooped->object].pos.v, PCamera->track_pos.v );
1106
1107 _update_stereo_channel( plooped->channel, diff, prlist );
1108 }
1109 }
1110 }
1111
1112 //--------------------------------------------------------------------------------------------
looped_stop_object_sounds(const CHR_REF ichr)1113 bool_t looped_stop_object_sounds( const CHR_REF ichr )
1114 {
1115 /// @details BB@> free any looped sound(s) being made by a certain character
1116 int freed;
1117 int cnt;
1118 bool_t found;
1119
1120 if ( !ALLOCATED_CHR( ichr ) ) return bfalse;
1121
1122 // we have to do this a funny way, because it is hard to guarantee how the
1123 // "delete"/"free" function LoopedList_free_one() will free an entry, and because
1124 // each object could have multiple looped sounds
1125
1126 freed = 0;
1127 found = btrue;
1128 while ( found && LoopedList.used_count > 0 )
1129 {
1130 found = bfalse;
1131 for ( cnt = 0; cnt < LoopedList.used_count; cnt++ )
1132 {
1133 LOOP_REF ref;
1134
1135 size_t index = LoopedList.used_ref[cnt];
1136 if ( index < 0 || index >= LOOPED_COUNT ) continue;
1137
1138 ref = ( LOOP_REF )index;
1139
1140 if ( LoopedList.lst[ref].object == ichr )
1141 {
1142 int channel = LoopedList.lst[ref].channel;
1143
1144 if ( LoopedList_free_one( index ) )
1145 {
1146 freed++;
1147 found = btrue;
1148 sound_stop_channel( channel );
1149 break;
1150 }
1151 }
1152 }
1153 }
1154
1155 return freed > 0;
1156 }
1157
1158 //--------------------------------------------------------------------------------------------
sound_finish_sound()1159 void sound_finish_sound()
1160 {
1161 Mix_FadeOutChannel( -1, 500 ); // Stop all in-game sounds that are playing
1162 sound_finish_song( 500 ); // Fade out the existing song and pop the music stack
1163 }
1164
1165 //--------------------------------------------------------------------------------------------
sound_free_chunk(Mix_Chunk * pchunk)1166 void sound_free_chunk( Mix_Chunk * pchunk )
1167 {
1168 if ( mixeron )
1169 {
1170 Mix_FreeChunk( pchunk );
1171 }
1172 }
1173
1174 //--------------------------------------------------------------------------------------------
get_current_song_playing()1175 int get_current_song_playing()
1176 {
1177 //ZF> This gives read access to the private variable 'songplaying'
1178 return songplaying;
1179 }
1180