The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
sound.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2016 by David White <[email protected]>
3  Part of the Battle for Wesnoth Project http://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 "global.hpp"
16 
17 #include "config.hpp"
18 #include "filesystem.hpp"
19 #include "game_preferences.hpp"
20 #include "log.hpp"
22 #include "sound.hpp"
23 #include "sound_music_track.hpp"
24 #include "util.hpp"
25 
26 #include <SDL_mixer.h>
27 #include <SDL.h> // Travis doesn't like this, although it works on my machine -> '#include <SDL_sound.h>
28 
29 #include <list>
30 #include <string>
31 #include <sstream>
32 
33 static lg::log_domain log_audio("audio");
34 #define DBG_AUDIO LOG_STREAM(debug, log_audio)
35 #define LOG_AUDIO LOG_STREAM(info, log_audio)
36 #define ERR_AUDIO LOG_STREAM(err, log_audio)
37 
38 
39 #if (MIX_MAJOR_VERSION < 1) || (MIX_MAJOR_VERSION == 1) && ((MIX_MINOR_VERSION < 2) || (MIX_MINOR_VERSION == 2) && (MIX_PATCHLEVEL <= 11))
40 #error "Please upgrade to SDL mixer version >= 1.2.12, we don't support older versions anymore."
41 #endif
42 
43 namespace sound {
44 // Channel-chunk mapping lets us know, if we can safely free a given chunk
45 static std::vector<Mix_Chunk*> channel_chunks;
46 
47 // Channel-id mapping for use with sound sources (to check if given source
48 // is playing on a channel for fading/panning)
49 static std::vector<int> channel_ids;
50 
51 static void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats=0,
52  unsigned int distance=0, int id=-1, int loop_ticks=0, int fadein_ticks=0);
53 }
54 
55 namespace {
56 
57 bool mix_ok = false;
58 int music_start_time = 0;
59 unsigned music_refresh = 0;
60 unsigned music_refresh_rate = 20;
61 bool want_new_music = false;
62 int fadingout_time=5000;
63 bool no_fading = false;
64 
65 // number of allocated channels,
66 const size_t n_of_channels = 32;
67 
68 // we need 2 channels, because we it for timer as well
69 const size_t bell_channel = 0;
70 const size_t timer_channel = 1;
71 
72 // number of channels reserved for sound sources
73 const size_t source_channels = 8;
74 const size_t source_channel_start = timer_channel + 1;
75 const size_t source_channel_last = source_channel_start + source_channels - 1;
76 const size_t UI_sound_channels = 2;
77 const size_t UI_sound_channel_start = source_channel_last + 1;
78 const size_t UI_sound_channel_last = UI_sound_channel_start + UI_sound_channels - 1;
79 const size_t n_reserved_channels = UI_sound_channel_last + 1; // sources, bell, timer and UI
80 
81 // Max number of sound chunks that we want to cache
82 // Keep this above number of available channels to avoid busy-looping
83 #ifdef LOW_MEM
84 unsigned max_cached_chunks = 64;
85 #else
86 unsigned max_cached_chunks = 256;
87 #endif
88 
89 std::map< Mix_Chunk*, int > chunk_usage;
90 
91 }
92 
93 static void increment_chunk_usage(Mix_Chunk* mcp) {
94  ++(chunk_usage[mcp]);
95 }
96 
97 static void decrement_chunk_usage(Mix_Chunk* mcp) {
98  if(mcp == nullptr) return;
99  std::map< Mix_Chunk*, int >::iterator this_usage = chunk_usage.find(mcp);
100  assert(this_usage != chunk_usage.end());
101  if(--(this_usage->second) == 0) {
102  Mix_FreeChunk(mcp);
103  chunk_usage.erase(this_usage);
104  }
105 }
106 
107 namespace {
108 
109 class sound_cache_chunk {
110 public:
111  sound_cache_chunk(const std::string& f) : group(sound::NULL_CHANNEL), file(f), data_(nullptr) {}
112  sound_cache_chunk(const sound_cache_chunk& scc)
113  : group(scc.group), file(scc.file), data_(scc.data_)
114  {
115  increment_chunk_usage(data_);
116  }
117 
118  ~sound_cache_chunk()
119  {
120  decrement_chunk_usage(data_);
121  }
122 
124  std::string file;
125 
126  void set_data(Mix_Chunk* d) {
128  decrement_chunk_usage(data_);
129  data_ = d;
130  }
131 
132  Mix_Chunk* get_data() const {
133  return data_;
134  }
135 
136  bool operator==(sound_cache_chunk const &scc) const {
137  return file == scc.file;
138  }
139 
140  bool operator!=(sound_cache_chunk const &scc) const { return !operator==(scc); }
141 
142  sound_cache_chunk& operator=(const sound_cache_chunk& scc) {
143  file = scc.file;
144  group = scc.group;
145  set_data(scc.get_data());
146  return *this;
147  }
148 
149 private:
150  Mix_Chunk* data_;
151 };
152 
153 std::list< sound_cache_chunk > sound_cache;
154 typedef std::list< sound_cache_chunk >::iterator sound_cache_iterator;
155 std::map<std::string,Mix_Music*> music_cache;
156 
157 std::vector<std::string> played_before;
158 
159 //
160 // FIXME: the first music_track may be initialized before main()
161 // is reached. Using the logging facilities may lead to a SIGSEGV
162 // because it's not guaranteed that their objects are already alive.
163 //
164 // Use the music_track default constructor to avoid trying to
165 // invoke a log object while resolving paths.
166 //
167 std::vector<sound::music_track> current_track_list;
168 sound::music_track current_track;
169 sound::music_track last_track;
170 unsigned int current_track_index = 0;
171 
172 }
173 
174 static bool track_ok(const std::string& id)
175 {
176  LOG_AUDIO << "Considering " << id << "\n";
177 
178  // If they committed changes to list, we forget previous plays, but
179  // still *never* repeat same track twice if we have an option.
180  if (id == current_track.file_path())
181  return false;
182 
183  if (current_track_list.size() <= 3)
184  return true;
185 
186  // Timothy Pinkham says:
187  // 1) can't be repeated without 2 other pieces have already played
188  // since A was played.
189  // 2) cannot play more than 2 times without every other piece
190  // having played at least 1 time.
191 
192  // Dammit, if our musicians keep coming up with algorithms, I'll
193  // be out of a job!
194  unsigned int num_played = 0;
195  std::set<std::string> played;
196  std::vector<std::string>::reverse_iterator i;
197 
198  for (i = played_before.rbegin(); i != played_before.rend(); ++i) {
199  if (*i == id) {
200  ++num_played;
201  if (num_played == 2)
202  break;
203  } else {
204  played.insert(*i);
205  }
206  }
207 
208  // If we've played this twice, must have played every other track.
209  if (num_played == 2 && played.size() != current_track_list.size() - 1) {
210  LOG_AUDIO << "Played twice with only " << played.size()
211  << " tracks between\n";
212  return false;
213  }
214 
215  // Check previous previous track not same.
216  i = played_before.rbegin();
217  if (i != played_before.rend()) {
218  ++i;
219  if (i != played_before.rend()) {
220  if (*i == id) {
221  LOG_AUDIO << "Played just before previous\n";
222  return false;
223  }
224  }
225  }
226 
227  return true;
228 }
229 
230 
232 {
233  assert(!current_track_list.empty());
234 
235  if (current_track_index >= current_track_list.size()) {
236  current_track_index = 0;
237  }
238 
239  if (current_track_list[current_track_index].shuffle()) {
240  unsigned int track = 0;
241 
242  if (current_track_list.size() > 1) {
243  do {
244  track = rand()%current_track_list.size();
245  } while (!track_ok( current_track_list[track].file_path() ));
246  }
247 
248  current_track_index = track;
249  }
250 
251  //LOG_AUDIO << "Next track will be " << current_track_list[track].file_path() << "\n";
252  played_before.push_back( current_track_list[current_track_index].file_path() );
253  return current_track_list[current_track_index++];
254 }
255 
256 static std::string pick_one(const std::string &files)
257 {
258  std::vector<std::string> ids = utils::square_parenthetical_split(files,',',"[","]");
259 
260  if (ids.empty())
261  return "";
262  if (ids.size() == 1)
263  return ids[0];
264 
265 #ifdef LOW_MEM
266  // We're memory constrained, so we shouldn't cache too many chunks
267  return ids[0];
268 #endif
269 
270  // We avoid returning same choice twice if we can avoid it.
271  static std::map<std::string,unsigned int> prev_choices;
272  unsigned int choice;
273 
274  if (prev_choices.find(files) != prev_choices.end()) {
275  choice = rand()%(ids.size()-1);
276  if (choice >= prev_choices[files])
277  ++choice;
278  prev_choices[files] = choice;
279  } else {
280  choice = rand()%ids.size();
281  prev_choices.insert(std::pair<std::string,unsigned int>(files,choice));
282  }
283 
284  return ids[choice];
285 }
286 
287 namespace {
288 
289 struct audio_lock
290 {
291  audio_lock()
292  {
293  SDL_LockAudio();
294  }
295 
296  ~audio_lock()
297  {
298  SDL_UnlockAudio();
299  }
300 };
301 
302 } // end of anonymous namespace
303 
304 
305 namespace sound {
306 
307 // Removes channel-chunk and channel-id mapping
309 {
310  channel_chunks[channel] = nullptr;
311  channel_ids[channel] = -1;
312 }
313 
314 bool init_sound() {
315  LOG_AUDIO << "Initializing audio...\n";
316  if(SDL_WasInit(SDL_INIT_AUDIO) == 0)
317  if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
318  return false;
319 
320  if(!mix_ok) {
321  if(Mix_OpenAudio(preferences::sample_rate(), MIX_DEFAULT_FORMAT, 2, preferences::sound_buffer_size()) == -1) {
322  mix_ok = false;
323  ERR_AUDIO << "Could not initialize audio: " << Mix_GetError() << std::endl;
324  return false;
325  }
326 
327  mix_ok = true;
328  Mix_AllocateChannels(n_of_channels);
329  Mix_ReserveChannels(n_reserved_channels);
330 
331  channel_chunks.clear();
332  channel_chunks.resize(n_of_channels, nullptr);
333  channel_ids.resize(n_of_channels, -1);
334 
335  Mix_GroupChannel(bell_channel, SOUND_BELL);
336  Mix_GroupChannel(timer_channel, SOUND_TIMER);
337  Mix_GroupChannels(source_channel_start, source_channel_last, SOUND_SOURCES);
338  Mix_GroupChannels(UI_sound_channel_start, UI_sound_channel_last, SOUND_UI);
339  Mix_GroupChannels(n_reserved_channels, n_of_channels - 1, SOUND_FX);
340 
345 
346  Mix_ChannelFinished(channel_finished_hook);
347 
348  LOG_AUDIO << "Audio initialized.\n";
349 
350  DBG_AUDIO << "Channel layout: " << n_of_channels << " channels (" << n_reserved_channels << " reserved)\n"
351  << " " << bell_channel << " - bell\n"
352  << " " << timer_channel << " - timer\n"
353  << " " << source_channel_start << ".." << source_channel_last << " - sound sources\n"
354  << " " << UI_sound_channel_start << ".." << UI_sound_channel_last << " - UI\n"
355  << " " << UI_sound_channel_last + 1 << ".." << n_of_channels - 1 << " - sound effects\n";
356 
357  play_music();
358  }
359  return true;
360 }
361 
362 void close_sound() {
363  int frequency, channels;
364  Uint16 format;
365 
366  if(mix_ok) {
367  stop_bell();
368  stop_UI_sound();
369  stop_sound();
370  sound_cache.clear();
371  stop_music();
372  mix_ok = false;
373 
374  int numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
375  if(numtimesopened == 0) {
376  ERR_AUDIO << "Error closing audio device: " << Mix_GetError() << std::endl;
377  }
378  while (numtimesopened) {
379  Mix_CloseAudio();
380  --numtimesopened;
381  }
382  }
383  if(SDL_WasInit(SDL_INIT_AUDIO) != 0)
384  SDL_QuitSubSystem(SDL_INIT_AUDIO);
385 
386  LOG_AUDIO << "Audio device released.\n";
387 }
388 
389 void reset_sound() {
390  bool music = preferences::music_on();
391  bool sound = preferences::sound_on();
392  bool UI_sound = preferences::UI_sound_on();
393  bool bell = preferences::turn_bell();
394 
395  if (music || sound || bell || UI_sound) {
397  if (!sound::init_sound()) {
398  ERR_AUDIO << "Error initializing audio device: " << Mix_GetError() << std::endl;
399  }
400  if (!music)
402  if (!sound)
404  if (!UI_sound)
406  if (!bell)
408  }
409 }
410 
411 void stop_music() {
412  if(mix_ok) {
413  Mix_HaltMusic();
414 
416  for(i = music_cache.begin(); i != music_cache.end(); ++i)
417  Mix_FreeMusic(i->second);
418  music_cache.clear();
419  }
420 }
421 
422 void stop_sound() {
423  if(mix_ok) {
424  Mix_HaltGroup(SOUND_SOURCES);
425  Mix_HaltGroup(SOUND_FX);
426  sound_cache_iterator itor = sound_cache.begin();
427  while(itor != sound_cache.end())
428  {
429  if(itor->group == SOUND_SOURCES || itor->group == SOUND_FX) {
430  itor = sound_cache.erase(itor);
431  } else {
432  ++itor;
433  }
434  }
435  }
436 }
437 
438 /*
439  * For the purpose of channel manipulation, we treat turn timer the same as bell
440  */
441 void stop_bell() {
442  if(mix_ok) {
443  Mix_HaltGroup(SOUND_BELL);
444  Mix_HaltGroup(SOUND_TIMER);
445  sound_cache_iterator itor = sound_cache.begin();
446  while(itor != sound_cache.end())
447  {
448  if(itor->group == SOUND_BELL || itor->group == SOUND_TIMER) {
449  itor = sound_cache.erase(itor);
450  } else {
451  ++itor;
452  }
453  }
454  }
455 }
456 
458  if(mix_ok) {
459  Mix_HaltGroup(SOUND_UI);
460  sound_cache_iterator itor = sound_cache.begin();
461  while(itor != sound_cache.end())
462  {
463  if(itor->group == SOUND_UI) {
464  itor = sound_cache.erase(itor);
465  } else {
466  ++itor;
467  }
468  }
469  }
470 }
471 
472 void play_music_once(const std::string &file)
473 {
474  // Clear list so it's not replayed.
475  current_track_list.clear();
476  current_track = music_track(file);
477  play_music();
478 }
479 
481 {
482  current_track_list.clear();
483 }
484 
486 {
487  music_start_time = 1; //immediate (same as effect as SDL_GetTicks())
488  want_new_music=true;
489  no_fading=false;
490  fadingout_time=current_track.ms_after();
491 }
492 
493 static void play_new_music()
494 {
495  music_start_time = 0; //reset status: no start time
496  want_new_music = true;
497 
498  if(!preferences::music_on() || !mix_ok || !current_track.valid()) {
499  return;
500  }
501 
502  const std::string& filename = current_track.file_path();
503 
504  std::map<std::string,Mix_Music*>::const_iterator itor = music_cache.find(filename);
505  if(itor == music_cache.end()) {
506  LOG_AUDIO << "attempting to insert track '" << filename << "' into cache\n";
507 
508  SDL_RWops *rwops = filesystem::load_RWops(filename);
509  Mix_Music* const music = Mix_LoadMUSType_RW(rwops, MUS_NONE, true); // SDL takes ownership of rwops
510 
511  if(music == nullptr) {
512  ERR_AUDIO << "Could not load music file '" << filename << "': "
513  << Mix_GetError() << "\n";
514  return;
515  }
516  itor = music_cache.insert(std::pair<std::string,Mix_Music*>(filename,music)).first;
517  last_track=current_track;
518  }
519 
520  LOG_AUDIO << "Playing track '" << filename << "'\n";
521  int fading_time=current_track.ms_before();
522  if(no_fading)
523  {
524  fading_time=0;
525  }
526 
527  const int res = Mix_FadeInMusic(itor->second, 1, fading_time);
528  if(res < 0)
529  {
530  ERR_AUDIO << "Could not play music: " << Mix_GetError() << " " << filename <<" " << std::endl;
531  }
532 
533  want_new_music=false;
534 }
535 
537 {
538  // Can happen if scenario doesn't specify.
539  if (id.empty())
540  return;
541 
542  current_track_list.clear();
543  current_track_list.push_back(music_track(id));
544 
545  // If we're already playing it, don't interrupt.
546  if (current_track != id) {
547  current_track = music_track(id);
548  play_music();
549  }
550 }
551 
552 void play_music_config(const config &music_node)
553 {
554  music_track track( music_node );
555 
556  if (!track.valid() && !track.id().empty()) {
557  ERR_AUDIO << "cannot open track '" << track.id() << "'; disabled in this playlist." << std::endl;
558  }
559 
560  // If they say play once, we don't alter playlist.
561  if (track.play_once()) {
562  current_track = track;
563  play_music();
564  return;
565  }
566 
567  // Clear play list unless they specify append.
568  if (!track.append()) {
569  current_track_list.clear();
570  }
571 
572  if(track.valid()) {
573  // Avoid 2 tracks with the same name, since that can cause an infinite loop
574  // in choose_track(), 2 tracks with the same name will always return the
575  // current track and track_ok() doesn't allow that.
576  std::vector<music_track>::const_iterator itor = current_track_list.begin();
577  while(itor != current_track_list.end()) {
578  if(track == *itor) break;
579  ++itor;
580  }
581 
582  if(itor == current_track_list.end()) {
583  current_track_list.push_back(track);
584  } else {
585  ERR_AUDIO << "tried to add duplicate track '" << track.file_path() << "'" << std::endl;
586  }
587  }
588 
589  // They can tell us to start playing this list immediately.
590  if (track.immediate()) {
591  current_track = track;
592  play_music();
593  } else if (!track.append()) { // Make sure the current track is finished
594  current_track.set_play_once(true);
595  }
596 }
597 
599  if(preferences::music_on()) {
600  if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
601  // Pick next track, add ending time to its start time.
602  current_track = choose_track();
603  music_start_time = info.ticks();
604  no_fading=true;
605  fadingout_time=0;
606  }
607 
608  if(music_start_time && info.ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
609  want_new_music=true;
610  }
611 
612  if(want_new_music) {
613  if(Mix_PlayingMusic()) {
614  Mix_FadeOutMusic(fadingout_time);
615  }
616  play_new_music();
617  }
618  }
619 }
620 
622 {
623  played_before.clear();
624 
625  // Play-once is OK if still playing.
626  if (current_track.play_once())
627  return;
628 
629  // If current track no longer on playlist, change it.
630  for (const music_track &m : current_track_list) {
631  if (current_track == m)
632  return;
633  }
634 
635  // Victory empties playlist: if next scenario doesn't specify one...
636  if (current_track_list.empty())
637  return;
638 
639  // FIXME: we don't pause ms_before on this first track. Should we?
640  current_track = choose_track();
641  play_music();
642 }
643 
645 {
646  // First entry clears playlist, others append to it.
647  bool append = false;
648  for (music_track &m : current_track_list) {
649  m.write(snapshot, append);
650  append = true;
651  }
652 }
653 
654 void reposition_sound(int id, unsigned int distance)
655 {
656  audio_lock lock;
657  for (unsigned ch = 0; ch < channel_ids.size(); ++ch)
658  {
659  if (channel_ids[ch] != id) continue;
660  if (distance >= DISTANCE_SILENT) {
661  // Don't call Mix_FadeOutChannel if the channel's volume is set to
662  // zero. It doesn't do anything in that case and the channel will
663  // resume playing as soon as its volume is reset to a non-zero
664  // value, which results in issues like sound sources deleted while
665  // their volume is zero coming back to life and escaping Wesnoth's
666  // sound source management code.
667  if (Mix_Volume(ch, -1) == 0) {
668  Mix_HaltChannel(ch);
669  } else {
670  Mix_FadeOutChannel(ch, 100);
671  }
672  } else {
673  Mix_SetDistance(ch, distance);
674  }
675  }
676 }
677 
678 bool is_sound_playing(int id)
679 {
680  audio_lock lock;
681  return std::find(channel_ids.begin(), channel_ids.end(), id) != channel_ids.end();
682 }
683 
684 void stop_sound(int id)
685 {
687 }
688 
689 void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
690 {
691  if(preferences::sound_on()) {
692  play_sound_internal(files, SOUND_SOURCES, repeats, distance, id);
693  }
694 }
695 
697 
698 static Mix_Chunk* load_chunk(const std::string& file, channel_group group)
699 {
700  sound_cache_iterator it_bgn, it_end;
701  sound_cache_iterator it;
702 
703  sound_cache_chunk temp_chunk(file); // search the sound cache on this key
704  it_bgn = sound_cache.begin(), it_end = sound_cache.end();
705  it = std::find(it_bgn, it_end, temp_chunk);
706 
707  if (it != it_end) {
708  if(it->group != group) {
709  // cached item has been used in multiple sound groups
710  it->group = NULL_CHANNEL;
711  }
712 
713  //splice the most recently used chunk to the front of the cache
714  sound_cache.splice(it_bgn, sound_cache, it);
715  } else {
716  // remove the least recently used chunk from cache if it's full
717  bool cache_full = (sound_cache.size() == max_cached_chunks);
718  while( cache_full && it != it_bgn ) {
719  // make sure this chunk is not being played before freeing it
721  if(std::find(channel_chunks.begin(), ch_end, (--it)->get_data()) == ch_end) {
722  sound_cache.erase(it);
723  cache_full = false;
724  }
725  }
726  if(cache_full) {
727  LOG_AUDIO << "Maximum sound cache size reached and all are busy, skipping.\n";
728  throw chunk_load_exception();
729  }
730  temp_chunk.group = group;
731  std::string const &filename = filesystem::get_binary_file_location("sounds", file);
732 
733  if (!filename.empty()) {
734  SDL_RWops *rwops = filesystem::load_RWops(filename);
735  temp_chunk.set_data(Mix_LoadWAV_RW(rwops, true)); // SDL takes ownership of rwops
736  } else {
737  ERR_AUDIO << "Could not load sound file '" << file << "'." << std::endl;
738  throw chunk_load_exception();
739  }
740 
741  if (temp_chunk.get_data() == nullptr) {
742  ERR_AUDIO << "Could not load sound file '" << filename << "': "
743  << Mix_GetError() << "\n";
744  throw chunk_load_exception();
745  }
746 
747  sound_cache.push_front(temp_chunk);
748  }
749 
750  return sound_cache.begin()->get_data();
751 }
752 
753 void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats,
754  unsigned int distance, int id, int loop_ticks, int fadein_ticks)
755 {
756  if(files.empty() || distance >= DISTANCE_SILENT || !mix_ok) {
757  return;
758  }
759 
760  audio_lock lock;
761 
762  // find a free channel in the desired group
763  int channel = Mix_GroupAvailable(group);
764  if(channel == -1) {
765  LOG_AUDIO << "All channels dedicated to sound group(" << group << ") are busy, skipping.\n";
766  return;
767  }
768 
769  Mix_Chunk *chunk;
770  std::string file = pick_one(files);
771 
772  try {
773  chunk = load_chunk(file, group);
774  assert(chunk);
775  } catch(const chunk_load_exception&) {
776  return;
777  }
778 
779  /*
780  * This check prevents SDL_Mixer from blowing up on Windows when UI sound is played
781  * in response to toggling the checkbox which disables sound.
782  */
783  if(group != SOUND_UI) {
784  Mix_SetDistance(channel, distance);
785  }
786 
787  int res;
788  if(loop_ticks > 0) {
789  if(fadein_ticks > 0) {
790  res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
791  } else {
792  res = Mix_PlayChannel(channel, chunk, -1);
793  }
794 
795  if(res >= 0) {
796  Mix_ExpireChannel(channel, loop_ticks);
797  }
798  } else {
799  if(fadein_ticks > 0) {
800  res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
801  } else {
802  res = Mix_PlayChannel(channel, chunk, repeats);
803  }
804  }
805 
806  if(res < 0) {
807  ERR_AUDIO << "error playing sound effect: " << Mix_GetError() << std::endl;
808  //still keep it in the sound cache, in case we want to try again later
809  return;
810  }
811 
813 
814  //reserve the channel's chunk from being freed, since it is playing
815  channel_chunks[res] = chunk;
816 }
817 
818 void play_sound(const std::string& files, channel_group group, unsigned int repeats)
819 {
820  if(preferences::sound_on()) {
821  play_sound_internal(files, group, repeats);
822  }
823 }
824 
825 // Play bell with separate volume setting
826 void play_bell(const std::string& files)
827 {
828  if (preferences::turn_bell()) {
830  }
831 }
832 
833 // Play timer with separate volume setting
834 void play_timer(const std::string& files, int loop_ticks, int fadein_ticks)
835 {
836  if(preferences::sound_on()) {
837  play_sound_internal(files, SOUND_TIMER, 0, 0, -1, loop_ticks, fadein_ticks);
838  }
839 }
840 
841 // Play UI sounds on separate volume than soundfx
842 void play_UI_sound(const std::string& files)
843 {
846  }
847 }
848 
849 void set_music_volume(int vol)
850 {
851  if(mix_ok && vol >= 0) {
852  if(vol > MIX_MAX_VOLUME)
853  vol = MIX_MAX_VOLUME;
854 
855  Mix_VolumeMusic(vol);
856  }
857 }
858 
859 void set_sound_volume(int vol)
860 {
861  if(mix_ok && vol >= 0) {
862  if(vol > MIX_MAX_VOLUME)
863  vol = MIX_MAX_VOLUME;
864 
865  // Bell, timer and UI have separate channels which we can't set up from this
866  for (unsigned i = 0; i < n_of_channels; ++i){
867  if(!(i >= UI_sound_channel_start && i <= UI_sound_channel_last)
868  && i != bell_channel && i != timer_channel)
869  {
870  Mix_Volume(i, vol);
871  }
872  }
873  }
874 }
875 
876 /*
877  * For the purpose of volume setting, we treat turn timer the same as bell
878  */
879 void set_bell_volume(int vol)
880 {
881  if(mix_ok && vol >= 0) {
882  if(vol > MIX_MAX_VOLUME)
883  vol = MIX_MAX_VOLUME;
884 
885  Mix_Volume(bell_channel, vol);
886  Mix_Volume(timer_channel, vol);
887  }
888 }
889 
890 void set_UI_volume(int vol)
891 {
892  if(mix_ok && vol >= 0) {
893  if(vol > MIX_MAX_VOLUME)
894  vol = MIX_MAX_VOLUME;
895 
896  for (unsigned i = UI_sound_channel_start; i <= UI_sound_channel_last; ++i) {
897  Mix_Volume(i, vol);
898  }
899  }
900 }
901 
902 } // end of sound namespace
void empty_playlist()
Definition: sound.cpp:480
void close_sound()
Definition: sound.cpp:362
int bell_volume()
bool turn_bell()
bool is_sound_playing(int id)
Definition: sound.cpp:678
void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
Definition: sound.cpp:689
void stop_music()
Definition: sound.cpp:411
channel_group
Definition: sound.hpp:25
logger & info()
Definition: log.cpp:91
void set_UI_volume(int vol)
Definition: sound.cpp:890
int ticks(unsigned *refresh_counter=nullptr, unsigned refresh_rate=1)
Definition: events.cpp:640
static void play_new_music()
Definition: sound.cpp:493
void stop_sound()
Definition: sound.cpp:422
unsigned int sample_rate()
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...
Audio output for sound and music.
Definition: sound.cpp:43
void play_music_once(const std::string &file)
Definition: sound.cpp:472
#define d
static lg::log_domain log_audio("audio")
bool operator!=(const config &a, const config &b)
Definition: config.hpp:79
void write_music_play_list(config &snapshot)
Definition: sound.cpp:644
Definitions for the interface to Wesnoth Markup Language (WML).
bool sound_on()
void reposition_sound(int id, unsigned int distance)
Definition: sound.cpp:654
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
Definition: sound.cpp:818
bool init_sound()
Definition: sound.cpp:314
static bool track_ok(const std::string &id)
Definition: sound.cpp:174
#define DBG_AUDIO
Definition: sound.cpp:34
void reset_sound()
Definition: sound.cpp:389
#define LOG_AUDIO
Definition: sound.cpp:35
void set_bell_volume(int vol)
Definition: sound.cpp:879
static void channel_finished_hook(int channel)
Definition: sound.cpp:308
GLuint id
Definition: glew.h:1647
std::vector< ttip > shuffle(const std::vector< ttip > &tips)
Shuffles the tips.
Definition: tips.cpp:49
static std::string pick_one(const std::string &files)
Definition: sound.cpp:256
void stop_UI_sound()
Definition: sound.cpp:457
void play_music()
Definition: sound.cpp:485
void play_music_config(const config &music_node)
Definition: sound.cpp:552
static Mix_Chunk * load_chunk(const std::string &file, channel_group group)
Definition: sound.cpp:698
bool UI_sound_on()
Templates and utility-routines for strings and numbers.
void set_sound_volume(int vol)
Definition: sound.cpp:859
const std::string & file_path() const
void set_music_volume(int vol)
Definition: sound.cpp:849
GLuint res
Definition: glew.h:9258
size_t sound_buffer_size()
std::map< std::string, tfilter >::iterator itor
Definition: filter.cpp:199
static std::vector< Mix_Chunk * > channel_chunks
Definition: sound.cpp:45
GLint GLint GLsizei GLsizei GLsizei GLint GLenum format
Definition: glew.h:1222
void process(events::pump_info &info)
Definition: sound.cpp:598
bool operator==(const config &a, const config &b)
Definition: config.cpp:1527
Internal representation of music tracks.
bool music_on()
#define ERR_AUDIO
Definition: sound.cpp:36
size_t i
Definition: function.cpp:1057
Declarations for File-IO.
void stop_bell()
Definition: sound.cpp:441
static const sound::music_track & choose_track()
Definition: sound.cpp:231
static void increment_chunk_usage(Mix_Chunk *mcp)
Definition: sound.cpp:93
void play_bell(const std::string &files)
Definition: sound.cpp:826
int sound_volume()
static std::vector< int > channel_ids
Definition: sound.cpp:49
GLboolean GLuint group
Definition: glew.h:2589
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:753
const GLdouble * m
Definition: glew.h:6968
void play_timer(const std::string &files, int loop_ticks, int fadein_ticks)
Definition: sound.cpp:834
bool find(E event, F functor)
Tests whether an event handler is available.
void play_music_repeatedly(const std::string &id)
Definition: sound.cpp:536
#define DISTANCE_SILENT
Definition: sound.hpp:59
void play_UI_sound(const std::string &files)
Definition: sound.cpp:842
Standard logging facilities (interface).
void commit_music_changes()
Definition: sound.cpp:621
SDL_RWops * load_RWops(const std::string &path)
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
const GLuint * ids
Definition: glew.h:1652
static void decrement_chunk_usage(Mix_Chunk *mcp)
Definition: sound.cpp:97
std::vector< std::string > square_parenthetical_split(std::string const &val, const char separator, std::string const &left, std::string const &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
GLsizei const GLcharARB ** string
Definition: glew.h:4503
int music_volume()
const std::string & id() const
GLclampf f
Definition: glew.h:3024
channel
Definition: utils.hpp:250