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