The Battle for Wesnoth  1.15.3+dev
sound.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "sound.hpp"
16 #include "config.hpp"
17 #include "filesystem.hpp"
18 #include "log.hpp"
19 #include "preferences/game.hpp"
20 #include "random.hpp"
22 #include "sound_music_track.hpp"
23 
24 #include <SDL2/SDL.h> // Travis doesn't like this, although it works on my machine -> '#include <SDL2/SDL_sound.h>
25 #include <SDL2/SDL_mixer.h>
26 
27 #include <list>
28 #include <sstream>
29 #include <string>
30 
31 static lg::log_domain log_audio("audio");
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)
35 
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."
38 #endif
39 
40 namespace sound
41 {
42 // Channel-chunk mapping lets us know, if we can safely free a given chunk
43 static std::vector<Mix_Chunk*> channel_chunks;
44 
45 // Channel-id mapping for use with sound sources (to check if given source
46 // is playing on a channel for fading/panning)
47 static std::vector<int> channel_ids;
48 }
49 
50 namespace
51 {
52 bool mix_ok = false;
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;
60 
61 // number of allocated channels,
62 const std::size_t n_of_channels = 32;
63 
64 // we need 2 channels, because we it for timer as well
65 const std::size_t bell_channel = 0;
66 const std::size_t timer_channel = 1;
67 
68 // number of channels reserved for sound sources
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; // sources, bell, timer and UI
76 
77 // Max number of sound chunks that we want to cache
78 // Keep this above number of available channels to avoid busy-looping
79 unsigned max_cached_chunks = 256;
80 
81 std::map<Mix_Chunk*, int> chunk_usage;
82 } // end anon namespace
83 
84 static void increment_chunk_usage(Mix_Chunk* mcp)
85 {
86  ++(chunk_usage[mcp]);
87 }
88 
89 static void decrement_chunk_usage(Mix_Chunk* mcp)
90 {
91  if(mcp == nullptr) {
92  return;
93  }
94 
95  std::map<Mix_Chunk*, int>::iterator this_usage = chunk_usage.find(mcp);
96  assert(this_usage != chunk_usage.end());
97  if(--(this_usage->second) == 0) {
98  Mix_FreeChunk(mcp);
99  chunk_usage.erase(this_usage);
100  }
101 }
102 
103 namespace
104 {
105 class sound_cache_chunk
106 {
107 public:
108  sound_cache_chunk(const std::string& f)
109  : group(sound::NULL_CHANNEL)
110  , file(f)
111  , data_(nullptr)
112  {
113  }
114  sound_cache_chunk(const sound_cache_chunk& scc)
115  : group(scc.group)
116  , file(scc.file)
117  , data_(scc.data_)
118  {
119  increment_chunk_usage(data_);
120  }
121 
122  ~sound_cache_chunk()
123  {
124  decrement_chunk_usage(data_);
125  }
126 
127  sound::channel_group group;
128  std::string file;
129 
130  void set_data(Mix_Chunk* d)
131  {
133  decrement_chunk_usage(data_);
134  data_ = d;
135  }
136 
137  Mix_Chunk* get_data() const
138  {
139  return data_;
140  }
141 
142  bool operator==(const sound_cache_chunk& scc) const
143  {
144  return file == scc.file;
145  }
146 
147  bool operator!=(const sound_cache_chunk& scc) const
148  {
149  return !operator==(scc);
150  }
151 
152  sound_cache_chunk& operator=(const sound_cache_chunk& scc)
153  {
154  file = scc.file;
155  group = scc.group;
156  set_data(scc.get_data());
157  return *this;
158  }
159 
160 private:
161  Mix_Chunk* data_;
162 };
163 
164 std::list<sound_cache_chunk> sound_cache;
165 typedef std::list<sound_cache_chunk>::iterator sound_cache_iterator;
166 std::map<std::string, std::shared_ptr<Mix_Music>> music_cache;
167 
168 std::vector<std::string> played_before;
169 
170 //
171 // FIXME: the first music_track may be initialized before main()
172 // is reached. Using the logging facilities may lead to a SIGSEGV
173 // because it's not guaranteed that their objects are already alive.
174 //
175 // Use the music_track default constructor to avoid trying to
176 // invoke a log object while resolving paths.
177 //
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;
182 
183 std::vector<std::shared_ptr<sound::music_track>>::const_iterator find_track(const sound::music_track& track)
184 {
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; }
187  );
188 }
189 
190 } // end anon namespace
191 
192 namespace sound
193 {
195 {
196  sound_cache.clear();
197  music_cache.clear();
198 }
199 
200 boost::optional<unsigned int> get_current_track_index()
201 {
202  if(current_track_index >= current_track_list.size()){
203  return {};
204  }
205  return current_track_index;
206 }
207 std::shared_ptr<music_track> get_current_track()
208 {
209  return current_track;
210 }
211 std::shared_ptr<music_track> get_previous_music_track()
212 {
213  return previous_track;
214 }
215 void set_previous_track(std::shared_ptr<music_track> track)
216 {
217  previous_track = track;
218 }
219 
220 unsigned int get_num_tracks()
221 {
222  return current_track_list.size();
223 }
224 
225 std::shared_ptr<music_track> get_track(unsigned int i)
226 {
227  if(i < current_track_list.size()) {
228  return current_track_list[i];
229  }
230 
231  if(i == current_track_list.size()) {
232  return current_track;
233  }
234 
235  return nullptr;
236 }
237 
238 void set_track(unsigned int i, const std::shared_ptr<music_track>& to)
239 {
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);
242  }
243 }
244 
245 void remove_track(unsigned int i)
246 {
247  if(i >= current_track_list.size()) {
248  return;
249  }
250 
251  if(i == current_track_index) {
252  // Let the track finish playing
253  if(current_track){
254  current_track->set_play_once(true);
255  }
256  // Set current index to the new size of the list
257  current_track_index = current_track_list.size() - 1;
258  } else if(i < current_track_index) {
259  current_track_index--;
260  }
261 
262  current_track_list.erase(current_track_list.begin() + i);
263 }
264 
265 } // end namespace sound
266 
267 static bool track_ok(const std::string& id)
268 {
269  LOG_AUDIO << "Considering " << id << "\n";
270 
271  if(!current_track) {
272  return true;
273  }
274 
275  // If they committed changes to list, we forget previous plays, but
276  // still *never* repeat same track twice if we have an option.
277  if(id == current_track->file_path()) {
278  return false;
279  }
280 
281  if(current_track_list.size() <= 3) {
282  return true;
283  }
284 
285  // Timothy Pinkham says:
286  // 1) can't be repeated without 2 other pieces have already played
287  // since A was played.
288  // 2) cannot play more than 2 times without every other piece
289  // having played at least 1 time.
290 
291  // Dammit, if our musicians keep coming up with algorithms, I'll
292  // be out of a job!
293  unsigned int num_played = 0;
294  std::set<std::string> played;
295  std::vector<std::string>::reverse_iterator i;
296 
297  for(i = played_before.rbegin(); i != played_before.rend(); ++i) {
298  if(*i == id) {
299  ++num_played;
300  if(num_played == 2) {
301  break;
302  }
303  } else {
304  played.insert(*i);
305  }
306  }
307 
308  // If we've played this twice, must have played every other track.
309  if(num_played == 2 && played.size() != current_track_list.size() - 1) {
310  LOG_AUDIO << "Played twice with only " << played.size() << " tracks between\n";
311  return false;
312  }
313 
314  // Check previous previous track not same.
315  i = played_before.rbegin();
316  if(i != played_before.rend()) {
317  ++i;
318  if(i != played_before.rend()) {
319  if(*i == id) {
320  LOG_AUDIO << "Played just before previous\n";
321  return false;
322  }
323  }
324  }
325 
326  return true;
327 }
328 
329 static std::shared_ptr<sound::music_track> choose_track()
330 {
331  assert(!current_track_list.empty());
332 
333  if(current_track_index >= current_track_list.size()) {
334  current_track_index = 0;
335  }
336 
337  if(current_track_list[current_track_index]->shuffle()) {
338  unsigned int track = 0;
339 
340  if(current_track_list.size() > 1) {
341  do {
342  track = randomness::rng::default_instance().get_random_int(0, current_track_list.size()-1);
343  } while(!track_ok(current_track_list[track]->file_path()));
344  }
345 
346  current_track_index = track;
347  }
348 
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];
352 }
353 
354 static std::string pick_one(const std::string& files)
355 {
356  std::vector<std::string> ids = utils::square_parenthetical_split(files, ',', "[", "]");
357 
358  if(ids.empty()) {
359  return "";
360  }
361 
362  if(ids.size() == 1) {
363  return ids[0];
364  }
365 
366  // We avoid returning same choice twice if we can avoid it.
367  static std::map<std::string, unsigned int> prev_choices;
368  unsigned int choice;
369 
370  if(prev_choices.find(files) != prev_choices.end()) {
371  choice = randomness::rng::default_instance().get_random_int(0, ids.size()-1 - 1);
372  if(choice >= prev_choices[files]) {
373  ++choice;
374  }
375 
376  prev_choices[files] = choice;
377  } else {
378  choice = randomness::rng::default_instance().get_random_int(0, ids.size()-1);
379  prev_choices.emplace(files, choice);
380  }
381 
382  return ids[choice];
383 }
384 
385 namespace
386 {
387 struct audio_lock
388 {
389  audio_lock()
390  {
391  SDL_LockAudio();
392  }
393 
394  ~audio_lock()
395  {
396  SDL_UnlockAudio();
397  }
398 };
399 
400 } // end of anonymous namespace
401 
402 namespace sound
403 {
404 // Removes channel-chunk and channel-id mapping
406 {
407  channel_chunks[channel] = nullptr;
408  channel_ids[channel] = -1;
409 }
410 
411 std::string current_driver()
412 {
413  const char* const drvname = SDL_GetCurrentAudioDriver();
414  return drvname ? drvname : "<not initialized>";
415 }
416 
417 std::vector<std::string> enumerate_drivers()
418 {
419  std::vector<std::string> res;
420  int num_drivers = SDL_GetNumVideoDrivers();
421 
422  for(int n = 0; n < num_drivers; ++n) {
423  const char* drvname = SDL_GetAudioDriver(n);
424  res.emplace_back(drvname ? drvname : "<invalid driver>");
425  }
426 
427  return res;
428 }
429 
431 {
432  driver_status res{mix_ok, 0, 0, 0, 0};
433 
434  if(mix_ok) {
435  Mix_QuerySpec(&res.frequency, &res.format, &res.channels);
436  res.chunk_size = preferences::sound_buffer_size();
437  }
438 
439  return res;
440 }
441 
443 {
444  LOG_AUDIO << "Initializing audio...\n";
445  if(SDL_WasInit(SDL_INIT_AUDIO) == 0) {
446  if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
447  return false;
448  }
449  }
450 
451  if(!mix_ok) {
452  if(Mix_OpenAudio(preferences::sample_rate(), MIX_DEFAULT_FORMAT, 2, preferences::sound_buffer_size()) == -1) {
453  mix_ok = false;
454  ERR_AUDIO << "Could not initialize audio: " << Mix_GetError() << std::endl;
455  return false;
456  }
457 
458  mix_ok = true;
459  Mix_AllocateChannels(n_of_channels);
460  Mix_ReserveChannels(n_reserved_channels);
461 
462  channel_chunks.clear();
463  channel_chunks.resize(n_of_channels, nullptr);
464  channel_ids.resize(n_of_channels, -1);
465 
466  Mix_GroupChannel(bell_channel, SOUND_BELL);
467  Mix_GroupChannel(timer_channel, SOUND_TIMER);
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);
471 
476 
477  Mix_ChannelFinished(channel_finished_hook);
478 
479  LOG_AUDIO << "Audio initialized.\n";
480 
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";
487 
488  play_music();
489  }
490 
491  return true;
492 }
493 
495 {
496  int frequency, channels;
497  uint16_t format;
498 
499  if(mix_ok) {
500  stop_bell();
501  stop_UI_sound();
502  stop_sound();
503  sound_cache.clear();
504  stop_music();
505  mix_ok = false;
506 
507  int numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
508  if(numtimesopened == 0) {
509  ERR_AUDIO << "Error closing audio device: " << Mix_GetError() << std::endl;
510  }
511 
512  while(numtimesopened) {
513  Mix_CloseAudio();
514  --numtimesopened;
515  }
516  }
517 
518  if(SDL_WasInit(SDL_INIT_AUDIO) != 0) {
519  SDL_QuitSubSystem(SDL_INIT_AUDIO);
520  }
521 
522  LOG_AUDIO << "Audio device released.\n";
523 }
524 
526 {
527  bool music = preferences::music_on();
528  bool sound = preferences::sound_on();
529  bool UI_sound = preferences::UI_sound_on();
530  bool bell = preferences::turn_bell();
531 
532  if(music || sound || bell || UI_sound) {
534  if(!sound::init_sound()) {
535  ERR_AUDIO << "Error initializing audio device: " << Mix_GetError() << std::endl;
536  }
537 
538  if(!music) {
540  }
541 
542  if(!sound) {
544  }
545 
546  if(!UI_sound) {
548  }
549 
550  if(!bell) {
552  }
553  }
554 }
555 
557 {
558  if(mix_ok) {
559  Mix_FadeOutMusic(500);
560  Mix_HookMusicFinished([]() { unload_music = true; });
561  }
562 }
563 
565 {
566  if(mix_ok) {
567  Mix_HaltGroup(SOUND_SOURCES);
568  Mix_HaltGroup(SOUND_FX);
569 
570  sound_cache.remove_if([](const sound_cache_chunk& c) {
571  return c.group == SOUND_SOURCES || c.group == SOUND_FX;
572  });
573  }
574 }
575 
576 /*
577  * For the purpose of channel manipulation, we treat turn timer the same as bell
578  */
579 void stop_bell()
580 {
581  if(mix_ok) {
582  Mix_HaltGroup(SOUND_BELL);
583  Mix_HaltGroup(SOUND_TIMER);
584 
585  sound_cache.remove_if([](const sound_cache_chunk& c) {
586  return c.group == SOUND_BELL || c.group == SOUND_TIMER;
587  });
588  }
589 }
590 
592 {
593  if(mix_ok) {
594  Mix_HaltGroup(SOUND_UI);
595 
596  sound_cache.remove_if([](const sound_cache_chunk& c) {
597  return c.group == SOUND_UI;
598  });
599  }
600 }
601 
602 void play_music_once(const std::string& file)
603 {
604  set_previous_track(current_track);
605  current_track = std::make_shared<music_track>(file);
606  current_track->set_play_once(true);
607  current_track_index = current_track_list.size();
608  play_music();
609 }
610 
612 {
613  current_track_list.clear();
614 }
615 
617 {
618  if(!current_track) {
619  return;
620  }
621 
622  music_start_time = 1; // immediate (same as effect as SDL_GetTicks())
623  want_new_music = true;
624  no_fading = false;
625  fadingout_time = previous_track != nullptr ? previous_track->ms_after() : 0;
626 }
627 
628 void play_track(unsigned int i)
629 {
630  set_previous_track(current_track);
631  if(i >= current_track_list.size()) {
632  current_track = choose_track();
633  } else {
634  current_track_index = i;
635  current_track = current_track_list[i];
636  }
637  play_music();
638 }
639 
640 static void play_new_music()
641 {
642  music_start_time = 0; // reset status: no start time
643  want_new_music = true;
644 
645  if(!preferences::music_on() || !mix_ok || !current_track || !current_track->valid()) {
646  return;
647  }
648 
649  const std::string localized = filesystem::get_localized_path(current_track->file_path());
650  const std::string& filename = localized.empty() ? current_track->file_path() : localized;
651 
652  auto itor = music_cache.find(filename);
653  if(itor == music_cache.end()) {
654  LOG_AUDIO << "attempting to insert track '" << filename << "' into cache\n";
655 
657  // SDL takes ownership of rwops
658  const std::shared_ptr<Mix_Music> music(Mix_LoadMUSType_RW(rwops.release(), MUS_NONE, true), &Mix_FreeMusic);
659 
660  if(music == nullptr) {
661  ERR_AUDIO << "Could not load music file '" << filename << "': " << Mix_GetError() << "\n";
662  return;
663  }
664 
665  itor = music_cache.emplace(filename, music).first;
666  }
667 
668  LOG_AUDIO << "Playing track '" << filename << "'\n";
669  int fading_time = current_track->ms_before();
670  if(no_fading) {
671  fading_time = 0;
672  }
673 
674  const int res = Mix_FadeInMusic(itor->second.get(), 1, fading_time);
675  if(res < 0) {
676  ERR_AUDIO << "Could not play music: " << Mix_GetError() << " " << filename << " " << std::endl;
677  }
678 
679  want_new_music = false;
680 }
681 
682 void play_music_repeatedly(const std::string& id)
683 {
684  // Can happen if scenario doesn't specify.
685  if(id.empty()) {
686  return;
687  }
688 
689  current_track_list.clear();
690  current_track_list.emplace_back(new music_track(id));
691 
692  std::shared_ptr<music_track> last_track = current_track;
693  current_track = current_track_list.back();
694  current_track_index = 0;
695 
696  // If we're already playing it, don't interrupt.
697  if(!last_track || !current_track || *last_track != *current_track) {
698  play_music();
699  }
700 
701  last_track.reset();
702 }
703 
704 void play_music_config(const config& music_node, bool allow_interrupt_current_track, int i)
705 {
706  //
707  // FIXME: there is a memory leak somewhere in this function, seemingly related to the shared_ptrs
708  // stored in current_track_list.
709  //
710  // vultraz 5/8/2017
711  //
712 
713  music_track track(music_node);
714 
715  if(!track.valid() && !track.id().empty()) {
716  ERR_AUDIO << "cannot open track '" << track.id() << "'; disabled in this playlist." << std::endl;
717  }
718 
719  // If they say play once, we don't alter playlist.
720  if(track.play_once()) {
721  set_previous_track(current_track);
722  current_track = std::make_shared<music_track>(track);
723  current_track_index = current_track_list.size();
724  play_music();
725  return;
726  }
727 
728  // Clear play list unless they specify append.
729  if(!track.append()) {
730  current_track_list.clear();
731  }
732 
733  if(!track.valid()) {
734  return;
735  }
736 
737  auto iter = find_track(track);
738  // Avoid 2 tracks with the same name, since that can cause an infinite loop
739  // in choose_track(), 2 tracks with the same name will always return the
740  // current track and track_ok() doesn't allow that.
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;
745  } else {
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++;
749  }
750  }
751  } else {
752  ERR_AUDIO << "tried to add duplicate track '" << track.file_path() << "'" << std::endl;
753  }
754 
755  // They can tell us to start playing this list immediately.
756  if(track.immediate()) {
757  set_previous_track(current_track);
758  current_track = *iter;
759  current_track_index = iter - current_track_list.begin();
760  play_music();
761  } else if(!track.append() && !allow_interrupt_current_track && current_track) {
762  // Make sure the current track will finish first
763  current_track->set_play_once(true);
764  }
765 }
766 
768 {
769  if(preferences::music_on()) {
770  if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
771  // Pick next track, add ending time to its start time.
772  set_previous_track(current_track);
773  current_track = choose_track();
774  music_start_time = info.ticks();
775  no_fading = true;
776  fadingout_time = 0;
777  }
778 
779  if(music_start_time && info.ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
780  want_new_music = true;
781  }
782 
783  if(want_new_music) {
784  if(Mix_PlayingMusic()) {
785  Mix_FadeOutMusic(fadingout_time);
786  }
787 
788  unload_music = false;
789  play_new_music();
790  }
791  }
792 
793  if(unload_music) {
794  // The custom shared_ptr deleter (Mix_FreeMusic) will handle the freeing of each track.
795  music_cache.clear();
796 
797  Mix_HookMusicFinished(nullptr);
798 
799  unload_music = false;
800  }
801 }
802 
804  : events::sdl_handler(false)
805 {
806  join_global();
807 }
808 
809 void music_muter::handle_window_event(const SDL_Event& event)
810 {
812  if(event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
813  Mix_ResumeMusic();
814  } else if(event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
815  if(Mix_PlayingMusic()) {
816  Mix_PauseMusic();
817  }
818  }
819  }
820 }
821 
823 {
824  played_before.clear();
825 
826  // Play-once is OK if still playing.
827  if(current_track) {
828  if(current_track->play_once()) {
829  return;
830  }
831 
832  // If current track no longer on playlist, change it.
833  for(auto m : current_track_list) {
834  if(*current_track == *m) {
835  return;
836  }
837  }
838  }
839 
840  // Victory empties playlist: if next scenario doesn't specify one...
841  if(current_track_list.empty()) {
842  return;
843  }
844 
845  // FIXME: we don't pause ms_before on this first track. Should we?
846  set_previous_track(current_track);
847  current_track = choose_track();
848  play_music();
849 }
850 
852 {
853  // First entry clears playlist, others append to it.
854  bool append = false;
855  for(auto m : current_track_list) {
856  m->write(snapshot, append);
857  append = true;
858  }
859 }
860 
861 void reposition_sound(int id, unsigned int distance)
862 {
863  audio_lock lock;
864  for(unsigned ch = 0; ch < channel_ids.size(); ++ch) {
865  if(channel_ids[ch] != id) {
866  continue;
867  }
868 
869  if(distance >= DISTANCE_SILENT) {
870  Mix_HaltChannel(ch);
871  } else {
872  Mix_SetDistance(ch, distance);
873  }
874  }
875 }
876 
877 bool is_sound_playing(int id)
878 {
879  audio_lock lock;
880  return std::find(channel_ids.begin(), channel_ids.end(), id) != channel_ids.end();
881 }
882 
883 void stop_sound(int id)
884 {
886 }
887 
889 {
890 };
891 
892 static Mix_Chunk* load_chunk(const std::string& file, channel_group group)
893 {
894  sound_cache_iterator it_bgn, it_end;
895  sound_cache_iterator it;
896 
897  sound_cache_chunk temp_chunk(file); // search the sound cache on this key
898  it_bgn = sound_cache.begin();
899  it_end = sound_cache.end();
900  it = std::find(it_bgn, it_end, temp_chunk);
901 
902  if(it != it_end) {
903  if(it->group != group) {
904  // cached item has been used in multiple sound groups
905  it->group = NULL_CHANNEL;
906  }
907 
908  // splice the most recently used chunk to the front of the cache
909  sound_cache.splice(it_bgn, sound_cache, it);
910  } else {
911  // remove the least recently used chunk from cache if it's full
912  bool cache_full = (sound_cache.size() == max_cached_chunks);
913  while(cache_full && it != it_bgn) {
914  // make sure this chunk is not being played before freeing it
916  if(std::find(channel_chunks.begin(), ch_end, (--it)->get_data()) == ch_end) {
917  sound_cache.erase(it);
918  cache_full = false;
919  }
920  }
921 
922  if(cache_full) {
923  LOG_AUDIO << "Maximum sound cache size reached and all are busy, skipping.\n";
924  throw chunk_load_exception();
925  }
926 
927  temp_chunk.group = group;
928  const std::string& filename = filesystem::get_binary_file_location("sounds", file);
929  const std::string localized = filesystem::get_localized_path(filename);
930 
931  if(!filename.empty()) {
932  filesystem::rwops_ptr rwops = filesystem::make_read_RWops(localized.empty() ? filename : localized);
933  temp_chunk.set_data(Mix_LoadWAV_RW(rwops.release(), true)); // SDL takes ownership of rwops
934  } else {
935  ERR_AUDIO << "Could not load sound file '" << file << "'." << std::endl;
936  throw chunk_load_exception();
937  }
938 
939  if(temp_chunk.get_data() == nullptr) {
940  ERR_AUDIO << "Could not load sound file '" << filename << "': " << Mix_GetError() << "\n";
941  throw chunk_load_exception();
942  }
943 
944  sound_cache.push_front(temp_chunk);
945  }
946 
947  return sound_cache.begin()->get_data();
948 }
949 
950 static void play_sound_internal(const std::string& files,
951  channel_group group,
952  unsigned int repeats = 0,
953  unsigned int distance = 0,
954  int id = -1,
955  int loop_ticks = 0,
956  int fadein_ticks = 0)
957 {
958  if(files.empty() || distance >= DISTANCE_SILENT || !mix_ok) {
959  return;
960  }
961 
962  audio_lock lock;
963 
964  // find a free channel in the desired group
965  int channel = Mix_GroupAvailable(group);
966  if(channel == -1) {
967  LOG_AUDIO << "All channels dedicated to sound group(" << group << ") are busy, skipping.\n";
968  return;
969  }
970 
971  Mix_Chunk* chunk;
972  std::string file = pick_one(files);
973 
974  try {
975  chunk = load_chunk(file, group);
976  assert(chunk);
977  } catch(const chunk_load_exception&) {
978  return;
979  }
980 
981  /*
982  * This check prevents SDL_Mixer from blowing up on Windows when UI sound is played
983  * in response to toggling the checkbox which disables sound.
984  */
985  if(group != SOUND_UI) {
986  Mix_SetDistance(channel, distance);
987  }
988 
989  int res;
990  if(loop_ticks > 0) {
991  if(fadein_ticks > 0) {
992  res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
993  } else {
994  res = Mix_PlayChannel(channel, chunk, -1);
995  }
996 
997  if(res >= 0) {
998  Mix_ExpireChannel(channel, loop_ticks);
999  }
1000  } else {
1001  if(fadein_ticks > 0) {
1002  res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
1003  } else {
1004  res = Mix_PlayChannel(channel, chunk, repeats);
1005  }
1006  }
1007 
1008  if(res < 0) {
1009  ERR_AUDIO << "error playing sound effect: " << Mix_GetError() << std::endl;
1010  // still keep it in the sound cache, in case we want to try again later
1011  return;
1012  }
1013 
1014  channel_ids[channel] = id;
1015 
1016  // reserve the channel's chunk from being freed, since it is playing
1017  channel_chunks[res] = chunk;
1018 }
1019 
1020 void play_sound(const std::string& files, channel_group group, unsigned int repeats)
1021 {
1022  if(preferences::sound_on()) {
1023  play_sound_internal(files, group, repeats);
1024  }
1025 }
1026 
1027 void play_sound_positioned(const std::string& files, int id, int repeats, unsigned int distance)
1028 {
1029  if(preferences::sound_on()) {
1030  play_sound_internal(files, SOUND_SOURCES, repeats, distance, id);
1031  }
1032 }
1033 
1034 // Play bell with separate volume setting
1035 void play_bell(const std::string& files)
1036 {
1037  if(preferences::turn_bell()) {
1039  }
1040 }
1041 
1042 // Play timer with separate volume setting
1043 void play_timer(const std::string& files, int loop_ticks, int fadein_ticks)
1044 {
1045  if(preferences::sound_on()) {
1046  play_sound_internal(files, SOUND_TIMER, 0, 0, -1, loop_ticks, fadein_ticks);
1047  }
1048 }
1049 
1050 // Play UI sounds on separate volume than soundfx
1051 void play_UI_sound(const std::string& files)
1052 {
1053  if(preferences::UI_sound_on()) {
1054  play_sound_internal(files, SOUND_UI);
1055  }
1056 }
1057 
1059 {
1060  if(mix_ok) {
1061  return Mix_VolumeMusic(-1);
1062  }
1063 
1064  return 0;
1065 }
1066 
1067 void set_music_volume(int vol)
1068 {
1069  if(mix_ok && vol >= 0) {
1070  if(vol > MIX_MAX_VOLUME) {
1071  vol = MIX_MAX_VOLUME;
1072  }
1073 
1074  Mix_VolumeMusic(vol);
1075  }
1076 }
1077 
1079 {
1080  if(mix_ok) {
1081  // Since set_sound_volume sets all main channels to the same, just return the volume of any main channel
1082  return Mix_Volume(source_channel_start, -1);
1083  }
1084  return 0;
1085 }
1086 
1087 void set_sound_volume(int vol)
1088 {
1089  if(mix_ok && vol >= 0) {
1090  if(vol > MIX_MAX_VOLUME) {
1091  vol = MIX_MAX_VOLUME;
1092  }
1093 
1094  // Bell, timer and UI have separate channels which we can't set up from this
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) {
1098  Mix_Volume(i, vol);
1099  }
1100  }
1101  }
1102 }
1103 
1104 /*
1105  * For the purpose of volume setting, we treat turn timer the same as bell
1106  */
1107 void set_bell_volume(int vol)
1108 {
1109  if(mix_ok && vol >= 0) {
1110  if(vol > MIX_MAX_VOLUME) {
1111  vol = MIX_MAX_VOLUME;
1112  }
1113 
1114  Mix_Volume(bell_channel, vol);
1115  Mix_Volume(timer_channel, vol);
1116  }
1117 }
1118 
1119 void set_UI_volume(int vol)
1120 {
1121  if(mix_ok && vol >= 0) {
1122  if(vol > MIX_MAX_VOLUME) {
1123  vol = MIX_MAX_VOLUME;
1124  }
1125 
1126  for(unsigned i = UI_sound_channel_start; i <= UI_sound_channel_last; ++i) {
1127  Mix_Volume(i, vol);
1128  }
1129  }
1130 }
1131 
1132 } // end namespace sound
void empty_playlist()
Definition: sound.cpp:611
void close_sound()
Definition: sound.cpp:494
unsigned int get_num_tracks()
Definition: sound.cpp:220
int bell_volume()
Definition: general.cpp:576
bool turn_bell()
Definition: general.cpp:616
const std::string & id() const
bool is_sound_playing(int id)
Definition: sound.cpp:877
void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
Definition: sound.cpp:1027
void stop_music()
Definition: sound.cpp:556
channel_group
Definition: sound.hpp:27
std::string current_driver()
Definition: sound.cpp:411
std::shared_ptr< music_track > get_current_track()
Definition: sound.cpp:207
logger & info()
Definition: log.cpp:90
void set_UI_volume(int vol)
Definition: sound.cpp:1119
int ticks(unsigned *refresh_counter=nullptr, unsigned refresh_rate=1)
Definition: events.cpp:835
rwops_ptr make_read_RWops(const std::string &path)
static void play_new_music()
Definition: sound.cpp:640
void remove_track(unsigned int i)
Definition: sound.cpp:245
std::shared_ptr< music_track > get_previous_music_track()
Definition: sound.cpp:211
void stop_sound()
Definition: sound.cpp:564
unsigned int sample_rate()
Definition: general.cpp:894
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&#39;t prese...
Audio output for sound and music.
Definition: sound.cpp:40
void play_music_once(const std::string &file)
Definition: sound.cpp:602
#define d
static lg::log_domain log_audio("audio")
bool operator!=(const config &a, const config &b)
Definition: config.hpp:64
void write_music_play_list(config &snapshot)
Definition: sound.cpp:851
Definitions for the interface to Wesnoth Markup Language (WML).
bool sound_on()
Definition: general.cpp:669
void reposition_sound(int id, unsigned int distance)
Definition: sound.cpp:861
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
Definition: sound.cpp:1020
bool init_sound()
Definition: sound.cpp:442
static bool track_ok(const std::string &id)
Definition: sound.cpp:267
#define DBG_AUDIO
Definition: sound.cpp:32
void reset_sound()
Definition: sound.cpp:525
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)
Definition: sound.cpp:950
#define LOG_AUDIO
Definition: sound.cpp:33
void set_bell_volume(int vol)
Definition: sound.cpp:1107
static void channel_finished_hook(int channel)
Definition: sound.cpp:405
static std::string pick_one(const std::string &files)
Definition: sound.cpp:354
void stop_UI_sound()
Definition: sound.cpp:591
void set_previous_track(std::shared_ptr< music_track > track)
Definition: sound.cpp:215
void play_music()
Definition: sound.cpp:616
static Mix_Chunk * load_chunk(const std::string &file, channel_group group)
Definition: sound.cpp:892
boost::optional< unsigned int > get_current_track_index()
Definition: sound.cpp:200
bool UI_sound_on()
Definition: general.cpp:640
int get_sound_volume()
Definition: sound.cpp:1078
void set_sound_volume(int vol)
Definition: sound.cpp:1087
void set_music_volume(int vol)
Definition: sound.cpp:1067
void flush_cache()
Definition: sound.cpp:194
std::size_t i
Definition: function.cpp:933
static std::vector< Mix_Chunk * > channel_chunks
Definition: sound.cpp:43
void process(events::pump_info &info)
Definition: sound.cpp:767
bool stop_music_in_background()
Definition: general.cpp:718
bool operator==(const config &a, const config &b)
Definition: config.cpp:1418
Internal representation of music tracks.
bool music_on()
Definition: general.cpp:692
#define ERR_AUDIO
Definition: sound.cpp:34
Declarations for File-IO.
void stop_bell()
Definition: sound.cpp:579
std::unique_ptr< SDL_RWops, void(*)(SDL_RWops *)> rwops_ptr
Definition: filesystem.hpp:42
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:51
std::string get_localized_path(const std::string &file, const std::string &suff)
Returns the localized version of the given filename, if it exists.
int get_music_volume()
Definition: sound.cpp:1058
static void increment_chunk_usage(Mix_Chunk *mcp)
Definition: sound.cpp:84
std::size_t sound_buffer_size()
Definition: general.cpp:516
Handling of system events.
Definition: manager.hpp:42
const std::string & file_path() const
void play_bell(const std::string &files)
Definition: sound.cpp:1035
int sound_volume()
Definition: general.cpp:561
static std::vector< int > channel_ids
Definition: sound.cpp:47
#define f
void play_timer(const std::string &files, int loop_ticks, int fadein_ticks)
Definition: sound.cpp:1043
bool find(E event, F functor)
Tests whether an event handler is available.
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition: sound.cpp:704
void play_music_repeatedly(const std::string &id)
Definition: sound.cpp:682
#define DISTANCE_SILENT
Definition: sound.hpp:75
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1051
Standard logging facilities (interface).
void handle_window_event(const SDL_Event &event) override
Definition: sound.cpp:809
int UI_volume()
Definition: general.cpp:591
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:46
void commit_music_changes()
Definition: sound.cpp:822
void set_track(unsigned int i, const std::shared_ptr< music_track > &to)
Definition: sound.cpp:238
static driver_status query()
Definition: sound.cpp:430
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
mock_char c
std::vector< std::string > enumerate_drivers()
Definition: sound.cpp:417
void play_track(unsigned int i)
Definition: sound.cpp:628
static map_location::DIRECTION n
static std::shared_ptr< sound::music_track > choose_track()
Definition: sound.cpp:329
static void decrement_chunk_usage(Mix_Chunk *mcp)
Definition: sound.cpp:89
static rng & default_instance()
Definition: random.cpp:73
virtual void join_global()
Definition: events.cpp:365
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
std::shared_ptr< music_track > get_track(unsigned int i)
Definition: sound.cpp:225
int music_volume()
Definition: general.cpp:546
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.
channel
Definition: utils.hpp:147