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