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