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