26 #include <SDL_mixer.h>
34 #define DBG_AUDIO LOG_STREAM(debug, log_audio)
35 #define LOG_AUDIO LOG_STREAM(info, log_audio)
36 #define ERR_AUDIO LOG_STREAM(err, log_audio)
39 #if (MIX_MAJOR_VERSION < 1) || (MIX_MAJOR_VERSION == 1) && ((MIX_MINOR_VERSION < 2) || (MIX_MINOR_VERSION == 2) && (MIX_PATCHLEVEL <= 11))
40 #error "Please upgrade to SDL mixer version >= 1.2.12, we don't support older versions anymore."
52 unsigned int distance=0,
int id=-1,
int loop_ticks=0,
int fadein_ticks=0);
58 int music_start_time = 0;
59 unsigned music_refresh = 0;
60 unsigned music_refresh_rate = 20;
61 bool want_new_music =
false;
62 int fadingout_time=5000;
63 bool no_fading =
false;
66 const size_t n_of_channels = 32;
69 const size_t bell_channel = 0;
70 const size_t timer_channel = 1;
73 const size_t source_channels = 8;
74 const size_t source_channel_start = timer_channel + 1;
75 const size_t source_channel_last = source_channel_start + source_channels - 1;
76 const size_t UI_sound_channels = 2;
77 const size_t UI_sound_channel_start = source_channel_last + 1;
78 const size_t UI_sound_channel_last = UI_sound_channel_start + UI_sound_channels - 1;
79 const size_t n_reserved_channels = UI_sound_channel_last + 1;
84 unsigned max_cached_chunks = 64;
86 unsigned max_cached_chunks = 256;
89 std::map< Mix_Chunk*, int > chunk_usage;
98 if(mcp ==
nullptr)
return;
100 assert(this_usage != chunk_usage.end());
101 if(--(this_usage->second) == 0) {
103 chunk_usage.erase(this_usage);
109 class sound_cache_chunk {
112 sound_cache_chunk(
const sound_cache_chunk& scc)
113 :
group(scc.
group), file(scc.file), data_(scc.data_)
126 void set_data(Mix_Chunk*
d) {
132 Mix_Chunk* get_data()
const {
136 bool operator==(sound_cache_chunk
const &scc)
const {
137 return file == scc.file;
142 sound_cache_chunk& operator=(
const sound_cache_chunk& scc) {
145 set_data(scc.get_data());
153 std::list< sound_cache_chunk > sound_cache;
155 std::map<std::string,Mix_Music*> music_cache;
157 std::vector<std::string> played_before;
167 std::vector<sound::music_track> current_track_list;
170 unsigned int current_track_index = 0;
176 LOG_AUDIO <<
"Considering " <<
id <<
"\n";
180 if (
id == current_track.file_path())
183 if (current_track_list.size() <= 3)
194 unsigned int num_played = 0;
195 std::set<std::string> played;
196 std::vector<std::string>::reverse_iterator
i;
198 for (i = played_before.rbegin(); i != played_before.rend(); ++
i) {
209 if (num_played == 2 && played.size() != current_track_list.size() - 1) {
210 LOG_AUDIO <<
"Played twice with only " << played.size()
211 <<
" tracks between\n";
216 i = played_before.rbegin();
217 if (i != played_before.rend()) {
219 if (i != played_before.rend()) {
221 LOG_AUDIO <<
"Played just before previous\n";
233 assert(!current_track_list.empty());
235 if (current_track_index >= current_track_list.size()) {
236 current_track_index = 0;
239 if (current_track_list[current_track_index].
shuffle()) {
240 unsigned int track = 0;
242 if (current_track_list.size() > 1) {
244 track = rand()%current_track_list.size();
245 }
while (!
track_ok( current_track_list[track].file_path() ));
248 current_track_index = track;
252 played_before.push_back( current_track_list[current_track_index].file_path() );
253 return current_track_list[current_track_index++];
271 static std::map<std::string,unsigned int> prev_choices;
274 if (prev_choices.find(files) != prev_choices.end()) {
275 choice = rand()%(ids.size()-1);
276 if (choice >= prev_choices[files])
278 prev_choices[files] = choice;
280 choice = rand()%ids.size();
281 prev_choices.insert(std::pair<std::string,unsigned int>(files,choice));
316 if(SDL_WasInit(SDL_INIT_AUDIO) == 0)
317 if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
323 ERR_AUDIO <<
"Could not initialize audio: " << Mix_GetError() << std::endl;
328 Mix_AllocateChannels(n_of_channels);
329 Mix_ReserveChannels(n_reserved_channels);
337 Mix_GroupChannels(source_channel_start, source_channel_last,
SOUND_SOURCES);
338 Mix_GroupChannels(UI_sound_channel_start, UI_sound_channel_last,
SOUND_UI);
339 Mix_GroupChannels(n_reserved_channels, n_of_channels - 1,
SOUND_FX);
350 DBG_AUDIO <<
"Channel layout: " << n_of_channels <<
" channels (" << n_reserved_channels <<
" reserved)\n"
351 <<
" " << bell_channel <<
" - bell\n"
352 <<
" " << timer_channel <<
" - timer\n"
353 <<
" " << source_channel_start <<
".." << source_channel_last <<
" - sound sources\n"
354 <<
" " << UI_sound_channel_start <<
".." << UI_sound_channel_last <<
" - UI\n"
355 <<
" " << UI_sound_channel_last + 1 <<
".." << n_of_channels - 1 <<
" - sound effects\n";
363 int frequency, channels;
374 int numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
375 if(numtimesopened == 0) {
376 ERR_AUDIO <<
"Error closing audio device: " << Mix_GetError() << std::endl;
378 while (numtimesopened) {
383 if(SDL_WasInit(SDL_INIT_AUDIO) != 0)
384 SDL_QuitSubSystem(SDL_INIT_AUDIO);
395 if (music || sound || bell || UI_sound) {
398 ERR_AUDIO <<
"Error initializing audio device: " << Mix_GetError() << std::endl;
416 for(i = music_cache.begin(); i != music_cache.end(); ++
i)
417 Mix_FreeMusic(i->second);
426 sound_cache_iterator
itor = sound_cache.begin();
427 while(itor != sound_cache.end())
430 itor = sound_cache.erase(itor);
445 sound_cache_iterator
itor = sound_cache.begin();
446 while(itor != sound_cache.end())
449 itor = sound_cache.erase(itor);
460 sound_cache_iterator
itor = sound_cache.begin();
461 while(itor != sound_cache.end())
464 itor = sound_cache.erase(itor);
475 current_track_list.clear();
482 current_track_list.clear();
487 music_start_time = 1;
490 fadingout_time=current_track.ms_after();
495 music_start_time = 0;
496 want_new_music =
true;
502 const std::string& filename = current_track.file_path();
504 std::map<std::string,Mix_Music*>::const_iterator
itor = music_cache.find(filename);
505 if(itor == music_cache.end()) {
506 LOG_AUDIO <<
"attempting to insert track '" << filename <<
"' into cache\n";
509 Mix_Music*
const music = Mix_LoadMUSType_RW(rwops, MUS_NONE,
true);
511 if(music ==
nullptr) {
512 ERR_AUDIO <<
"Could not load music file '" << filename <<
"': "
513 << Mix_GetError() <<
"\n";
516 itor = music_cache.insert(std::pair<std::string,Mix_Music*>(filename,music)).first;
517 last_track=current_track;
520 LOG_AUDIO <<
"Playing track '" << filename <<
"'\n";
521 int fading_time=current_track.ms_before();
527 const int res = Mix_FadeInMusic(itor->second, 1, fading_time);
530 ERR_AUDIO <<
"Could not play music: " << Mix_GetError() <<
" " << filename <<
" " << std::endl;
533 want_new_music=
false;
542 current_track_list.clear();
546 if (current_track !=
id) {
556 if (!track.
valid() && !track.
id().empty()) {
557 ERR_AUDIO <<
"cannot open track '" << track.
id() <<
"'; disabled in this playlist." << std::endl;
562 current_track = track;
569 current_track_list.clear();
576 std::vector<music_track>::const_iterator
itor = current_track_list.begin();
577 while(itor != current_track_list.end()) {
578 if(track == *itor)
break;
582 if(itor == current_track_list.end()) {
583 current_track_list.push_back(track);
585 ERR_AUDIO <<
"tried to add duplicate track '" << track.
file_path() <<
"'" << std::endl;
591 current_track = track;
593 }
else if (!track.
append()) {
594 current_track.set_play_once(
true);
600 if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
603 music_start_time = info.
ticks();
608 if(music_start_time && info.
ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
613 if(Mix_PlayingMusic()) {
614 Mix_FadeOutMusic(fadingout_time);
623 played_before.clear();
626 if (current_track.play_once())
631 if (current_track ==
m)
636 if (current_track_list.empty())
649 m.write(snapshot, append);
657 for (
unsigned ch = 0; ch <
channel_ids.size(); ++ch)
667 if (Mix_Volume(ch, -1) == 0) {
670 Mix_FadeOutChannel(ch, 100);
673 Mix_SetDistance(ch, distance);
700 sound_cache_iterator it_bgn, it_end;
701 sound_cache_iterator it;
703 sound_cache_chunk temp_chunk(file);
704 it_bgn = sound_cache.begin(), it_end = sound_cache.end();
705 it =
std::find(it_bgn, it_end, temp_chunk);
708 if(it->group != group) {
714 sound_cache.splice(it_bgn, sound_cache, it);
717 bool cache_full = (sound_cache.size() == max_cached_chunks);
718 while( cache_full && it != it_bgn ) {
722 sound_cache.erase(it);
727 LOG_AUDIO <<
"Maximum sound cache size reached and all are busy, skipping.\n";
730 temp_chunk.group =
group;
733 if (!filename.empty()) {
735 temp_chunk.set_data(Mix_LoadWAV_RW(rwops,
true));
737 ERR_AUDIO <<
"Could not load sound file '" << file <<
"'." << std::endl;
741 if (temp_chunk.get_data() ==
nullptr) {
742 ERR_AUDIO <<
"Could not load sound file '" << filename <<
"': "
743 << Mix_GetError() <<
"\n";
747 sound_cache.push_front(temp_chunk);
750 return sound_cache.begin()->get_data();
754 unsigned int distance,
int id,
int loop_ticks,
int fadein_ticks)
763 int channel = Mix_GroupAvailable(group);
765 LOG_AUDIO <<
"All channels dedicated to sound group(" << group <<
") are busy, skipping.\n";
784 Mix_SetDistance(channel, distance);
789 if(fadein_ticks > 0) {
790 res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
792 res = Mix_PlayChannel(channel, chunk, -1);
796 Mix_ExpireChannel(channel, loop_ticks);
799 if(fadein_ticks > 0) {
800 res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
802 res = Mix_PlayChannel(channel, chunk, repeats);
807 ERR_AUDIO <<
"error playing sound effect: " << Mix_GetError() << std::endl;
851 if(mix_ok && vol >= 0) {
852 if(vol > MIX_MAX_VOLUME)
853 vol = MIX_MAX_VOLUME;
855 Mix_VolumeMusic(vol);
861 if(mix_ok && vol >= 0) {
862 if(vol > MIX_MAX_VOLUME)
863 vol = MIX_MAX_VOLUME;
866 for (
unsigned i = 0;
i < n_of_channels; ++
i){
867 if(!(
i >= UI_sound_channel_start &&
i <= UI_sound_channel_last)
868 &&
i != bell_channel &&
i != timer_channel)
881 if(mix_ok && vol >= 0) {
882 if(vol > MIX_MAX_VOLUME)
883 vol = MIX_MAX_VOLUME;
885 Mix_Volume(bell_channel, vol);
886 Mix_Volume(timer_channel, vol);
892 if(mix_ok && vol >= 0) {
893 if(vol > MIX_MAX_VOLUME)
894 vol = MIX_MAX_VOLUME;
896 for (
unsigned i = UI_sound_channel_start;
i <= UI_sound_channel_last; ++
i) {
bool is_sound_playing(int id)
void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
void set_UI_volume(int vol)
int ticks(unsigned *refresh_counter=nullptr, unsigned refresh_rate=1)
static void play_new_music()
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)
void set_bell_volume(int vol)
static void channel_finished_hook(int channel)
std::vector< ttip > shuffle(const std::vector< ttip > &tips)
Shuffles the tips.
static std::string pick_one(const std::string &files)
void play_music_config(const config &music_node)
static Mix_Chunk * load_chunk(const std::string &file, channel_group group)
Templates and utility-routines for strings and numbers.
void set_sound_volume(int vol)
const std::string & file_path() const
void set_music_volume(int vol)
size_t sound_buffer_size()
std::map< std::string, tfilter >::iterator itor
static std::vector< Mix_Chunk * > channel_chunks
GLint GLint GLsizei GLsizei GLsizei GLint GLenum format
void process(events::pump_info &info)
bool operator==(const config &a, const config &b)
Internal representation of music tracks.
Declarations for File-IO.
static const sound::music_track & choose_track()
static void increment_chunk_usage(Mix_Chunk *mcp)
void play_bell(const std::string &files)
static std::vector< int > channel_ids
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 play_timer(const std::string &files, int loop_ticks, int fadein_ticks)
bool find(E event, F functor)
Tests whether an event handler is available.
void play_music_repeatedly(const std::string &id)
void play_UI_sound(const std::string &files)
Standard logging facilities (interface).
void commit_music_changes()
SDL_RWops * load_RWops(const std::string &path)
A config object defines a single node in a WML file, with access to child nodes.
static void decrement_chunk_usage(Mix_Chunk *mcp)
std::vector< std::string > square_parenthetical_split(std::string const &val, const char separator, std::string const &left, std::string const &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
GLsizei const GLcharARB ** string
const std::string & id() const