25 #include <SDL2/SDL_mixer.h> 32 #define DBG_AUDIO LOG_STREAM(debug, log_audio) 33 #define LOG_AUDIO LOG_STREAM(info, log_audio) 34 #define ERR_AUDIO LOG_STREAM(err, log_audio) 36 #if (MIX_MAJOR_VERSION < 1) || (MIX_MAJOR_VERSION == 1) && ((MIX_MINOR_VERSION < 2) || (MIX_MINOR_VERSION == 2) && (MIX_PATCHLEVEL <= 11)) 37 #error "Please upgrade to SDL mixer version >= 1.2.12, we don't support older versions anymore." 53 int music_start_time = 0;
54 unsigned music_refresh = 0;
55 unsigned music_refresh_rate = 20;
56 bool want_new_music =
false;
57 int fadingout_time = 5000;
58 bool no_fading =
false;
59 bool unload_music =
false;
62 const std::size_t n_of_channels = 32;
65 const std::size_t bell_channel = 0;
66 const std::size_t timer_channel = 1;
69 const std::size_t source_channels = 8;
70 const std::size_t source_channel_start = timer_channel + 1;
71 const std::size_t source_channel_last = source_channel_start + source_channels - 1;
72 const std::size_t UI_sound_channels = 2;
73 const std::size_t UI_sound_channel_start = source_channel_last + 1;
74 const std::size_t UI_sound_channel_last = UI_sound_channel_start + UI_sound_channels - 1;
75 const std::size_t n_reserved_channels = UI_sound_channel_last + 1;
79 unsigned max_cached_chunks = 256;
81 std::map<Mix_Chunk*, int> chunk_usage;
96 assert(this_usage != chunk_usage.end());
97 if(--(this_usage->second) == 0) {
99 chunk_usage.erase(this_usage);
105 class sound_cache_chunk
108 sound_cache_chunk(
const std::string&
f)
114 sound_cache_chunk(
const sound_cache_chunk& scc)
130 void set_data(Mix_Chunk*
d)
137 Mix_Chunk* get_data()
const 142 bool operator==(
const sound_cache_chunk& scc)
const 144 return file == scc.file;
147 bool operator!=(
const sound_cache_chunk& scc)
const 152 sound_cache_chunk& operator=(
const sound_cache_chunk& scc)
156 set_data(scc.get_data());
164 std::list<sound_cache_chunk> sound_cache;
166 std::map<std::string, std::shared_ptr<Mix_Music>> music_cache;
168 std::vector<std::string> played_before;
178 std::vector<std::shared_ptr<sound::music_track>> current_track_list;
179 std::shared_ptr<sound::music_track> current_track;
180 unsigned int current_track_index = 0;
181 std::shared_ptr<sound::music_track> previous_track;
183 std::vector<std::shared_ptr<sound::music_track>>::const_iterator find_track(
const sound::music_track& track)
185 return std::find_if(current_track_list.begin(), current_track_list.end(),
186 [&track](
const std::shared_ptr<const sound::music_track>& ptr) {
return *ptr == track; }
202 if(current_track_index >= current_track_list.size()){
205 return current_track_index;
209 return current_track;
213 return previous_track;
217 previous_track = track;
222 return current_track_list.size();
227 if(i < current_track_list.size()) {
228 return current_track_list[
i];
231 if(i == current_track_list.size()) {
232 return current_track;
238 void set_track(
unsigned int i,
const std::shared_ptr<music_track>& to)
240 if(i < current_track_list.size() && find_track(*to) != current_track_list.end()) {
241 current_track_list[
i] = std::make_shared<music_track>(*to);
247 if(i >= current_track_list.size()) {
251 if(i == current_track_index) {
254 current_track->set_play_once(
true);
257 current_track_index = current_track_list.size() - 1;
258 }
else if(i < current_track_index) {
259 current_track_index--;
262 current_track_list.erase(current_track_list.begin() +
i);
269 LOG_AUDIO <<
"Considering " <<
id <<
"\n";
277 if(
id == current_track->file_path()) {
281 if(current_track_list.size() <= 3) {
293 unsigned int num_played = 0;
294 std::set<std::string> played;
295 std::vector<std::string>::reverse_iterator
i;
297 for(i = played_before.rbegin(); i != played_before.rend(); ++
i) {
300 if(num_played == 2) {
309 if(num_played == 2 && played.size() != current_track_list.size() - 1) {
310 LOG_AUDIO <<
"Played twice with only " << played.size() <<
" tracks between\n";
315 i = played_before.rbegin();
316 if(i != played_before.rend()) {
318 if(i != played_before.rend()) {
320 LOG_AUDIO <<
"Played just before previous\n";
331 assert(!current_track_list.empty());
333 if(current_track_index >= current_track_list.size()) {
334 current_track_index = 0;
337 if(current_track_list[current_track_index]->
shuffle()) {
338 unsigned int track = 0;
340 if(current_track_list.size() > 1) {
343 }
while(!
track_ok(current_track_list[track]->file_path()));
346 current_track_index = track;
349 DBG_AUDIO <<
"Next track will be " << current_track_list[current_track_index]->file_path() <<
"\n";
350 played_before.push_back(current_track_list[current_track_index]->file_path());
351 return current_track_list[current_track_index];
354 static std::string
pick_one(
const std::string& files)
362 if(ids.size() == 1) {
367 static std::map<std::string, unsigned int> prev_choices;
370 if(prev_choices.find(files) != prev_choices.end()) {
372 if(choice >= prev_choices[files]) {
376 prev_choices[files] = choice;
379 prev_choices.emplace(files, choice);
413 const char*
const drvname = SDL_GetCurrentAudioDriver();
414 return drvname ? drvname :
"<not initialized>";
419 std::vector<std::string> res;
420 int num_drivers = SDL_GetNumVideoDrivers();
422 for(
int n = 0;
n < num_drivers; ++
n) {
423 const char* drvname = SDL_GetAudioDriver(
n);
424 res.emplace_back(drvname ? drvname :
"<invalid driver>");
435 Mix_QuerySpec(&res.frequency, &res.format, &res.channels);
445 if(SDL_WasInit(SDL_INIT_AUDIO) == 0) {
446 if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
454 ERR_AUDIO <<
"Could not initialize audio: " << Mix_GetError() << std::endl;
459 Mix_AllocateChannels(n_of_channels);
460 Mix_ReserveChannels(n_reserved_channels);
468 Mix_GroupChannels(source_channel_start, source_channel_last,
SOUND_SOURCES);
469 Mix_GroupChannels(UI_sound_channel_start, UI_sound_channel_last,
SOUND_UI);
470 Mix_GroupChannels(n_reserved_channels, n_of_channels - 1,
SOUND_FX);
481 DBG_AUDIO <<
"Channel layout: " << n_of_channels <<
" channels (" << n_reserved_channels <<
" reserved)\n" 482 <<
" " << bell_channel <<
" - bell\n" 483 <<
" " << timer_channel <<
" - timer\n" 484 <<
" " << source_channel_start <<
".." << source_channel_last <<
" - sound sources\n" 485 <<
" " << UI_sound_channel_start <<
".." << UI_sound_channel_last <<
" - UI\n" 486 <<
" " << UI_sound_channel_last + 1 <<
".." << n_of_channels - 1 <<
" - sound effects\n";
496 int frequency, channels;
507 int numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
508 if(numtimesopened == 0) {
509 ERR_AUDIO <<
"Error closing audio device: " << Mix_GetError() << std::endl;
512 while(numtimesopened) {
518 if(SDL_WasInit(SDL_INIT_AUDIO) != 0) {
519 SDL_QuitSubSystem(SDL_INIT_AUDIO);
532 if(music || sound || bell || UI_sound) {
535 ERR_AUDIO <<
"Error initializing audio device: " << Mix_GetError() << std::endl;
559 Mix_FadeOutMusic(500);
560 Mix_HookMusicFinished([]() { unload_music =
true; });
570 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
585 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
596 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
605 current_track = std::make_shared<music_track>(file);
606 current_track->set_play_once(
true);
607 current_track_index = current_track_list.size();
613 current_track_list.clear();
622 music_start_time = 1;
623 want_new_music =
true;
625 fadingout_time = previous_track !=
nullptr ? previous_track->ms_after() : 0;
631 if(i >= current_track_list.size()) {
634 current_track_index =
i;
635 current_track = current_track_list[
i];
642 music_start_time = 0;
643 want_new_music =
true;
650 const std::string& filename = localized.empty() ? current_track->file_path() : localized;
652 auto itor = music_cache.find(filename);
653 if(itor == music_cache.end()) {
654 LOG_AUDIO <<
"attempting to insert track '" << filename <<
"' into cache\n";
658 const std::shared_ptr<Mix_Music> music(Mix_LoadMUSType_RW(rwops.release(), MUS_NONE,
true), &Mix_FreeMusic);
660 if(music ==
nullptr) {
661 ERR_AUDIO <<
"Could not load music file '" << filename <<
"': " << Mix_GetError() <<
"\n";
665 itor = music_cache.emplace(filename, music).first;
668 LOG_AUDIO <<
"Playing track '" << filename <<
"'\n";
669 int fading_time = current_track->ms_before();
674 const int res = Mix_FadeInMusic(itor->second.get(), 1, fading_time);
676 ERR_AUDIO <<
"Could not play music: " << Mix_GetError() <<
" " << filename <<
" " << std::endl;
679 want_new_music =
false;
689 current_track_list.clear();
690 current_track_list.emplace_back(
new music_track(
id));
692 std::shared_ptr<music_track> last_track = current_track;
693 current_track = current_track_list.back();
694 current_track_index = 0;
697 if(!last_track || !current_track || *last_track != *current_track) {
715 if(!track.
valid() && !track.
id().empty()) {
716 ERR_AUDIO <<
"cannot open track '" << track.
id() <<
"'; disabled in this playlist." << std::endl;
722 current_track = std::make_shared<music_track>(track);
723 current_track_index = current_track_list.size();
730 current_track_list.clear();
737 auto iter = find_track(track);
741 if(iter == current_track_list.end()) {
742 if(i < 0 || static_cast<std::size_t>(i) >= current_track_list.size()) {
743 current_track_list.emplace_back(
new music_track(track));
744 iter = current_track_list.end() - 1;
746 iter = current_track_list.emplace(current_track_list.begin() + 1,
new music_track(track));
747 if(current_track_index >= static_cast<std::size_t>(i)) {
748 current_track_index++;
752 ERR_AUDIO <<
"tried to add duplicate track '" << track.
file_path() <<
"'" << std::endl;
758 current_track = *iter;
759 current_track_index = iter - current_track_list.begin();
761 }
else if(!track.
append() && !allow_interrupt_current_track && current_track) {
763 current_track->set_play_once(
true);
770 if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
774 music_start_time = info.
ticks();
779 if(music_start_time && info.
ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
780 want_new_music =
true;
784 if(Mix_PlayingMusic()) {
785 Mix_FadeOutMusic(fadingout_time);
788 unload_music =
false;
797 Mix_HookMusicFinished(
nullptr);
799 unload_music =
false;
804 :
events::sdl_handler(false)
812 if(event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
814 }
else if(event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
815 if(Mix_PlayingMusic()) {
824 played_before.clear();
828 if(current_track->play_once()) {
833 for(
auto m : current_track_list) {
834 if(*current_track == *m) {
841 if(current_track_list.empty()) {
855 for(
auto m : current_track_list) {
856 m->write(snapshot, append);
864 for(
unsigned ch = 0; ch <
channel_ids.size(); ++ch) {
872 Mix_SetDistance(ch, distance);
894 sound_cache_iterator it_bgn, it_end;
895 sound_cache_iterator it;
897 sound_cache_chunk temp_chunk(file);
898 it_bgn = sound_cache.begin();
899 it_end = sound_cache.end();
900 it = std::find(it_bgn, it_end, temp_chunk);
903 if(it->group != group) {
909 sound_cache.splice(it_bgn, sound_cache, it);
912 bool cache_full = (sound_cache.size() == max_cached_chunks);
913 while(cache_full && it != it_bgn) {
916 if(std::find(
channel_chunks.begin(), ch_end, (--it)->get_data()) == ch_end) {
917 sound_cache.erase(it);
923 LOG_AUDIO <<
"Maximum sound cache size reached and all are busy, skipping.\n";
927 temp_chunk.group = group;
931 if(!filename.empty()) {
933 temp_chunk.set_data(Mix_LoadWAV_RW(rwops.release(),
true));
935 ERR_AUDIO <<
"Could not load sound file '" << file <<
"'." << std::endl;
939 if(temp_chunk.get_data() ==
nullptr) {
940 ERR_AUDIO <<
"Could not load sound file '" << filename <<
"': " << Mix_GetError() <<
"\n";
944 sound_cache.push_front(temp_chunk);
947 return sound_cache.begin()->get_data();
952 unsigned int repeats = 0,
953 unsigned int distance = 0,
956 int fadein_ticks = 0)
965 int channel = Mix_GroupAvailable(group);
967 LOG_AUDIO <<
"All channels dedicated to sound group(" << group <<
") are busy, skipping.\n";
986 Mix_SetDistance(channel, distance);
991 if(fadein_ticks > 0) {
992 res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
994 res = Mix_PlayChannel(channel, chunk, -1);
998 Mix_ExpireChannel(channel, loop_ticks);
1001 if(fadein_ticks > 0) {
1002 res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
1004 res = Mix_PlayChannel(channel, chunk, repeats);
1009 ERR_AUDIO <<
"error playing sound effect: " << Mix_GetError() << std::endl;
1043 void play_timer(
const std::string& files,
int loop_ticks,
int fadein_ticks)
1061 return Mix_VolumeMusic(-1);
1069 if(mix_ok && vol >= 0) {
1070 if(vol > MIX_MAX_VOLUME) {
1071 vol = MIX_MAX_VOLUME;
1074 Mix_VolumeMusic(vol);
1082 return Mix_Volume(source_channel_start, -1);
1089 if(mix_ok && vol >= 0) {
1090 if(vol > MIX_MAX_VOLUME) {
1091 vol = MIX_MAX_VOLUME;
1095 for(
unsigned i = 0;
i < n_of_channels; ++
i) {
1096 if(!(
i >= UI_sound_channel_start &&
i <= UI_sound_channel_last) &&
i != bell_channel
1097 &&
i != timer_channel) {
1109 if(mix_ok && vol >= 0) {
1110 if(vol > MIX_MAX_VOLUME) {
1111 vol = MIX_MAX_VOLUME;
1114 Mix_Volume(bell_channel, vol);
1115 Mix_Volume(timer_channel, vol);
1121 if(mix_ok && vol >= 0) {
1122 if(vol > MIX_MAX_VOLUME) {
1123 vol = MIX_MAX_VOLUME;
1126 for(
unsigned i = UI_sound_channel_start;
i <= UI_sound_channel_last; ++
i) {
unsigned int get_num_tracks()
const std::string & id() const
bool is_sound_playing(int id)
void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
std::string current_driver()
std::shared_ptr< music_track > get_current_track()
void set_UI_volume(int vol)
int ticks(unsigned *refresh_counter=nullptr, unsigned refresh_rate=1)
rwops_ptr make_read_RWops(const std::string &path)
static void play_new_music()
void remove_track(unsigned int i)
std::shared_ptr< music_track > get_previous_music_track()
unsigned int sample_rate()
std::string get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type or an empty string if the file isn't prese...
Audio output for sound and music.
void play_music_once(const std::string &file)
static lg::log_domain log_audio("audio")
bool operator!=(const config &a, const config &b)
void write_music_play_list(config &snapshot)
Definitions for the interface to Wesnoth Markup Language (WML).
void reposition_sound(int id, unsigned int distance)
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
static bool track_ok(const std::string &id)
static void play_sound_internal(const std::string &files, channel_group group, unsigned int repeats=0, unsigned int distance=0, int id=-1, int loop_ticks=0, int fadein_ticks=0)
void set_bell_volume(int vol)
static void channel_finished_hook(int channel)
static std::string pick_one(const std::string &files)
void set_previous_track(std::shared_ptr< music_track > track)
static Mix_Chunk * load_chunk(const std::string &file, channel_group group)
void set_sound_volume(int vol)
void set_music_volume(int vol)
static std::vector< Mix_Chunk * > channel_chunks
void process(events::pump_info &info)
bool stop_music_in_background()
bool operator==(const config &a, const config &b)
Internal representation of music tracks.
Declarations for File-IO.
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
std::string get_localized_path(const std::string &file, const std::string &suff)
Returns the localized version of the given filename, if it exists.
static void increment_chunk_usage(Mix_Chunk *mcp)
std::size_t sound_buffer_size()
Handling of system events.
const std::string & file_path() const
void play_bell(const std::string &files)
static std::vector< int > channel_ids
void play_timer(const std::string &files, int loop_ticks, int fadein_ticks)
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
std::optional< unsigned int > get_current_track_index()
void play_music_repeatedly(const std::string &id)
void play_UI_sound(const std::string &files)
Standard logging facilities (interface).
void handle_window_event(const SDL_Event &event) override
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
void commit_music_changes()
void set_track(unsigned int i, const std::shared_ptr< music_track > &to)
static driver_status query()
A config object defines a single node in a WML file, with access to child nodes.
std::vector< std::string > enumerate_drivers()
void play_track(unsigned int i)
static map_location::DIRECTION n
static std::shared_ptr< sound::music_track > choose_track()
static void decrement_chunk_usage(Mix_Chunk *mcp)
static rng & default_instance()
virtual void join_global()
std::string::const_iterator iterator
std::shared_ptr< music_track > get_track(unsigned int i)
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.