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