1 #if defined(SDL_SOUND)
2
3 #include "sdlsound.h"
4
5 #include <cstdlib>
6 #include <algorithm>
7 #include <chrono>
8 #include <map>
9 #include <string>
10 #include <unordered_map>
11 #include <vector>
12 #include <exception>
13 #include <memory>
14 #include <ostream>
15 #include <utility>
16
17 #if defined(_MSC_VER) && defined(USE_VCPKG)
18 # include <SDL2/SDL_mixer.h>
19 #else
20 # include <SDL_mixer.h>
21 #endif
22
23 #include "cached_options.h"
24 #include "debug.h"
25 #include "init.h"
26 #include "json.h"
27 #include "loading_ui.h"
28 #include "messages.h"
29 #include "options.h"
30 #include "path_info.h"
31 #include "rng.h"
32 #include "sdl_wrappers.h"
33 #include "sounds.h"
34 #include "units.h"
35
36 #define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
37
38 using id_and_variant = std::pair<std::string, std::string>;
39 struct sound_effect_resource {
40 std::string path;
41 struct deleter {
42 // Operator overloaded to leverage deletion API.
operator ()sound_effect_resource::deleter43 void operator()( Mix_Chunk *const c ) const {
44 Mix_FreeChunk( c );
45 }
46 };
47 std::unique_ptr<Mix_Chunk, deleter> chunk;
48 };
49 struct sound_effect {
50 int volume = 0;
51 int resource_id = 0;
52 };
53 struct sfx_resources_t {
54 std::vector<sound_effect_resource> resource;
55 std::map<id_and_variant, std::vector<sound_effect>> sound_effects;
56 };
57 struct music_playlist {
58 // list of filenames relative to the soundpack location
59 struct entry {
60 std::string file;
61 int volume;
62 };
63 std::vector<entry> entries;
64 bool shuffle;
65
music_playlistmusic_playlist66 music_playlist() : shuffle( false ) {
67 }
68 };
69 /** The music we're currently playing. */
70 static Mix_Music *current_music = nullptr;
71 static int current_music_track_volume = 0;
72 static std::string current_playlist;
73 static size_t current_playlist_at = 0;
74 static size_t absolute_playlist_at = 0;
75 static std::vector<std::size_t> playlist_indexes;
76 static bool sound_init_success = false;
77 static std::map<std::string, music_playlist> playlists;
78 static std::string current_soundpack_path;
79
80 static std::unordered_map<std::string, int> unique_paths;
81 static sfx_resources_t sfx_resources;
82 static std::vector<id_and_variant> sfx_preload;
83
84 bool sounds::sound_enabled = false;
85
check_sound(const int volume=1)86 static inline bool check_sound( const int volume = 1 )
87 {
88 return( sound_init_success && sounds::sound_enabled && volume > 0 );
89 }
90
91 /**
92 * Attempt to initialize an audio device. Returns false if initialization fails.
93 */
init_sound()94 bool init_sound()
95 {
96 int audio_rate = 44100;
97 Uint16 audio_format = AUDIO_S16;
98 int audio_channels = 2;
99 int audio_buffers = 2048;
100
101 // We should only need to init once
102 if( !sound_init_success ) {
103 // Mix_OpenAudio returns non-zero if something went wrong trying to open the device
104 if( !Mix_OpenAudio( audio_rate, audio_format, audio_channels, audio_buffers ) ) {
105 Mix_AllocateChannels( 128 );
106 Mix_ReserveChannels( static_cast<int>( sfx::channel::MAX_CHANNEL ) );
107
108 // For the sound effects system.
109 Mix_GroupChannels( static_cast<int>( sfx::channel::daytime_outdoors_env ),
110 static_cast<int>( sfx::channel::nighttime_outdoors_env ),
111 static_cast<int>( sfx::group::time_of_day ) );
112 Mix_GroupChannels( static_cast<int>( sfx::channel::underground_env ),
113 static_cast<int>( sfx::channel::outdoor_blizzard ),
114 static_cast<int>( sfx::group::weather ) );
115 Mix_GroupChannels( static_cast<int>( sfx::channel::danger_extreme_theme ),
116 static_cast<int>( sfx::channel::danger_low_theme ),
117 static_cast<int>( sfx::group::context_themes ) );
118 Mix_GroupChannels( static_cast<int>( sfx::channel::stamina_75 ),
119 static_cast<int>( sfx::channel::stamina_35 ),
120 static_cast<int>( sfx::group::fatigue ) );
121
122 sound_init_success = true;
123 } else {
124 dbg( D_ERROR ) << "Failed to open audio mixer, sound won't work: " << Mix_GetError();
125 }
126 }
127
128 return sound_init_success;
129 }
shutdown_sound()130 void shutdown_sound()
131 {
132 // De-allocate all loaded sound.
133 sfx_resources.resource.clear();
134 sfx_resources.sound_effects.clear();
135
136 playlists.clear();
137 Mix_CloseAudio();
138 }
139
140 static void musicFinished();
141
play_music_file(const std::string & filename,int volume)142 static void play_music_file( const std::string &filename, int volume )
143 {
144 if( test_mode ) {
145 return;
146 }
147
148 if( !check_sound( volume ) ) {
149 return;
150 }
151
152 const std::string path = ( current_soundpack_path + "/" + filename );
153 current_music = Mix_LoadMUS( path.c_str() );
154 if( current_music == nullptr ) {
155 dbg( D_ERROR ) << "Failed to load audio file " << path << ": " << Mix_GetError();
156 return;
157 }
158 Mix_VolumeMusic( volume * get_option<int>( "MUSIC_VOLUME" ) / 100 );
159 if( Mix_PlayMusic( current_music, 0 ) != 0 ) {
160 dbg( D_ERROR ) << "Starting playlist " << path << " failed: " << Mix_GetError();
161 return;
162 }
163 Mix_HookMusicFinished( musicFinished );
164 }
165
166 /** Callback called when we finish playing music. */
musicFinished()167 void musicFinished()
168 {
169 if( test_mode ) {
170 return;
171 }
172
173 Mix_HaltMusic();
174 Mix_FreeMusic( current_music );
175 current_music = nullptr;
176
177 const auto iter = playlists.find( current_playlist );
178 if( iter == playlists.end() ) {
179 return;
180 }
181 const music_playlist &list = iter->second;
182 if( list.entries.empty() ) {
183 return;
184 }
185
186 // Load the next file to play.
187 absolute_playlist_at++;
188
189 // Wrap around if we reached the end of the playlist.
190 if( absolute_playlist_at >= list.entries.size() ) {
191 absolute_playlist_at = 0;
192 }
193
194 current_playlist_at = playlist_indexes.at( absolute_playlist_at );
195
196 const music_playlist::entry &next = list.entries[current_playlist_at];
197 play_music_file( next.file, next.volume );
198 }
199
play_music(const std::string & playlist)200 void play_music( const std::string &playlist )
201 {
202 const auto iter = playlists.find( playlist );
203 if( iter == playlists.end() ) {
204 return;
205 }
206 const music_playlist &list = iter->second;
207 if( list.entries.empty() ) {
208 return;
209 }
210
211 // Don't interrupt playlist that's already playing.
212 if( playlist == current_playlist ) {
213 return;
214 }
215
216 for( size_t i = 0; i < list.entries.size(); i++ ) {
217 playlist_indexes.push_back( i );
218 }
219 if( list.shuffle ) {
220 // Son't need to worry about the determinism check here because it only
221 // affects audio, not game logic.
222 // NOLINTNEXTLINE(cata-determinism)
223 static auto eng = cata_default_random_engine(
224 std::chrono::system_clock::now().time_since_epoch().count() );
225 std::shuffle( playlist_indexes.begin(), playlist_indexes.end(), eng );
226 }
227
228 current_playlist = playlist;
229 current_playlist_at = playlist_indexes.at( absolute_playlist_at );
230
231 const music_playlist::entry &next = list.entries[current_playlist_at];
232 current_music_track_volume = next.volume;
233 play_music_file( next.file, next.volume );
234 }
235
stop_music()236 void stop_music()
237 {
238 if( test_mode ) {
239 return;
240 }
241
242 Mix_FreeMusic( current_music );
243 Mix_HaltMusic();
244 current_music = nullptr;
245
246 current_playlist.clear();
247 current_playlist_at = 0;
248 absolute_playlist_at = 0;
249 }
250
update_music_volume()251 void update_music_volume()
252 {
253 if( test_mode ) {
254 return;
255 }
256
257 sounds::sound_enabled = ::get_option<bool>( "SOUND_ENABLED" );
258
259 if( !sounds::sound_enabled ) {
260 stop_music();
261 return;
262 }
263
264 Mix_VolumeMusic( current_music_track_volume * get_option<int>( "MUSIC_VOLUME" ) / 100 );
265 // Start playing music, if we aren't already doing so (if
266 // SOUND_ENABLED was toggled.)
267
268 // needs to be changed to something other than a static string when
269 // #28018 is resolved, as this function may be called from places
270 // other than the main menu.
271 play_music( "title" );
272 }
273
274 // Allocate new Mix_Chunk as a null-chunk. Results in a valid, but empty chunk
275 // that is created when loading of a sound effect resource fails. Does not own
276 // memory. Mix_FreeChunk will free the SDL_malloc'd Mix_Chunk pointer.
make_null_chunk()277 static Mix_Chunk *make_null_chunk()
278 {
279 static Mix_Chunk null_chunk = { 0, nullptr, 0, 0 };
280 // SDL_malloc to match up with Mix_FreeChunk's SDL_free call
281 // to free the Mix_Chunk object memory
282 Mix_Chunk *nchunk = static_cast<Mix_Chunk *>( SDL_malloc( sizeof( Mix_Chunk ) ) );
283
284 // Assign as copy of null_chunk
285 ( *nchunk ) = null_chunk;
286 return nchunk;
287 }
288
load_chunk(const std::string & path)289 static Mix_Chunk *load_chunk( const std::string &path )
290 {
291 Mix_Chunk *result = Mix_LoadWAV( path.c_str() );
292 if( result == nullptr ) {
293 // Failing to load a sound file is not a fatal error worthy of a backtrace
294 dbg( D_WARNING ) << "Failed to load sfx audio file " << path << ": " << Mix_GetError();
295 result = make_null_chunk();
296 }
297 return result;
298 }
299
300 // Check to see if the resource has already been loaded
301 // - Loaded: Return stored pointer
302 // - Not Loaded: Load chunk from stored resource path
get_sfx_resource(int resource_id)303 static inline Mix_Chunk *get_sfx_resource( int resource_id )
304 {
305 sound_effect_resource &resource = sfx_resources.resource[ resource_id ];
306 if( !resource.chunk ) {
307 std::string path = ( current_soundpack_path + "/" + resource.path );
308 resource.chunk.reset( load_chunk( path ) );
309 }
310 return resource.chunk.get();
311 }
312
add_sfx_path(const std::string & path)313 static inline int add_sfx_path( const std::string &path )
314 {
315 auto find_result = unique_paths.find( path );
316 if( find_result != unique_paths.end() ) {
317 return find_result->second;
318 } else {
319 int result = sfx_resources.resource.size();
320 sound_effect_resource new_resource;
321 new_resource.path = path;
322 new_resource.chunk.reset();
323 sfx_resources.resource.push_back( std::move( new_resource ) );
324 unique_paths[ path ] = result;
325 return result;
326 }
327 }
328
load_sound_effects(const JsonObject & jsobj)329 void sfx::load_sound_effects( const JsonObject &jsobj )
330 {
331 if( !sound_init_success ) {
332 return;
333 }
334 const id_and_variant key( jsobj.get_string( "id" ), jsobj.get_string( "variant", "default" ) );
335 const int volume = jsobj.get_int( "volume", 100 );
336 auto &effects = sfx_resources.sound_effects[ key ];
337
338 for( const std::string file : jsobj.get_array( "files" ) ) {
339 sound_effect new_sound_effect;
340 new_sound_effect.volume = volume;
341 new_sound_effect.resource_id = add_sfx_path( file );
342
343 effects.push_back( new_sound_effect );
344 }
345 }
load_sound_effect_preload(const JsonObject & jsobj)346 void sfx::load_sound_effect_preload( const JsonObject &jsobj )
347 {
348 if( !sound_init_success ) {
349 return;
350 }
351
352 for( JsonObject aobj : jsobj.get_array( "preload" ) ) {
353 const id_and_variant preload_key( aobj.get_string( "id" ), aobj.get_string( "variant",
354 "default" ) );
355 sfx_preload.push_back( preload_key );
356 }
357 }
358
load_playlist(const JsonObject & jsobj)359 void sfx::load_playlist( const JsonObject &jsobj )
360 {
361 if( !sound_init_success ) {
362 return;
363 }
364
365 for( JsonObject playlist : jsobj.get_array( "playlists" ) ) {
366 const std::string playlist_id = playlist.get_string( "id" );
367 music_playlist playlist_to_load;
368 playlist_to_load.shuffle = playlist.get_bool( "shuffle", false );
369
370 for( JsonObject entry : playlist.get_array( "files" ) ) {
371 const music_playlist::entry e{ entry.get_string( "file" ), entry.get_int( "volume" ) };
372 playlist_to_load.entries.push_back( e );
373 }
374
375 playlists[playlist_id] = std::move( playlist_to_load );
376 }
377 }
378
379 // Returns a random sound effect matching given id and variant or `nullptr` if there is no
380 // matching sound effect.
find_random_effect(const id_and_variant & id_variants_pair)381 static const sound_effect *find_random_effect( const id_and_variant &id_variants_pair )
382 {
383 const auto iter = sfx_resources.sound_effects.find( id_variants_pair );
384 if( iter == sfx_resources.sound_effects.end() ) {
385 return nullptr;
386 }
387 return &random_entry_ref( iter->second );
388 }
389
390 // Same as above, but with fallback to "default" variant. May still return `nullptr`
find_random_effect(const std::string & id,const std::string & variant)391 static const sound_effect *find_random_effect( const std::string &id, const std::string &variant )
392 {
393 const sound_effect *eff = find_random_effect( id_and_variant( id, variant ) );
394 if( eff != nullptr ) {
395 return eff;
396 }
397 return find_random_effect( id_and_variant( id, "default" ) );
398 }
399
has_variant_sound(const std::string & id,const std::string & variant)400 bool sfx::has_variant_sound( const std::string &id, const std::string &variant )
401 {
402 return find_random_effect( id, variant ) != nullptr;
403 }
404
405 // Deletes the dynamically created chunk (if such a chunk had been played).
cleanup_when_channel_finished(int,void * udata)406 static void cleanup_when_channel_finished( int /* channel */, void *udata )
407 {
408 Mix_Chunk *chunk = static_cast<Mix_Chunk *>( udata );
409 free( chunk->abuf );
410 free( chunk );
411 }
412
413 // empty effect, as we cannot change the size of the output buffer,
414 // therefore we cannot do the math from do_pitch_shift here
empty_effect(int,void *,int,void *)415 static void empty_effect( int /* chan */, void * /* stream */, int /* len */, void * /* udata */ )
416 {
417 }
418
do_pitch_shift(Mix_Chunk * s,float pitch)419 static Mix_Chunk *do_pitch_shift( Mix_Chunk *s, float pitch )
420 {
421 Uint32 s_in = s->alen / 4;
422 Uint32 s_out = static_cast<Uint32>( static_cast<float>( s_in ) * pitch );
423 float pitch_real = static_cast<float>( s_out ) / static_cast<float>( s_in );
424 Mix_Chunk *result = static_cast<Mix_Chunk *>( malloc( sizeof( Mix_Chunk ) ) );
425 result->allocated = 1;
426 result->alen = s_out * 4;
427 result->abuf = static_cast<Uint8 *>( malloc( result->alen * sizeof( Uint8 ) ) );
428 result->volume = s->volume;
429 for( Uint32 i = 0; i < s_out; i++ ) {
430 Sint16 lt = 0;
431 Sint16 rt = 0;
432 Sint16 lt_out = 0;
433 Sint16 rt_out = 0;
434 Sint64 lt_avg = 0;
435 Sint64 rt_avg = 0;
436 Uint32 begin = static_cast<Uint32>( static_cast<float>( i ) / pitch_real );
437 Uint32 end = static_cast<Uint32>( static_cast<float>( i + 1 ) / pitch_real );
438
439 // check for boundary case
440 if( end > 0 && ( end >= ( s->alen / 4 ) ) ) {
441 end = begin;
442 }
443
444 for( Uint32 j = begin; j <= end; j++ ) {
445 lt = ( s->abuf[( 4 * j ) + 1] << 8 ) | ( s->abuf[( 4 * j ) + 0] );
446 rt = ( s->abuf[( 4 * j ) + 3] << 8 ) | ( s->abuf[( 4 * j ) + 2] );
447 lt_avg += lt;
448 rt_avg += rt;
449 }
450 lt_out = static_cast<Sint16>( static_cast<float>( lt_avg ) / static_cast<float>
451 ( end - begin + 1 ) );
452 rt_out = static_cast<Sint16>( static_cast<float>( rt_avg ) / static_cast<float>
453 ( end - begin + 1 ) );
454 result->abuf[( 4 * i ) + 1] = static_cast<Uint8>( ( lt_out >> 8 ) & 0xFF );
455 result->abuf[( 4 * i ) + 0] = static_cast<Uint8>( lt_out & 0xFF );
456 result->abuf[( 4 * i ) + 3] = static_cast<Uint8>( ( rt_out >> 8 ) & 0xFF );
457 result->abuf[( 4 * i ) + 2] = static_cast<Uint8>( rt_out & 0xFF );
458 }
459 return result;
460 }
461
play_variant_sound(const std::string & id,const std::string & variant,int volume)462 void sfx::play_variant_sound( const std::string &id, const std::string &variant, int volume )
463 {
464 if( test_mode ) {
465 return;
466 }
467
468 add_msg_debug( "sound id: %s, variant: %s, volume: %d ", id, variant, volume );
469
470 if( !check_sound( volume ) ) {
471 return;
472 }
473 const sound_effect *eff = find_random_effect( id, variant );
474 if( eff == nullptr ) {
475 eff = find_random_effect( id, "default" );
476 if( eff == nullptr ) {
477 return;
478 }
479 }
480 const sound_effect &selected_sound_effect = *eff;
481
482 Mix_Chunk *effect_to_play = get_sfx_resource( selected_sound_effect.resource_id );
483 Mix_VolumeChunk( effect_to_play,
484 selected_sound_effect.volume * get_option<int>( "SOUND_EFFECT_VOLUME" ) * volume / ( 100 * 100 ) );
485 bool failed = ( Mix_PlayChannel( static_cast<int>( channel::any ), effect_to_play, 0 ) == -1 );
486 if( failed ) {
487 dbg( D_ERROR ) << "Failed to play sound effect: " << Mix_GetError();
488 }
489 }
490
play_variant_sound(const std::string & id,const std::string & variant,int volume,units::angle angle,double pitch_min,double pitch_max)491 void sfx::play_variant_sound( const std::string &id, const std::string &variant, int volume,
492 units::angle angle, double pitch_min, double pitch_max )
493 {
494 if( test_mode ) {
495 return;
496 }
497
498 add_msg_debug( "sound id: %s, variant: %s, volume: %d ", id, variant, volume );
499
500 if( !check_sound( volume ) ) {
501 return;
502 }
503 const sound_effect *eff = find_random_effect( id, variant );
504 if( eff == nullptr ) {
505 return;
506 }
507 const sound_effect &selected_sound_effect = *eff;
508
509 Mix_Chunk *effect_to_play = get_sfx_resource( selected_sound_effect.resource_id );
510 bool is_pitched = ( pitch_min > 0 ) && ( pitch_max > 0 );
511 if( is_pitched ) {
512 double pitch_random = rng_float( pitch_min, pitch_max );
513 effect_to_play = do_pitch_shift( effect_to_play, static_cast<float>( pitch_random ) );
514 }
515 Mix_VolumeChunk( effect_to_play,
516 selected_sound_effect.volume * get_option<int>( "SOUND_EFFECT_VOLUME" ) * volume / ( 100 * 100 ) );
517 int channel = Mix_PlayChannel( static_cast<int>( sfx::channel::any ), effect_to_play, 0 );
518 bool failed = ( channel == -1 );
519 if( !failed && is_pitched ) {
520 if( Mix_RegisterEffect( channel, empty_effect, cleanup_when_channel_finished,
521 effect_to_play ) == 0 ) {
522 // To prevent use after free, stop the playback right now.
523 failed = true;
524 dbg( D_WARNING ) << "Mix_RegisterEffect failed: " << Mix_GetError();
525 Mix_HaltChannel( channel );
526 }
527 }
528 if( !failed ) {
529 if( Mix_SetPosition( channel, static_cast<Sint16>( to_degrees( angle ) ), 1 ) == 0 ) {
530 // Not critical
531 dbg( D_INFO ) << "Mix_SetPosition failed: " << Mix_GetError();
532 }
533 }
534 if( failed ) {
535 dbg( D_ERROR ) << "Failed to play sound effect: " << Mix_GetError();
536 if( is_pitched ) {
537 cleanup_when_channel_finished( channel, effect_to_play );
538 }
539 }
540 }
541
play_ambient_variant_sound(const std::string & id,const std::string & variant,int volume,channel channel,int fade_in_duration,double pitch,int loops)542 void sfx::play_ambient_variant_sound( const std::string &id, const std::string &variant, int volume,
543 channel channel, int fade_in_duration, double pitch, int loops )
544 {
545 if( test_mode ) {
546 return;
547 }
548 if( !check_sound( volume ) ) {
549 return;
550 }
551 if( is_channel_playing( channel ) ) {
552 return;
553 }
554 const sound_effect *eff = find_random_effect( id, variant );
555 if( eff == nullptr ) {
556 return;
557 }
558 const sound_effect &selected_sound_effect = *eff;
559
560 Mix_Chunk *effect_to_play = get_sfx_resource( selected_sound_effect.resource_id );
561 bool is_pitched = ( pitch > 0 );
562 if( is_pitched ) {
563 effect_to_play = do_pitch_shift( effect_to_play, static_cast<float>( pitch ) );
564 }
565 Mix_VolumeChunk( effect_to_play,
566 selected_sound_effect.volume * get_option<int>( "AMBIENT_SOUND_VOLUME" ) * volume / ( 100 * 100 ) );
567 bool failed = false;
568 int ch = static_cast<int>( channel );
569 if( fade_in_duration ) {
570 failed = ( Mix_FadeInChannel( ch, effect_to_play, loops, fade_in_duration ) == -1 );
571 } else {
572 failed = ( Mix_PlayChannel( ch, effect_to_play, loops ) == -1 );
573 }
574 if( !failed && is_pitched ) {
575 if( Mix_RegisterEffect( ch, empty_effect, cleanup_when_channel_finished, effect_to_play ) == 0 ) {
576 // To prevent use after free, stop the playback right now.
577 failed = true;
578 dbg( D_WARNING ) << "Mix_RegisterEffect failed: " << Mix_GetError();
579 Mix_HaltChannel( ch );
580 }
581 }
582 if( failed ) {
583 dbg( D_ERROR ) << "Failed to play sound effect: " << Mix_GetError();
584 if( is_pitched ) {
585 cleanup_when_channel_finished( ch, effect_to_play );
586 }
587 }
588 }
589
load_soundset()590 void load_soundset()
591 {
592 const std::string default_path = PATH_INFO::defaultsounddir();
593 const std::string default_soundpack = "basic";
594 std::string current_soundpack = get_option<std::string>( "SOUNDPACKS" );
595 std::string soundpack_path;
596
597 // Get current soundpack and it's directory path.
598 if( current_soundpack.empty() ) {
599 dbg( D_ERROR ) << "Soundpack not set in options or empty.";
600 soundpack_path = default_path;
601 current_soundpack = default_soundpack;
602 } else {
603 dbg( D_INFO ) << "Current soundpack is: " << current_soundpack;
604 soundpack_path = SOUNDPACKS[current_soundpack];
605 }
606
607 if( soundpack_path.empty() ) {
608 dbg( D_ERROR ) << "Soundpack with name " << current_soundpack << " can't be found or empty string";
609 soundpack_path = default_path;
610 current_soundpack = default_soundpack;
611 } else {
612 dbg( D_INFO ) << '"' << current_soundpack << '"' << " soundpack: found path: " << soundpack_path;
613 }
614
615 current_soundpack_path = soundpack_path;
616 try {
617 loading_ui ui( false );
618 DynamicDataLoader::get_instance().load_data_from_path( soundpack_path, "core", ui );
619 } catch( const std::exception &err ) {
620 dbg( D_ERROR ) << "failed to load sounds: " << err.what();
621 }
622
623 // Preload sound effects
624 for( const id_and_variant &preload : sfx_preload ) {
625 const auto find_result = sfx_resources.sound_effects.find( preload );
626 if( find_result != sfx_resources.sound_effects.end() ) {
627 for( const auto &sfx : find_result->second ) {
628 get_sfx_resource( sfx.resource_id );
629 }
630 }
631 }
632
633 // Memory of unique_paths no longer required, swap with locally scoped unordered_map
634 // to force deallocation of resources.
635 {
636 unique_paths.clear();
637 std::unordered_map<std::string, int> t_swap;
638 unique_paths.swap( t_swap );
639 }
640 // Memory of sfx_preload no longer required, swap with locally scoped vector
641 // to force deallocation of resources.
642 {
643 sfx_preload.clear();
644 std::vector<id_and_variant> t_swap;
645 sfx_preload.swap( t_swap );
646 }
647 }
648
649 #endif
650