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.
22 #include "SoundEngine.hh"
25 #include "SDL_mixer.h"
26 #include "SDL_mutex.h"
31 using namespace enigma
;
32 using namespace sound
;
34 /* -------------------- Local variables -------------------- */
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();
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())
111 se
.has_position
= true;
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())
127 se
.has_position
= false;
128 se
.position
= ecl::V2();
129 se
.priority
= priority
;
130 se
.volume
= volume
* options::GetDouble("SoundVolume");
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
=="")
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()
194 MutexLock (SDL_mutex
*m
) {
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
),
212 m_freq (MIX_DEFAULT_FREQUENCY
),
213 m_format (MIX_DEFAULT_FORMAT
),
214 m_channels (MIX_DEFAULT_CHANNELS
)
216 assert (m_instance
== 0);
220 SoundEngine_SDL::~SoundEngine_SDL()
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());
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
);
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());
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());
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();
260 m_initialized
= true;
265 void SoundEngine_SDL::shutdown()
268 Mix_FreeMusic(m_current_music
);
271 SDL_DestroyMutex (m_mutex
);
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
);
286 void SoundEngine_SDL::set_sound_volume (double soundvol
)
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
)
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()
309 Mix_FreeMusic(m_current_music
);
313 bool SoundEngine_SDL::play_music (const std::string
&filename
, double position
)
315 if (Mix_Music
*music
= Mix_LoadMUS(filename
.c_str())) {
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";
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);
333 void SoundEngine_SDL::fadeout_music(bool blocking
)
335 while (Mix_FadingMusic() != MIX_NO_FADING
) {
341 if (Mix_PlayingMusic()) {
342 Mix_FadeOutMusic(500);
347 while (Mix_PlayingMusic())
351 void SoundEngine_SDL::update_channel (int channel
)
353 SoundEvent
&se
= m_channelinfo
[channel
];
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
));
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
);
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()) {
396 std::string filename
;
397 if (app
.resourceFS
->findFile("soundsets/" + name
+ ".wav", filename
))
398 ch
= Mix_LoadWAV(filename
.c_str());
400 // Sounds from other resources shoudl return correct error:
401 Mix_SetError("Sound not found in resources.");
403 wav_cache
.insert(name
, ch
);
405 enigma::Log
<< "Couldn't load sample '" << name
<< "': "
406 << Mix_GetError() << std::endl
;
412 void SoundEngine_SDL::cache_sound(const SoundEffect
&s
)
414 std::string filename
= s
.getFilename();
416 cache_sound(filename
);
419 bool SoundEngine_SDL::play_sound (const SoundEvent
&s
)
421 int channel
= already_playing (s
);
423 MutexLock (m_instance
->m_mutex
);
424 SoundEvent
&se
= m_channelinfo
[channel
];
426 update_channel(channel
);
431 if (Mix_Chunk
*chunk
= cache_sound(s
.name
)) {
432 channel
= -1; //Mix_GroupOldest(-1);
434 channel
= Mix_PlayChannel(channel
, chunk
, 0);
438 MutexLock (m_instance
->m_mutex
);
439 SoundEvent
&se
= m_channelinfo
[channel
];
442 se
.playing_time
= 0.0;
444 update_channel (channel
);
446 return true; // even if no free channel was found
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
];
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
);
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
];
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
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
494 Sint8
* SoundEngine_SDL::resample (const Sint8
*data
, Uint32 len
, int oldfreq
,
495 int newfreq
, Uint32
*newlen_
)
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
);
506 Sint8
*newdata
= (Sint8
*) malloc (sample_size
* newlen
);
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);
523 Mix_Chunk
* SoundEngine_SDL::ChunkFromRaw (const Uint8
*buf
, Uint32 len
,
524 int sfreq
, int sformat
, int schannels
)
526 if (sound::IsSoundMute() || !buf
)
529 // Get destination format
530 int dfreq
, dchannels
;
532 Mix_QuerySpec (&dfreq
, &dformat
, &dchannels
);
536 Uint8
*newbuf
= (Uint8
*)resample((const Sint8
*)buf
, len
, sfreq
, dfreq
, &newlen
);
538 // Convert audio data
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
);
546 memcpy(cvt
.buf
, newbuf
, newlen
);
549 SDL_ConvertAudio(&cvt
);
551 Mix_Chunk
*chunk
= Mix_QuickLoad_RAW(cvt
.buf
, cvt
.len_cvt
);
552 chunk
->allocated
= 1;