Added reload of levels on F7 (Update levelpack) to ease the test of changes.
[enigmagame.git] / src / SoundEngine.cc
blobc8bd3cf2bedf61b341ad8ae36296fad2b784a3fb
1 /*
2 * Copyright (C) 2002,2003,2004 Daniel Heck
3 * Copyright (C) 2007,2008 Andreas Lochmann
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "errors.hh"
21 #include "options.hh"
22 #include "SoundEngine.hh"
23 #include "main.hh"
25 #include "SDL_mixer.h"
26 #include "SDL_mutex.h"
28 #include <string>
29 #include <cassert>
31 using namespace enigma;
32 using namespace sound;
34 /* -------------------- Local variables -------------------- */
35 namespace
37 std::auto_ptr<SoundEngine> sound_engine;
39 bool sound_enabled = true;
40 bool music_enabled = true;
41 bool sound_enabled_temp = false;
42 bool sound_mute = false;
43 bool music_mute = false;
46 /* -------------------- Interface Functions -------------------- */
48 void sound::Init(bool withMusic, bool withSound)
50 sound_enabled = withSound;
51 music_enabled = withMusic;
52 if (!sound_engine.get()) {
53 sound_engine.reset(new SoundEngine_SDL);
56 if (sound_engine->init()) {
57 sound::UpdateVolume();
58 } else {
59 sound_enabled = false;
60 music_enabled = false;
61 sound_engine.reset(new SoundEngine_Null);
65 void sound::Shutdown()
67 if (sound_engine.get())
68 sound_engine->shutdown();
71 void sound::Tick (double dtime)
73 sound_engine->tick (dtime);
74 sound::MusicTick(dtime);
77 void sound::TempDisableSound() {
78 sound_enabled_temp = sound_enabled;
79 sound_enabled = false;
82 void sound::TempReEnableSound() {
83 sound_enabled = sound_enabled_temp;
86 bool sound::IsSoundMute() {
87 return !sound_enabled || sound_mute;
90 bool sound::IsMusicMute() {
91 return !sound_enabled || !music_enabled || music_mute;
94 void sound::DefineSound (const SoundName &name, const SoundData &data)
96 sound_engine->define_sound (name, data);
99 void sound::SetListenerPosition (const ecl::V2 &pos)
101 sound_engine->set_listenerpos (pos);
104 bool sound::PlaySound (const SoundName &name, const ecl::V2 &pos, double volume, int priority)
106 if (sound::IsSoundMute())
107 return false;
109 SoundEvent se;
110 se.name = name;
111 se.has_position = true;
112 se.position = pos;
113 se.priority = priority;
114 se.volume = volume * options::GetDouble("SoundVolume");
115 se.left = se.right = 0;
117 return sound_engine->play_sound (se);
120 bool sound::PlaySoundGlobal (const SoundName &name, double volume, int priority)
122 if (sound::IsSoundMute())
123 return false;
125 SoundEvent se;
126 se.name = name;
127 se.has_position = false;
128 se.position = ecl::V2();
129 se.priority = priority;
130 se.volume = volume * options::GetDouble("SoundVolume");
131 se.left = 255;
132 se.right = 255;
134 return sound_engine->play_sound (se);
137 void sound::ClearCache()
139 sound_engine->clear_cache();
142 void sound::CacheSound(const SoundEffect &s)
144 sound_engine->cache_sound(s);
147 void sound::FadeoutMusic(bool blocking)
149 sound_engine->fadeout_music(blocking);
152 bool sound::PlayMusic (const std::string &name, double position)
154 if(sound::IsMusicMute() || name=="")
155 return false;
157 std::string fname;
158 return app.resourceFS->findFile (name, fname) && sound_engine->play_music(fname, position);
161 void sound::StopMusic() {
162 sound_engine->stop_music();
165 bool sound::IsMusicPlaying() {
166 return sound_engine->is_music_playing();
169 void sound::SetSoundVolume (double vol)
171 sound_engine->set_sound_volume (vol);
172 sound_mute = (vol == 0.0);
175 void sound::SetMusicVolume (double vol)
177 sound_engine->set_music_volume (vol);
178 music_mute = (vol == 0.0);
181 void sound::UpdateVolume()
183 sound::SetSoundVolume(options::GetDouble("SoundVolume"));
184 sound::SetMusicVolume(options::GetDouble("MusicVolume"));
187 /* -------------------- SoundEngine_SDL implementation -------------------- */
189 SoundEngine::SoundEngine()
192 class MutexLock {
193 public:
194 MutexLock (SDL_mutex *m) {
195 mutex = m;
196 SDL_mutexP (mutex);
198 ~MutexLock () {
199 SDL_mutexV (mutex);
201 private:
202 SDL_mutex *mutex;
205 SoundEngine_SDL *SoundEngine_SDL::m_instance = 0;
207 SoundEngine_SDL::SoundEngine_SDL()
208 : m_initialized(false),
209 m_soundvolume (MIX_MAX_VOLUME),
210 m_musicvolume (MIX_MAX_VOLUME),
211 m_current_music (0),
212 m_freq (MIX_DEFAULT_FREQUENCY),
213 m_format (MIX_DEFAULT_FORMAT),
214 m_channels (MIX_DEFAULT_CHANNELS)
216 assert (m_instance == 0);
217 m_instance = this;
220 SoundEngine_SDL::~SoundEngine_SDL()
222 shutdown();
223 m_instance = 0;
226 bool SoundEngine_SDL::init()
228 if (!m_initialized) {
229 // Initialize SDL audio subsystem
230 if (SDL_InitSubSystem (SDL_INIT_AUDIO) == -1) {
231 fprintf(stderr, "Couldn't open SDL audio subsystem: %s\n", SDL_GetError());
232 return false;
235 const SDL_version* vi = Mix_Linked_Version();
236 Log << ecl::strf("SDL_mixer Version: %u.%u.%u\n", vi->major, vi->minor, vi->patch);
237 #ifdef SDL_MIX_INIT
238 int mix_flags = MIX_INIT_OGG | MIX_INIT_MOD;
239 if (Mix_Init(mix_flags) & mix_flags != mix_flags) {
240 Log << ecl::strf( "Couldn't initialize SDL_mixer: %s\n", Mix_GetError());
241 return false;
243 #endif
245 // Initialize SDL_mixer lib
246 if (Mix_OpenAudio(m_freq, m_format, m_channels, 1024) < 0) {
247 fprintf(stderr, "Couldn't open mixer: %s\n", Mix_GetError());
248 return false;
251 // Update number of available channels
252 m_channels = Mix_GroupCount (-1);
253 m_channelinfo.resize (m_channels);
255 Mix_ChannelFinished (&channel_finished);
256 m_mutex = SDL_CreateMutex();
257 if (!m_mutex)
258 return false;
260 m_initialized = true;
262 return true;
265 void SoundEngine_SDL::shutdown()
267 if (m_initialized) {
268 Mix_FreeMusic(m_current_music);
269 Mix_CloseAudio();
270 clear_cache();
271 SDL_DestroyMutex (m_mutex);
272 #ifdef SDL_MIX_INIT
273 Mix_Quit();
274 #endif
275 m_initialized = false;
279 void SoundEngine_SDL::clear_cache()
281 for (ecl::Dict<Mix_Chunk*>::iterator it = wav_cache.begin(); it != wav_cache.end(); ++it)
282 Mix_FreeChunk(it->second);
283 wav_cache.clear();
286 void SoundEngine_SDL::set_sound_volume (double soundvol)
288 if (!m_initialized)
289 return; // SDL_mixer crashes without this check
291 m_soundvolume = ecl::round_down<int>(ecl::Clamp(soundvol, 0.0, 1.0) * MIX_MAX_VOLUME);
292 Mix_Volume (-1, m_soundvolume);
295 void SoundEngine_SDL::set_music_volume (double musicvol)
297 if (!m_initialized)
298 return; // SDL_mixer crashes without this check
300 m_musicvolume = ecl::round_down<int>(ecl::Clamp(musicvol, 0.0, 1.0) * MIX_MAX_VOLUME);
301 Mix_VolumeMusic (m_musicvolume);
302 //Mix_SetPanning(MIX_CHANNEL_POST, m_musicvolume, m_musicvolume);
306 void SoundEngine_SDL::stop_music()
308 Mix_HaltMusic();
309 Mix_FreeMusic(m_current_music);
310 m_current_music = 0;
313 bool SoundEngine_SDL::play_music (const std::string &filename, double position)
315 if (Mix_Music *music = Mix_LoadMUS(filename.c_str())) {
316 if (m_current_music)
317 Mix_FreeMusic (m_current_music);
318 m_current_music = music;
319 if(Mix_PlayMusic (m_current_music, 1) == -1)
320 Log << "Mix_PlayMusic: " << Mix_GetError() << "\n";
321 if(position > 0) {
322 Log << "Start music at position " << position << "\n";
323 if(Mix_SetMusicPosition(position) == -1)
324 Log << "Mix_SetMusicPosition: " << Mix_GetError() << "\n";
326 Mix_VolumeMusic (m_musicvolume);
327 //Mix_SetPanning(MIX_CHANNEL_POST, m_musicvolume, m_musicvolume);
328 return true;
330 return false;
333 void SoundEngine_SDL::fadeout_music(bool blocking)
335 while (Mix_FadingMusic() != MIX_NO_FADING) {
336 if (!blocking)
337 return;
338 SDL_Delay(10);
341 if (Mix_PlayingMusic()) {
342 Mix_FadeOutMusic(500);
343 if (!blocking)
344 return;
345 SDL_Delay(400);
347 while (Mix_PlayingMusic())
348 SDL_Delay(10);
351 void SoundEngine_SDL::update_channel (int channel)
353 SoundEvent &se = m_channelinfo[channel];
355 double volume;
356 int left;
357 int right;
358 if (se.has_position) {
359 ecl::V2 distv = se.position - m_listenerpos;
360 int xdist = int(distv[0] * options::GetDouble("StereoSeparation"));
361 left = ecl::Clamp (255 - xdist, 0, 255);
362 right = ecl::Clamp (255 + xdist, 0, 255);
363 volume = se.effectiveVolume(length(distv));
365 else
367 volume = se.volume;
368 left = se.left;
369 right = se.right;
372 Mix_SetPanning (channel, left, right);
374 int mixvol = ecl::round_down<int>(volume * MIX_MAX_VOLUME);
375 Mix_Volume(channel, ecl::Clamp(mixvol, 0, MIX_MAX_VOLUME));
378 int SoundEngine_SDL::already_playing (const SoundEvent &s)
380 for (size_t i=0; i<m_channelinfo.size(); ++i) {
381 const SoundEvent &se = m_channelinfo[i];
383 if (se.active && se.name == s.name && se.playing_time < 0.05
384 && (!se.has_position || !s.has_position ||
385 ecl::length(se.position - s.position) < se.range + se.fullvol_range))
386 return static_cast<int> (i);
388 return -1;
391 Mix_Chunk *SoundEngine_SDL::cache_sound(const std::string &name)
393 ecl::Dict<Mix_Chunk*>::iterator i=wav_cache.find(name);
394 if (i == wav_cache.end()) {
395 Mix_Chunk *ch = 0;
396 std::string filename;
397 if (app.resourceFS->findFile("soundsets/" + name + ".wav", filename))
398 ch = Mix_LoadWAV(filename.c_str());
399 else
400 // Sounds from other resources shoudl return correct error:
401 Mix_SetError("Sound not found in resources.");
402 if (ch != 0)
403 wav_cache.insert(name, ch);
404 else
405 enigma::Log << "Couldn't load sample '" << name << "': "
406 << Mix_GetError() << std::endl;
407 return ch;
408 } else
409 return i->second;
412 void SoundEngine_SDL::cache_sound(const SoundEffect &s)
414 std::string filename = s.getFilename();
415 if (filename != "")
416 cache_sound(filename);
419 bool SoundEngine_SDL::play_sound (const SoundEvent &s)
421 int channel = already_playing (s);
422 if (channel != -1) {
423 MutexLock (m_instance->m_mutex);
424 SoundEvent &se = m_channelinfo [channel];
425 if(se.merge(s)) {
426 update_channel(channel);
427 return true;
431 if (Mix_Chunk *chunk = cache_sound(s.name)) {
432 channel = -1; //Mix_GroupOldest(-1);
434 channel = Mix_PlayChannel(channel, chunk, 0);
436 if (channel != -1) {
438 MutexLock (m_instance->m_mutex);
439 SoundEvent &se = m_channelinfo[channel];
440 se = s;
441 se.active = true;
442 se.playing_time = 0.0;
444 update_channel (channel);
446 return true; // even if no free channel was found
447 } else
448 return false;
451 bool SoundEngine_SDL::is_music_playing() {
452 return Mix_PlayingMusic() || Mix_PausedMusic();
455 void SoundEngine_SDL::tick (double dtime)
457 MutexLock (m_instance->m_mutex);
458 for (size_t i=0; i<m_channelinfo.size(); ++i) {
459 SoundEvent &se = m_channelinfo[i];
460 if (se.active)
461 se.playing_time += dtime;
465 void SoundEngine_SDL::define_sound (
466 const SoundName &name,
467 const SoundData &data)
469 Uint32 bufsize = static_cast<Uint32> (data.buf.size());
470 Mix_Chunk *ch= ChunkFromRaw (&data.buf[0], bufsize,
471 data.freq, AUDIO_S8, data.nchannels);
472 if (ch != 0)
473 wav_cache.insert(name, ch);
476 void SoundEngine_SDL::channel_finished (int channel)
478 MutexLock (m_instance->m_mutex);
479 SoundEvent &se = m_instance->m_channelinfo[channel];
480 se.active = false;
483 /*! SDL_ConvertAudio is only capable of changing the sound frequency by
484 integer powers of 2 (i.e., by a factor of ... 1/4 1/2 1 2 ...).
485 The sound files used by Oxyd are sampled at 6kHz which we must
486 convert to roughly 22kHz. This function resamples between any two
487 frequencies using simple linear interpolation. It is not capable
488 of changing the sample format or dealing with more than one
489 channel.
491 FIXME: We should apply a lowpass filter after reampling to get rid
492 of the artifacts introduced by linear interpolation or use a better
493 interpolator. */
494 Sint8* SoundEngine_SDL::resample (const Sint8 *data, Uint32 len, int oldfreq,
495 int newfreq, Uint32 *newlen_)
497 assert (data);
498 assert (len>0);
499 assert (oldfreq > 0);
500 assert (newfreq > 0);
501 const int sample_size = 1; // 8bit sample data
503 float ratio = float(oldfreq) / float(newfreq);
504 Uint32 newlen = ecl::round_down<int> (len / ratio);
505 *newlen_ = newlen;
506 Sint8 *newdata = (Sint8*) malloc (sample_size * newlen);
507 if (!newdata)
508 return 0;
510 const Sint8 *src = data;
511 Sint8 *dst = newdata;
513 float srcinc = float (len-1) / float (newlen);
514 for (unsigned i=0; i<newlen; ++i) {
515 int srcidx = ecl::round_down <int> (i * srcinc);
516 float a2 = i*srcinc - srcidx;
517 float a1 = 1.0f - a2;
518 dst[i] = static_cast<Sint8> ((a1*src[srcidx] + a2*src[srcidx+1])/2);
520 return newdata;
523 Mix_Chunk* SoundEngine_SDL::ChunkFromRaw (const Uint8 *buf, Uint32 len,
524 int sfreq, int sformat, int schannels)
526 if (sound::IsSoundMute() || !buf)
527 return 0;
529 // Get destination format
530 int dfreq, dchannels;
531 Uint16 dformat;
532 Mix_QuerySpec (&dfreq, &dformat, &dchannels);
534 // Resample
535 Uint32 newlen=0;
536 Uint8 *newbuf = (Uint8*)resample((const Sint8*)buf, len, sfreq, dfreq, &newlen);
538 // Convert audio data
539 SDL_AudioCVT cvt;
540 if (!SDL_BuildAudioCVT (&cvt, sformat, schannels, dfreq,
541 dformat, dchannels, dfreq))
542 return 0; // memory leak!
544 cvt.buf = (Uint8*) malloc(newlen * cvt.len_mult);
545 cvt.len = newlen;
546 memcpy(cvt.buf, newbuf, newlen);
547 free(newbuf);
549 SDL_ConvertAudio(&cvt);
551 Mix_Chunk *chunk = Mix_QuickLoad_RAW(cvt.buf, cvt.len_cvt);
552 chunk->allocated = 1;
553 return chunk;