26 #include <SDL2/SDL_mixer.h>
33 #define DBG_AUDIO LOG_STREAM(debug, log_audio)
34 #define LOG_AUDIO LOG_STREAM(info, log_audio)
35 #define ERR_AUDIO LOG_STREAM(err, log_audio)
47 using namespace std::chrono_literals;
52 utils::optional<std::chrono::steady_clock::time_point> music_start_time;
54 bool want_new_music =
false;
55 auto fade_out_time = 5000ms;
56 bool no_fading =
false;
57 bool unload_music =
false;
60 const std::size_t n_of_channels = 32;
63 const std::size_t bell_channel = 0;
64 const std::size_t timer_channel = 1;
67 const std::size_t source_channels = 8;
68 const std::size_t source_channel_start = timer_channel + 1;
69 const std::size_t source_channel_last = source_channel_start + source_channels - 1;
70 const std::size_t UI_sound_channels = 2;
71 const std::size_t UI_sound_channel_start = source_channel_last + 1;
72 const std::size_t UI_sound_channel_last = UI_sound_channel_start + UI_sound_channels - 1;
73 const std::size_t n_reserved_channels = UI_sound_channel_last + 1;
77 unsigned max_cached_chunks = 256;
79 std::map<Mix_Chunk*, int> chunk_usage;
94 assert(this_usage != chunk_usage.end());
95 if(--(this_usage->second) == 0) {
97 chunk_usage.erase(this_usage);
103 class sound_cache_chunk
106 sound_cache_chunk(
const std::string&
f)
112 sound_cache_chunk(
const sound_cache_chunk& scc)
128 void set_data(Mix_Chunk*
d)
135 Mix_Chunk* get_data()
const
140 bool operator==(
const sound_cache_chunk& scc)
const
142 return file == scc.file;
145 bool operator!=(
const sound_cache_chunk& scc)
const
150 sound_cache_chunk& operator=(
const sound_cache_chunk& scc)
154 set_data(scc.get_data());
162 std::list<sound_cache_chunk> sound_cache;
164 std::map<std::string, std::shared_ptr<Mix_Music>> music_cache;
166 std::vector<std::string> played_before;
176 std::vector<std::shared_ptr<sound::music_track>> current_track_list;
177 std::shared_ptr<sound::music_track> current_track;
178 unsigned int current_track_index = 0;
179 std::shared_ptr<sound::music_track> previous_track;
181 std::vector<std::shared_ptr<sound::music_track>>::const_iterator find_track(
const sound::music_track& track)
183 return std::find_if(current_track_list.begin(), current_track_list.end(),
184 [&track](
const std::shared_ptr<const sound::music_track>& ptr) { return *ptr == track; }
200 if(current_track_index >= current_track_list.size()){
203 return current_track_index;
207 return current_track;
211 return previous_track;
215 previous_track = std::move(track);
220 return current_track_list.size();
225 if(
i < current_track_list.size()) {
226 return current_track_list[
i];
229 if(
i == current_track_list.size()) {
230 return current_track;
236 void set_track(
unsigned int i,
const std::shared_ptr<music_track>& to)
238 if(
i < current_track_list.size() && find_track(*to) != current_track_list.end()) {
239 current_track_list[
i] = std::make_shared<music_track>(*to);
245 if(
i >= current_track_list.size()) {
249 if(
i == current_track_index) {
252 current_track->set_play_once(
true);
255 current_track_index = current_track_list.size() - 1;
256 }
else if(
i < current_track_index) {
257 current_track_index--;
260 current_track_list.erase(current_track_list.begin() +
i);
275 if(
id == current_track->file_path()) {
279 if(current_track_list.size() <= 3) {
291 unsigned int num_played = 0;
292 std::set<std::string> played;
293 std::vector<std::string>::reverse_iterator
i;
295 for(
i = played_before.rbegin();
i != played_before.rend(); ++
i) {
298 if(num_played == 2) {
307 if(num_played == 2 && played.size() != current_track_list.size() - 1) {
308 LOG_AUDIO <<
"Played twice with only " << played.size() <<
" tracks between";
313 i = played_before.rbegin();
314 if(
i != played_before.rend()) {
316 if(
i != played_before.rend()) {
318 LOG_AUDIO <<
"Played just before previous";
329 assert(!current_track_list.empty());
331 if(current_track_index >= current_track_list.size()) {
332 current_track_index = 0;
335 if(current_track_list[current_track_index]->
shuffle()) {
336 unsigned int track = 0;
338 if(current_track_list.size() > 1) {
341 }
while(!
track_ok(current_track_list[track]->file_path()));
344 current_track_index = track;
347 DBG_AUDIO <<
"Next track will be " << current_track_list[current_track_index]->file_path();
348 played_before.push_back(current_track_list[current_track_index]->file_path());
349 return current_track_list[current_track_index];
352 static std::string
pick_one(
const std::string& files)
360 if(ids.size() == 1) {
365 static std::map<std::string, unsigned int> prev_choices;
368 if(prev_choices.find(files) != prev_choices.end()) {
370 if(choice >= prev_choices[files]) {
374 prev_choices[files] = choice;
377 prev_choices.emplace(files, choice);
411 const char*
const drvname = SDL_GetCurrentAudioDriver();
412 return drvname ? drvname :
"<not initialized>";
417 std::vector<std::string> res;
418 int num_drivers = SDL_GetNumVideoDrivers();
420 for(
int n = 0;
n < num_drivers; ++
n) {
421 const char* drvname = SDL_GetAudioDriver(
n);
422 res.emplace_back(drvname ? drvname :
"<invalid driver>");
433 Mix_QuerySpec(&res.frequency, &res.format, &res.channels);
443 if(SDL_WasInit(SDL_INIT_AUDIO) == 0) {
444 if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
450 if(Mix_OpenAudio(
prefs::get().sample_rate(), MIX_DEFAULT_FORMAT, 2,
prefs::get().sound_buffer_size()) == -1) {
452 ERR_AUDIO <<
"Could not initialize audio: " << Mix_GetError();
457 Mix_AllocateChannels(n_of_channels);
458 Mix_ReserveChannels(n_reserved_channels);
466 Mix_GroupChannels(source_channel_start, source_channel_last,
SOUND_SOURCES);
467 Mix_GroupChannels(UI_sound_channel_start, UI_sound_channel_last,
SOUND_UI);
468 Mix_GroupChannels(n_reserved_channels, n_of_channels - 1,
SOUND_FX);
479 DBG_AUDIO <<
"Channel layout: " << n_of_channels <<
" channels (" << n_reserved_channels <<
" reserved)\n"
480 <<
" " << bell_channel <<
" - bell\n"
481 <<
" " << timer_channel <<
" - timer\n"
482 <<
" " << source_channel_start <<
".." << source_channel_last <<
" - sound sources\n"
483 <<
" " << UI_sound_channel_start <<
".." << UI_sound_channel_last <<
" - UI\n"
484 <<
" " << UI_sound_channel_last + 1 <<
".." << n_of_channels - 1 <<
" - sound effects";
494 int frequency, channels;
505 int numtimesopened = Mix_QuerySpec(&frequency, &
format, &channels);
506 if(numtimesopened == 0) {
507 ERR_AUDIO <<
"Error closing audio device: " << Mix_GetError();
510 while(numtimesopened) {
516 if(SDL_WasInit(SDL_INIT_AUDIO) != 0) {
517 SDL_QuitSubSystem(SDL_INIT_AUDIO);
530 if(music ||
sound || bell || UI_sound) {
533 ERR_AUDIO <<
"Error initializing audio device: " << Mix_GetError();
557 Mix_FadeOutMusic(500);
558 Mix_HookMusicFinished([]() { unload_music =
true; });
568 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
583 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
594 sound_cache.remove_if([](
const sound_cache_chunk&
c) {
603 current_track = std::make_shared<music_track>(file);
604 current_track->set_play_once(
true);
605 current_track_index = current_track_list.size();
611 current_track_list.clear();
620 music_start_time = std::chrono::steady_clock::now();
621 want_new_music =
true;
623 fade_out_time = previous_track !=
nullptr ? previous_track->ms_after() : 0ms;
629 if(
i >= current_track_list.size()) {
632 current_track_index =
i;
633 current_track = current_track_list[
i];
640 music_start_time.reset();
641 want_new_music =
true;
643 if(!
prefs::get().
music_on() || !mix_ok || !current_track || !current_track->valid()) {
647 std::string
filename = current_track->file_path();
652 auto itor = music_cache.find(
filename);
653 if(itor == music_cache.end()) {
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();
665 itor = music_cache.emplace(
filename, music).first;
669 auto fading_time = current_track->ms_before();
682 const int res = Mix_FadeInMusic(itor->second.get(), 1, fading_time.count());
684 ERR_AUDIO <<
"Could not play music: " << Mix_GetError() <<
" " <<
filename <<
" ";
687 want_new_music =
false;
697 current_track_list.clear();
698 current_track_list.emplace_back(
new music_track(
id));
700 std::shared_ptr<music_track> last_track = current_track;
701 current_track = current_track_list.back();
702 current_track_index = 0;
705 if(!last_track || !current_track || *last_track != *current_track) {
723 if(!track.
valid() && !track.
id().empty()) {
724 ERR_AUDIO <<
"cannot open track '" << track.
id() <<
"'; disabled in this playlist.";
730 current_track = std::make_shared<music_track>(track);
731 current_track_index = current_track_list.size();
738 current_track_list.clear();
745 auto iter = find_track(track);
749 if(iter == current_track_list.end()) {
750 if(
i < 0 ||
static_cast<std::size_t
>(
i) >= current_track_list.size()) {
751 current_track_list.emplace_back(
new music_track(track));
752 iter = current_track_list.end() - 1;
754 iter = current_track_list.emplace(current_track_list.begin() + 1,
new music_track(track));
755 if(current_track_index >=
static_cast<std::size_t
>(
i)) {
756 current_track_index++;
766 current_track = *iter;
767 current_track_index = iter - current_track_list.begin();
769 }
else if(!track.
append() && !allow_interrupt_current_track && current_track) {
771 current_track->set_play_once(
true);
777 if(Mix_FadingMusic() != MIX_NO_FADING) {
784 auto now = std::chrono::steady_clock::now();
786 if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
790 music_start_time = now;
795 if(music_start_time && music_refresh_rate.poll()) {
796 want_new_music = now >= *music_start_time - fade_out_time;
800 if(Mix_PlayingMusic()) {
801 Mix_FadeOutMusic(fade_out_time.count());
805 unload_music =
false;
814 Mix_HookMusicFinished(
nullptr);
816 unload_music =
false;
820 music_muter::music_muter()
821 :
events::sdl_handler(false)
829 if(event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
831 }
else if(event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
832 if(Mix_PlayingMusic()) {
841 played_before.clear();
845 if(current_track->play_once()) {
850 for(
auto m : current_track_list) {
851 if(*current_track == *m) {
858 if(current_track_list.empty()) {
872 for(
auto m : current_track_list) {
873 m->write(snapshot, append);
881 for(
unsigned ch = 0; ch <
channel_ids.size(); ++ch) {
889 Mix_SetDistance(ch, distance);
907 struct chunk_load_exception
911 Mix_Chunk* load_chunk(
const std::string& file,
channel_group group)
913 sound_cache_iterator it_bgn, it_end;
914 sound_cache_iterator it;
916 sound_cache_chunk temp_chunk(file);
917 it_bgn = sound_cache.begin();
918 it_end = sound_cache.end();
919 it =
std::find(it_bgn, it_end, temp_chunk);
922 if(it->group != group) {
928 sound_cache.splice(it_bgn, sound_cache, it);
931 bool cache_full = (sound_cache.size() == max_cached_chunks);
932 while(cache_full && it != it_bgn) {
936 sound_cache.erase(it);
942 LOG_AUDIO <<
"Maximum sound cache size reached and all are busy, skipping.";
943 throw chunk_load_exception();
946 temp_chunk.group = group;
952 temp_chunk.set_data(Mix_LoadWAV_RW(rwops.release(),
true));
954 ERR_AUDIO <<
"Could not load sound file '" << file <<
"'.";
955 throw chunk_load_exception();
958 if(temp_chunk.get_data() ==
nullptr) {
959 ERR_AUDIO <<
"Could not load sound file '" <<
filename.value() <<
"': " << Mix_GetError();
960 throw chunk_load_exception();
963 sound_cache.push_front(temp_chunk);
966 return sound_cache.begin()->get_data();
969 using namespace std::chrono_literals;
971 void play_sound_internal(
const std::string& files,
973 unsigned int repeats = 0,
974 unsigned int distance = 0,
976 const std::chrono::milliseconds& loop_ticks = 0ms,
977 const std::chrono::milliseconds& fadein_ticks = 0ms)
986 int channel = Mix_GroupAvailable(group);
988 LOG_AUDIO <<
"All channels dedicated to sound group(" << group <<
") are busy, skipping.";
996 chunk = load_chunk(file, group);
998 }
catch(
const chunk_load_exception&) {
1007 Mix_SetDistance(
channel, distance);
1011 if(loop_ticks > 0ms) {
1012 if(fadein_ticks > 0ms) {
1013 res = Mix_FadeInChannelTimed(
channel, chunk, -1, fadein_ticks.count(), loop_ticks.count());
1015 res = Mix_PlayChannel(
channel, chunk, -1);
1019 Mix_ExpireChannel(
channel, loop_ticks.count());
1022 if(fadein_ticks > 0ms) {
1023 res = Mix_FadeInChannel(
channel, chunk, repeats, fadein_ticks.count());
1025 res = Mix_PlayChannel(
channel, chunk, repeats);
1030 ERR_AUDIO <<
"error playing sound effect: " << Mix_GetError();
1046 play_sound_internal(files, group, repeats);
1053 play_sound_internal(files,
SOUND_SOURCES, repeats, distance,
id);
1066 void play_timer(
const std::string& files,
const std::chrono::milliseconds& loop_ticks,
const std::chrono::milliseconds& fadein_ticks)
1069 play_sound_internal(files,
SOUND_TIMER, 0, 0, -1, loop_ticks, fadein_ticks);
1077 play_sound_internal(files,
SOUND_UI);
1084 return Mix_VolumeMusic(-1);
1092 if(mix_ok && vol >= 0) {
1093 if(vol > MIX_MAX_VOLUME) {
1094 vol = MIX_MAX_VOLUME;
1097 Mix_VolumeMusic(vol);
1105 return Mix_Volume(source_channel_start, -1);
1112 if(mix_ok && vol >= 0) {
1113 if(vol > MIX_MAX_VOLUME) {
1114 vol = MIX_MAX_VOLUME;
1118 for(
unsigned i = 0;
i < n_of_channels; ++
i) {
1119 if(!(
i >= UI_sound_channel_start &&
i <= UI_sound_channel_last) &&
i != bell_channel
1120 &&
i != timer_channel) {
1132 if(mix_ok && vol >= 0) {
1133 if(vol > MIX_MAX_VOLUME) {
1134 vol = MIX_MAX_VOLUME;
1137 Mix_Volume(bell_channel, vol);
1138 Mix_Volume(timer_channel, vol);
1144 if(mix_ok && vol >= 0) {
1145 if(vol > MIX_MAX_VOLUME) {
1146 vol = MIX_MAX_VOLUME;
1149 for(
unsigned i = UI_sound_channel_start;
i <= UI_sound_channel_last; ++
i) {
A config object defines a single node in a WML file, with access to child nodes.
virtual void join_global()
std::size_t sound_buffer_size()
static rng & default_instance()
int get_random_int(int min, int max)
void handle_window_event(const SDL_Event &event) override
Internal representation of music tracks.
const std::string & file_path() const
const std::string & id() const
bool operator==(const config &a, const config &b)
bool operator!=(const config &a, const config &b)
Declarations for File-IO.
std::string id
Text to match against addon_info.tags()
Standard logging facilities (interface).
static lua_music_track * get_track(lua_State *L, int i)
Handling of system events.
std::unique_ptr< SDL_RWops, sdl_rwops_deleter > rwops_ptr
rwops_ptr make_read_RWops(const std::string &path)
utils::optional< 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, if it exists.
utils::optional< 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 int music_volume()
static bool ui_sound_on()
static int sound_volume()
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Audio output for sound and music.
void write_music_play_list(config &snapshot)
void set_bell_volume(int vol)
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
void remove_track(unsigned int i)
void play_music_repeatedly(const std::string &id)
unsigned int get_num_tracks()
void play_music_once(const std::string &file)
void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
utils::optional< unsigned int > get_current_track_index()
void set_track(unsigned int i, const std::shared_ptr< music_track > &to)
std::vector< std::string > enumerate_drivers()
bool is_sound_playing(int id)
static std::vector< Mix_Chunk * > channel_chunks
static std::vector< int > channel_ids
void reposition_sound(int id, unsigned int distance)
void play_timer(const std::string &files, const std::chrono::milliseconds &loop_ticks, const std::chrono::milliseconds &fadein_ticks)
void commit_music_changes()
static void channel_finished_hook(int channel)
static void play_new_music()
void play_track(unsigned int i)
void play_UI_sound(const std::string &files)
void set_previous_track(std::shared_ptr< music_track > track)
std::shared_ptr< music_track > get_previous_music_track()
void set_music_volume(int vol)
std::shared_ptr< music_track > get_current_track()
std::string current_driver()
void set_UI_volume(int vol)
void set_sound_volume(int vol)
void play_bell(const std::string &files)
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.
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
std::string::const_iterator iterator
static void decrement_chunk_usage(Mix_Chunk *mcp)
static std::shared_ptr< sound::music_track > choose_track()
static void increment_chunk_usage(Mix_Chunk *mcp)
static lg::log_domain log_audio("audio")
static bool track_ok(const std::string &id)
static std::string pick_one(const std::string &files)
std::string filename
Filename.
static map_location::direction n