sound.cpp

Go to the documentation of this file.
00001 /* $Id: sound.cpp 26501 2008-05-09 22:44:58Z mordante $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 #include "global.hpp"
00016 #include "events.hpp"
00017 #include "filesystem.hpp"
00018 #include "game_config.hpp"
00019 #include "game_preferences.hpp"
00020 #include "log.hpp"
00021 #include "random.hpp"
00022 #include "sound.hpp"
00023 
00024 #include "SDL.h"
00025 #include "SDL_mixer.h"
00026 
00027 #include <cassert>
00028 #include <iostream>
00029 #include <map>
00030 #include <list>
00031 
00032 #define LOG_AUDIO LOG_STREAM(info, audio)
00033 #define ERR_AUDIO LOG_STREAM(err, audio)
00034 
00035 namespace sound {
00036 // Channel-chunk mapping lets us know, if we can safely free a given chunk
00037 std::vector<Mix_Chunk*> channel_chunks;
00038 
00039 // Channel-id mapping for use with sound sources (to check if given source
00040 // is playing on a channel for fading/panning)
00041 std::vector<int> channel_ids;
00042 
00043 static void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats=0,
00044                  unsigned int distance=0, int id=-1, int loop_ticks=0, int fadein_ticks=0);
00045 }
00046 
00047 namespace {
00048 
00049 bool mix_ok = false;
00050 int music_start_time = 0;
00051 unsigned music_refresh = 0;
00052 unsigned music_refresh_rate = 20;
00053 bool want_new_music = false;
00054 int fadingout_time=5000;
00055 bool no_fading = false;
00056 
00057 // number of allocated channels, 
00058 const size_t n_of_channels = 16;
00059 
00060 // we need 2 channels, because we it for timer as well
00061 const size_t bell_channel = 0; 
00062 const size_t timer_channel = 1;
00063 
00064 // number of channels reserved for sound sources
00065 const size_t source_channels = n_of_channels - 8;
00066 const size_t source_channel_start = timer_channel + 1;
00067 const size_t source_channel_last = source_channel_start + source_channels - 1;
00068 const size_t UI_sound_channel = source_channel_last + 1;
00069 const size_t n_reserved_channels = UI_sound_channel + 1; // sources, bell, timer and UI
00070 
00071 // Max number of sound chunks that we want to cache
00072 // Keep this above number of available channels to avoid busy-looping
00073 #ifdef LOW_MEM
00074 unsigned max_cached_chunks = 64;
00075 #else
00076 unsigned max_cached_chunks = 256;
00077 #endif
00078 
00079 std::map< Mix_Chunk*, int > chunk_usage;
00080 
00081 }
00082 
00083 static void increment_chunk_usage(Mix_Chunk* mcp) {
00084     ++(chunk_usage[mcp]);
00085 }
00086 
00087 static void decrement_chunk_usage(Mix_Chunk* mcp) {
00088     if(mcp == NULL) return;
00089     std::map< Mix_Chunk*, int >::iterator this_usage = chunk_usage.find(mcp);
00090     assert(this_usage != chunk_usage.end());
00091     if(--(this_usage->second) == 0) {
00092         Mix_FreeChunk(mcp);
00093         chunk_usage.erase(this_usage);
00094     }
00095 }
00096 
00097 namespace {
00098 
00099 class sound_cache_chunk {
00100 public:
00101     sound_cache_chunk(const std::string& f) : group(sound::NULL_CHANNEL), file(f), data_(NULL) {}
00102     sound_cache_chunk(const sound_cache_chunk& scc)
00103         : group(scc.group), file(scc.file), data_(scc.data_)
00104     {
00105         increment_chunk_usage(data_);
00106     }
00107 
00108     ~sound_cache_chunk()
00109     {
00110         decrement_chunk_usage(data_);
00111     }
00112 
00113     sound::channel_group group;
00114     std::string file;
00115 
00116     void set_data(Mix_Chunk* d) {
00117         increment_chunk_usage(d);
00118         decrement_chunk_usage(data_);
00119         data_ = d;
00120     }
00121 
00122     Mix_Chunk* get_data() const {
00123         return data_;
00124     }
00125 
00126     bool operator==(sound_cache_chunk const &scc) const {
00127         return file == scc.file;
00128     }
00129 
00130     bool operator!=(sound_cache_chunk const &scc) const { return !operator==(scc); }
00131 
00132     sound_cache_chunk& operator=(const sound_cache_chunk& scc) {
00133         file = scc.file;
00134         group = scc.group;
00135         set_data(scc.get_data());
00136         return *this;
00137     }
00138 
00139 private:
00140     Mix_Chunk* data_;
00141 };
00142 
00143 std::list< sound_cache_chunk > sound_cache;
00144 typedef std::list< sound_cache_chunk >::iterator sound_cache_iterator;
00145 std::map<std::string,Mix_Music*> music_cache;
00146 
00147 struct music_track
00148 {
00149     music_track(const std::string &tname);
00150     music_track(const std::string &tname,
00151                 const std::string &ms_before_str,
00152                 const std::string &ms_after_str);
00153     void write(config &snapshot, bool append);
00154 
00155     std::string name;
00156     unsigned int ms_before, ms_after;
00157     bool once;
00158 };
00159 
00160 std::vector<std::string> played_before;
00161 
00162 music_track::music_track(const std::string &tname)
00163     : name(tname), ms_before(0), ms_after(0), once(false)
00164 {
00165 }
00166 
00167 music_track::music_track(const std::string &tname,
00168                          const std::string &ms_before_str,
00169                          const std::string &ms_after_str)
00170     : name(tname), once(false)
00171 {
00172     if (ms_before_str.empty())
00173         ms_before = 0;
00174     else
00175         ms_before = lexical_cast<int,std::string>(ms_before_str);
00176 
00177     if (ms_after_str.empty())
00178         ms_after = 0;
00179     else
00180         ms_after = lexical_cast<int,std::string>(ms_after_str);
00181 }
00182 
00183 void music_track::write(config &snapshot, bool append)
00184 {
00185         config& m = snapshot.add_child("music");
00186         m["name"] = name;
00187         m["ms_before"] = lexical_cast<std::string>(ms_before);
00188         m["ms_after"] = lexical_cast<std::string>(ms_after);
00189         if (append)
00190             m["append"] = "yes";
00191 }
00192 
00193 std::vector<music_track> current_track_list;
00194 struct music_track current_track("");
00195 struct music_track last_track("");
00196 
00197 }
00198 
00199 static bool track_ok(const std::string &name)
00200 {
00201     LOG_AUDIO << "Considering " << name << "\n";
00202 
00203     // If they committed changes to list, we forget previous plays, but
00204     // still *never* repeat same track twice if we have an option.
00205     if (name == current_track.name)
00206         return false;
00207 
00208     if (current_track_list.size() <= 3)
00209         return true;
00210 
00211     // Timothy Pinkham says:
00212     // 1) can't be repeated without 2 other pieces have already played
00213     // since A was played.
00214     // 2) cannot play more than 2 times without every other piece
00215     // having played at least 1 time.
00216 
00217     // Dammit, if our musicians keep coming up with algorithms, I'll
00218     // be out of a job!
00219     unsigned int num_played = 0;
00220     std::set<std::string> played;
00221     std::vector<std::string>::reverse_iterator i;
00222 
00223     for (i = played_before.rbegin(); i != played_before.rend(); i++) {
00224         if (*i == name) {
00225             num_played++;
00226             if (num_played == 2)
00227                 break;
00228         } else {
00229             played.insert(*i);
00230         }
00231     }
00232 
00233     // If we've played this twice, must have played every other track.
00234     if (num_played == 2 && played.size() != current_track_list.size() - 1) {
00235         LOG_AUDIO << "Played twice with only "
00236                   << lexical_cast<std::string>(played.size())
00237                   << " tracks between\n";
00238         return false;
00239     }
00240 
00241     // Check previous previous track not same.
00242     i = played_before.rbegin();
00243     if (i != played_before.rend()) {
00244         i++;
00245         if (i != played_before.rend()) {
00246             if (*i == name) {
00247                 LOG_AUDIO << "Played just before previous\n";
00248                 return false;
00249             }
00250         }
00251     }
00252 
00253     return true;
00254 }
00255 
00256 
00257 static const music_track &choose_track()
00258 {
00259     assert(!current_track_list.empty());
00260 
00261     std::string name;
00262     unsigned int track = 0;
00263 
00264     if (current_track_list.size() > 1) {
00265         do {
00266             track = rand()%current_track_list.size();
00267         } while (!track_ok(current_track_list[track].name));
00268     }
00269 
00270     //LOG_AUDIO << "Next track will be " << current_track_list[track].name << "\n";
00271     played_before.push_back(current_track_list[track].name);
00272     return current_track_list[track];
00273 }
00274 
00275 static std::string pick_one(const std::string &files)
00276 {
00277     std::vector<std::string> names = utils::split(files);
00278 
00279     if (names.size() == 0)
00280         return "";
00281     if (names.size() == 1)
00282         return names[0];
00283 
00284 #ifdef LOW_MEM
00285     // We're memory constrained, so we shouldn't cache too many chunks
00286     return names[0];
00287 #endif
00288 
00289     // We avoid returning same choice twice if we can avoid it.
00290     static std::map<std::string,unsigned int> prev_choices;
00291     unsigned int choice;
00292 
00293     if (prev_choices.find(files) != prev_choices.end()) {
00294         choice = rand()%(names.size()-1);
00295         if (choice >= prev_choices[files])
00296             choice++;
00297         prev_choices[files] = choice;
00298     } else {
00299         choice = rand()%names.size();
00300         prev_choices.insert(std::pair<std::string,unsigned int>(files,choice));
00301     }
00302 
00303     return names[choice];
00304 }
00305 
00306 namespace {
00307 
00308 struct audio_lock
00309 {
00310     audio_lock()
00311     {
00312         SDL_LockAudio();
00313     }
00314 
00315     ~audio_lock()
00316     {
00317         SDL_UnlockAudio();
00318     }
00319 };
00320 
00321 } // end of anonymous namespace
00322 
00323 
00324 namespace sound {
00325 
00326 // Removes channel-chunk and channel-id mapping
00327 static void channel_finished_hook(int channel)
00328 {
00329     channel_chunks[channel] = NULL;
00330     channel_ids[channel] = -1;
00331 }
00332 
00333 bool init_sound() {
00334     LOG_AUDIO << "Initializing audio...\n";
00335     if(SDL_WasInit(SDL_INIT_AUDIO) == 0)
00336         if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
00337             return false;
00338 
00339     if(!mix_ok) {
00340         if(Mix_OpenAudio(preferences::sample_rate(), MIX_DEFAULT_FORMAT, 2, preferences::sound_buffer_size()) == -1) {
00341             mix_ok = false;
00342             ERR_AUDIO << "Could not initialize audio: " << Mix_GetError() << "\n";
00343             return false;
00344         }
00345 
00346         mix_ok = true;
00347         Mix_AllocateChannels(n_of_channels);
00348         Mix_ReserveChannels(n_reserved_channels);
00349 
00350         channel_chunks.clear();
00351         channel_chunks.resize(n_of_channels, NULL);
00352         channel_ids.resize(n_of_channels, -1);
00353 
00354         Mix_GroupChannel(bell_channel, SOUND_BELL);
00355         Mix_GroupChannel(timer_channel, SOUND_TIMER);
00356         Mix_GroupChannels(source_channel_start, source_channel_last, SOUND_SOURCES);
00357         Mix_GroupChannel(UI_sound_channel, SOUND_UI);
00358         Mix_GroupChannels(n_reserved_channels, n_of_channels - 1, SOUND_FX);
00359 
00360         set_sound_volume(preferences::sound_volume());
00361         set_UI_volume(preferences::UI_volume());
00362         set_music_volume(preferences::music_volume());
00363         set_bell_volume(preferences::bell_volume());
00364 
00365         Mix_ChannelFinished(channel_finished_hook);
00366 
00367         LOG_AUDIO << "Audio initialized.\n";
00368 
00369         play_music();
00370     }
00371     return true;
00372 }
00373 
00374 void close_sound() {
00375     int numtimesopened, frequency, channels;
00376     Uint16 format;
00377 
00378     if(mix_ok) {
00379         stop_bell();
00380         stop_UI_sound();
00381         stop_sound();
00382         sound_cache.clear();
00383         stop_music();
00384         mix_ok = false;
00385 
00386         numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
00387         if(numtimesopened == 0) {
00388             ERR_AUDIO << "Error closing audio device: " << Mix_GetError() << "\n";
00389         }
00390         while (numtimesopened) {
00391             Mix_CloseAudio();
00392             --numtimesopened;
00393         }
00394     }
00395     if(SDL_WasInit(SDL_INIT_AUDIO) != 0)
00396         SDL_QuitSubSystem(SDL_INIT_AUDIO);
00397 
00398     LOG_AUDIO << "Audio device released.\n";
00399 }
00400 
00401 void reset_sound() {
00402     bool music = preferences::music_on();
00403     bool sound = preferences::sound_on();
00404     bool UI_sound = preferences::UI_sound_on();
00405     bool bell = preferences::turn_bell();
00406 
00407     if (music || sound || bell || UI_sound) {
00408         sound::close_sound();
00409         if (!sound::init_sound()) {
00410             ERR_AUDIO << "Error initializing audio device: " << Mix_GetError() << "\n";
00411         }
00412         if (!music)
00413             sound::stop_music();
00414         if (!sound)
00415             sound::stop_sound();
00416         if (!UI_sound)
00417             sound::stop_UI_sound();
00418         if (!bell)
00419             sound::stop_bell();
00420     }
00421 }
00422 
00423 void stop_music() {
00424     if(mix_ok) {
00425         Mix_HaltMusic();
00426 
00427         std::map<std::string,Mix_Music*>::iterator i;
00428         for(i = music_cache.begin(); i != music_cache.end(); ++i)
00429             Mix_FreeMusic(i->second);
00430         music_cache.clear();
00431     }
00432 }
00433 
00434 void stop_sound() {
00435     if(mix_ok) {
00436         Mix_HaltGroup(SOUND_SOURCES);
00437         Mix_HaltGroup(SOUND_FX);
00438         sound_cache_iterator itor = sound_cache.begin();
00439         while(itor != sound_cache.end())
00440         {
00441             if(itor->group == SOUND_SOURCES || itor->group == SOUND_FX) {
00442                 itor = sound_cache.erase(itor);
00443             } else {
00444                 ++itor;
00445             }
00446         }
00447     }
00448 }
00449 
00450 /*
00451  * For the purpose of channel manipulation, we treat turn timer the same as bell
00452  */
00453 void stop_bell() {
00454     if(mix_ok) {
00455         Mix_HaltGroup(SOUND_BELL);
00456         Mix_HaltGroup(SOUND_TIMER);
00457         sound_cache_iterator itor = sound_cache.begin();
00458         while(itor != sound_cache.end())
00459         {
00460             if(itor->group == SOUND_BELL || itor->group == SOUND_TIMER) {
00461                 itor = sound_cache.erase(itor);
00462             } else {
00463                 ++itor;
00464             }
00465         }
00466     }
00467 }
00468 
00469 void stop_UI_sound() {
00470     if(mix_ok) {
00471         Mix_HaltGroup(SOUND_UI);
00472         sound_cache_iterator itor = sound_cache.begin();
00473         while(itor != sound_cache.end())
00474         {
00475             if(itor->group == SOUND_UI) {
00476                 itor = sound_cache.erase(itor);
00477             } else {
00478                 ++itor;
00479             }
00480         }
00481     }
00482 }
00483 
00484 void play_music_once(const std::string &file)
00485 {
00486     // Clear list so it's not replayed.
00487     current_track_list = std::vector<music_track>();
00488     current_track = music_track(file);
00489     play_music();
00490 }
00491 
00492 void play_no_music()
00493 {
00494     current_track_list = std::vector<music_track>();
00495     current_track = music_track("");
00496 
00497     if (preferences::music_on() && mix_ok && Mix_PlayingMusic()) {
00498         Mix_FadeOutMusic(500);
00499     }
00500 }
00501 
00502 void play_music()
00503 {
00504     music_start_time = 1; //immediate (same as effect as SDL_GetTicks())
00505     want_new_music=true;
00506     no_fading=false;
00507     fadingout_time=current_track.ms_after;
00508 }
00509 
00510 static void play_new_music()
00511 {
00512     music_start_time = 0; //reset status: no start time
00513     want_new_music = true;
00514 
00515     if(!preferences::music_on() || !mix_ok || current_track.name.empty())
00516         return;
00517 
00518     std::map<std::string,Mix_Music*>::const_iterator itor = music_cache.find(current_track.name);
00519     if(itor == music_cache.end()) {
00520         const std::string& filename = get_binary_file_location("music", current_track.name);
00521 
00522         if(filename.empty()) {
00523             ERR_AUDIO << "Could not open track '" << current_track.name << "'\n";
00524             return;
00525         }
00526 
00527         Mix_Music* const music = Mix_LoadMUS(filename.c_str());
00528         if(music == NULL) {
00529             ERR_AUDIO << "Could not load music file '" << filename << "': "
00530                       << Mix_GetError() << "\n";
00531             return;
00532         }
00533         itor = music_cache.insert(std::pair<std::string,Mix_Music*>(current_track.name,music)).first;
00534         last_track=current_track;
00535     }
00536 
00537     LOG_AUDIO << "Playing track '" << current_track.name << "'\n";
00538     int fading_time=current_track.ms_before;
00539     if(no_fading)
00540     {
00541         fading_time=0;
00542     }
00543 
00544     const int res = Mix_FadeInMusic(itor->second, 1, fading_time);
00545     if(res < 0)
00546     {
00547         ERR_AUDIO << "Could not play music: " << Mix_GetError() << " " << current_track.name <<" \n";
00548     }
00549 
00550     want_new_music=false;
00551 }
00552 
00553 void play_music_repeatedly(const std::string &name)
00554 {
00555     // Can happen if scenario doesn't specify.
00556     if (name.empty())
00557         return;
00558 
00559     current_track_list = std::vector<music_track>(1, music_track(name));
00560 
00561     // If we're already playing it, don't interrupt.
00562     if (current_track.name != name) {
00563         current_track = music_track(name);
00564         play_music();
00565     }
00566 }
00567 
00568 void play_music_config(const config &music)
00569 {
00570     struct music_track track(music["name"], music["ms_before"], music["ms_after"]);
00571 
00572     // If they say play once, we don't alter playlist.
00573     if (utils::string_bool(music["play_once"])) {
00574         current_track = track;
00575         current_track.once = true;
00576         play_music();
00577         return;
00578     }
00579 
00580     // Clear play list unless they specify append.
00581     if (!utils::string_bool(music["append"])) {
00582         current_track_list = std::vector<music_track>();
00583     }
00584 
00585     // Avoid 2 tracks with the same name, since that can cause an infinite loop
00586     // in choose_track(), 2 tracks with the same name will always return the 
00587     // current track and track_ok() doesn't allow that.
00588     std::vector<music_track>::const_iterator itor = current_track_list.begin();
00589     while(itor != current_track_list.end()) {
00590         if(itor->name == track.name) break;
00591         ++itor;
00592     }
00593     
00594     if(itor == current_track_list.end()) {
00595         current_track_list.push_back(track);
00596     } else {
00597         ERR_AUDIO << "Tried to add duplicate track '" << track.name << "'\n";
00598     }
00599 
00600     // They can tell us to start playing this list immediately.
00601     if (utils::string_bool(music["immediate"])) {
00602         current_track = track;
00603         play_music();
00604     }
00605 }
00606 
00607 void music_thinker::process(events::pump_info &info) {
00608     if(preferences::music_on()) {
00609         if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
00610             // Pick next track, add ending time to its start time.
00611             current_track = choose_track();
00612             music_start_time = info.ticks();
00613             no_fading=true;
00614             fadingout_time=0;
00615         }
00616 
00617         if(music_start_time && info.ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
00618             want_new_music=true;
00619         }
00620 
00621         if(want_new_music) {
00622             if(Mix_PlayingMusic()) {
00623                 Mix_FadeOutMusic(fadingout_time);
00624             }
00625             play_new_music();
00626         }
00627     }
00628 }
00629 
00630 void commit_music_changes()
00631 {
00632     std::vector<music_track>::iterator i;
00633 
00634     // Clear list of tracks played before.
00635     played_before = std::vector<std::string>();
00636 
00637     // Play-once is OK if still playing.
00638     if (current_track.once)
00639         return;
00640 
00641     // If current track no longer on playlist, change it.
00642     for (i = current_track_list.begin(); i != current_track_list.end(); i++) {
00643         if (current_track.name == i->name)
00644             return;
00645     }
00646 
00647     // Victory empties playlist: if next scenario doesn't specify one...
00648     if (current_track_list.empty())
00649         return;
00650 
00651     // FIXME: we don't pause ms_before on this first track.  Should we?
00652     current_track = choose_track();
00653     play_music();
00654 }
00655 
00656 void write_music_play_list(config& snapshot)
00657 {
00658     std::vector<music_track>::iterator i;
00659     bool append = false;
00660 
00661     // First entry clears playlist, others append to it.
00662     for (i = current_track_list.begin(); i != current_track_list.end(); i++) {
00663         i->write(snapshot, append);
00664         append = true;
00665     }
00666 }
00667 
00668 void reposition_sound(int id, unsigned int distance)
00669 {
00670     audio_lock lock();
00671     for(unsigned int ch = 0; ch < channel_ids.size(); ++ch) {
00672         int& ch_id = channel_ids[ch];
00673         if(ch_id == id) {
00674             if(distance >= DISTANCE_SILENT) {
00675                 Mix_FadeOutChannel(ch, 100);
00676             }
00677             else {
00678                 Mix_SetDistance(ch, distance);
00679             }
00680         }
00681     }
00682 }
00683 
00684 bool is_sound_playing(int id)
00685 {
00686     audio_lock lock();
00687     return std::find(channel_ids.begin(), channel_ids.end(), id) != channel_ids.end();
00688 }
00689 
00690 void stop_sound(int id)
00691 {
00692     reposition_sound(id, DISTANCE_SILENT);
00693 }
00694 
00695 void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
00696 {
00697     if(preferences::sound_on()) {
00698         play_sound_internal(files, SOUND_SOURCES, repeats, distance, id);
00699     }
00700 }
00701 
00702 struct chunk_load_exception { };
00703 
00704 static Mix_Chunk* load_chunk(const std::string& file, channel_group group) 
00705 {
00706     sound_cache_iterator it_bgn, it_end;
00707     sound_cache_iterator it;
00708 
00709     sound_cache_chunk temp_chunk(file); // search the sound cache on this key
00710     it_bgn = sound_cache.begin(), it_end = sound_cache.end();
00711     it = std::find(it_bgn, it_end, temp_chunk);
00712 
00713     if (it != it_end) {
00714         if(it->group != group) {
00715             // cached item has been used in multiple sound groups
00716             it->group = NULL_CHANNEL;
00717         }
00718 
00719         //splice the most recently used chunk to the front of the cache
00720         sound_cache.splice(it_bgn, sound_cache, it);
00721     } else {
00722         // remove the least recently used chunk from cache if it's full
00723         bool cache_full = (sound_cache.size() == max_cached_chunks);
00724         while( cache_full && it != it_bgn ) {
00725             // make sure this chunk is not being played before freeing it
00726             std::vector<Mix_Chunk*>::iterator ch_end = channel_chunks.end();
00727             if(std::find(channel_chunks.begin(), ch_end, (--it)->get_data()) == ch_end) {
00728                 sound_cache.erase(it);
00729                 cache_full = false;
00730             }
00731         }
00732         if(cache_full) {
00733             LOG_AUDIO << "Maximum sound cache size reached and all are busy, skipping.\n";
00734             throw chunk_load_exception();
00735         }
00736         temp_chunk.group = group;
00737         std::string const &filename = get_binary_file_location("sounds", file);
00738 
00739         if (!filename.empty()) {
00740             temp_chunk.set_data(Mix_LoadWAV(filename.c_str()));
00741         } else {
00742             ERR_AUDIO << "Could not load sound with empty filename\n";
00743             throw chunk_load_exception();
00744         }
00745 
00746         if (temp_chunk.get_data() == NULL) {
00747             ERR_AUDIO << "Could not load sound file '" << filename << "': "
00748                 << Mix_GetError() << "\n";
00749             throw chunk_load_exception();
00750         }
00751 
00752         sound_cache.push_front(temp_chunk);
00753     }
00754 
00755     return sound_cache.begin()->get_data();
00756 }
00757 
00758 void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats, 
00759             unsigned int distance, int id, int loop_ticks, int fadein_ticks)
00760 {
00761     if(files.empty() || distance >= DISTANCE_SILENT || !mix_ok) {
00762         return;
00763     }
00764 
00765     audio_lock lock();
00766 
00767     // find a free channel in the desired group
00768     int channel = Mix_GroupAvailable(group);
00769     if(channel == -1) {
00770         LOG_AUDIO << "All channels dedicated to sound group(" << group << ") are busy, skipping.\n";
00771         return;
00772     }
00773 
00774     Mix_Chunk *chunk;
00775     std::string file = pick_one(files);
00776 
00777     try {
00778         chunk = load_chunk(file, group);
00779         assert(chunk);
00780     } catch(const chunk_load_exception& e) {
00781         return;
00782     }
00783 
00784     /*
00785      * This check prevents SDL_Mixer from blowing up on Windows when UI sound is played
00786      * in response to toggling the checkbox which disables sound.
00787      */
00788     if(group != SOUND_UI) {
00789         Mix_SetDistance(channel, distance);
00790     }
00791 
00792     int res;
00793     if(loop_ticks > 0) {
00794         if(fadein_ticks > 0) {
00795             res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
00796         } else {
00797             res = Mix_PlayChannel(channel, chunk, -1);
00798         }
00799 
00800         if(res >= 0) {
00801             Mix_ExpireChannel(channel, loop_ticks);
00802         }
00803     } else {
00804         if(fadein_ticks > 0) {
00805             res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
00806         } else {
00807             res = Mix_PlayChannel(channel, chunk, repeats);
00808         }
00809     }
00810 
00811     if(res < 0) {
00812         ERR_AUDIO << "error playing sound effect: " << Mix_GetError() << "\n";
00813         //still keep it in the sound cache, in case we want to try again later
00814         return;
00815     }
00816 
00817     channel_ids[channel] = id;
00818 
00819     //reserve the channel's chunk from being freed, since it is playing
00820     channel_chunks[res] = chunk;
00821 }
00822 
00823 void play_sound(const std::string& files, channel_group group, unsigned int repeats)
00824 {
00825     if(preferences::sound_on()) {
00826         play_sound_internal(files, group, repeats);
00827     }
00828 }
00829 
00830 // Play bell with separate volume setting
00831 void play_bell(const std::string& files)
00832 {
00833     if(preferences::sound_on() && preferences::turn_bell()) {
00834         play_sound_internal(files, SOUND_BELL);
00835     }
00836 }
00837 
00838 // Play timer with separate volume setting
00839 void play_timer(const std::string& files, int loop_ticks, int fadein_ticks)
00840 {
00841     if(preferences::sound_on()) {
00842         play_sound_internal(files, SOUND_TIMER, 0, 0, -1, loop_ticks, fadein_ticks);
00843     }
00844 }
00845 
00846 // Play UI sounds on separate volume than soundfx
00847 void play_UI_sound(const std::string& files)
00848 {
00849     if(preferences::UI_sound_on()) {
00850         play_sound_internal(files, SOUND_UI);
00851     }
00852 }
00853 
00854 
00855 void set_music_volume(int vol)
00856 {
00857     if(mix_ok && vol >= 0) {
00858         if(vol > MIX_MAX_VOLUME)
00859             vol = MIX_MAX_VOLUME;
00860 
00861         Mix_VolumeMusic(vol);
00862     }
00863 }
00864 
00865 void set_sound_volume(int vol)
00866 {
00867     if(mix_ok && vol >= 0) {
00868         if(vol > MIX_MAX_VOLUME)
00869             vol = MIX_MAX_VOLUME;
00870 
00871         // Bell, timer and UI have separate channels which we can't set up from this
00872         for (unsigned i = 0; i < n_of_channels; i++){
00873             if(i != UI_sound_channel && i != bell_channel && i != timer_channel) {
00874                 Mix_Volume(i, vol);
00875             }
00876         }
00877     }
00878 }
00879 
00880 /*
00881  * For the purpose of volume setting, we treat turn timer the same as bell
00882  */
00883 void set_bell_volume(int vol)
00884 {
00885     if(mix_ok && vol >= 0) {
00886         if(vol > MIX_MAX_VOLUME)
00887             vol = MIX_MAX_VOLUME;
00888 
00889         Mix_Volume(bell_channel, vol);
00890         Mix_Volume(timer_channel, vol);
00891     }
00892 }
00893 
00894 void set_UI_volume(int vol)
00895 {
00896     if(mix_ok && vol >= 0) {
00897         if(vol > MIX_MAX_VOLUME)
00898             vol = MIX_MAX_VOLUME;
00899 
00900         Mix_Volume(UI_sound_channel, vol);
00901     }
00902 }
00903 
00904 } // end of sound namespace

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs