Files
commandergenius/project/jni/application/enigma/src/sound.cpp
2010-10-13 17:30:44 +03:00

1037 lines
30 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2002,2003,2004 Daniel Heck
* Copyright (C) 2007 Andreas Lochmann
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "errors.hh"
#include "enigma.hh"
#include "options.hh"
#include "sound.hh"
#include "main.hh"
#include "oxyd.hh"
#include "oxydlib/OxydVersion.h"
#include "nls.hh"
#include "client.hh"
#include "SDL.h"
#include "SDL_mixer.h"
#include <string>
#include <iostream>
#include <cassert>
#include <memory>
using namespace std;
using namespace enigma;
using namespace sound;
using namespace OxydLib;
#include "sound_internal.hh"
/*! This function converts raw audio data with a specified format to
the mixer's audio format. This is used for converting the original
Oxyd sounds to a format usable by Enigma. */
Mix_Chunk *ChunkFromRaw (const Uint8 *buf, Uint32 len,
int freq, int format, int channels);
/* -------------------- SoundEvent implementation -------------------- */
SoundEvent::SoundEvent ()
: name(""), has_position(false), position(),
priority (0), volume (0.0),
left (0), right(0), active (false),
playing_time (0)
{
}
bool not_active (const SoundEvent &s)
{
return !s.active;
}
bool lower_priority (const SoundEvent &s, const SoundEvent &t)
{
return s.priority < t.priority;
}
/* -------------------- SoundEngine_SDL implementation -------------------- */
class MutexLock {
public:
MutexLock (SDL_mutex *m) {
mutex = m;
SDL_mutexP (mutex);
}
~MutexLock () {
SDL_mutexV (mutex);
};
private:
SDL_mutex *mutex;
};
SoundEngine_SDL *SoundEngine_SDL::m_instance = 0;
SoundEngine_SDL::SoundEngine_SDL()
: m_initialized(false),
m_soundvolume (MIX_MAX_VOLUME),
m_musicvolume (MIX_MAX_VOLUME),
m_current_music (0),
m_freq (MIX_DEFAULT_FREQUENCY),
m_format (MIX_DEFAULT_FORMAT),
m_channels (MIX_DEFAULT_CHANNELS)
{
assert (m_instance == 0);
m_instance = this;
}
SoundEngine_SDL::~SoundEngine_SDL()
{
shutdown();
m_instance = 0;
}
bool SoundEngine_SDL::init()
{
if (!m_initialized) {
// Initialize SDL audio subsystem
if (SDL_InitSubSystem (SDL_INIT_AUDIO) == -1) {
fprintf(stderr, "Couldn't open SDL audio subsystem: %s\n", SDL_GetError());
return false;
}
const SDL_version* vi = Mix_Linked_Version();
Log << ecl::strf("SDL_mixer Version: %u.%u.%u\n", vi->major, vi->minor, vi->patch);
#ifdef SDL_MIX_INIT
int mix_flags = MIX_INIT_MOD;
if (Mix_Init(mix_flags) & mix_flags != mix_flags) {
Log << ecl::strf( "Couldn't initialize SDL_mixer: %s\n", Mix_GetError());
return false;
}
#endif
// Initialize SDL_mixer lib
if (Mix_OpenAudio(m_freq, m_format, m_channels, 1024) < 0) {
fprintf(stderr, "Couldn't open mixer: %s\n", Mix_GetError());
return false;
}
// Update number of available channels
m_channels = Mix_GroupCount (-1);
m_channelinfo.resize (m_channels);
Mix_ChannelFinished (&channel_finished);
m_mutex = SDL_CreateMutex();
if (!m_mutex)
return false;
m_initialized = true;
}
return true;
}
void SoundEngine_SDL::shutdown()
{
if (m_initialized) {
Mix_FreeMusic(m_current_music);
Mix_CloseAudio();
clear_cache();
SDL_DestroyMutex (m_mutex);
#ifdef SDL_MIX_INIT
Mix_Quit();
#endif
m_initialized = false;
}
}
void SoundEngine_SDL::clear_cache()
{
for (ecl::Dict<Mix_Chunk*>::iterator it = wav_cache.begin(); it != wav_cache.end(); ++it)
Mix_FreeChunk(it->second);
wav_cache.clear();
}
void SoundEngine_SDL::set_sound_volume (double soundvol)
{
if (!m_initialized)
return; // SDL_mixer crashes without this check
m_soundvolume = ecl::round_down<int>(ecl::Clamp(soundvol, 0.0, 1.0) * MIX_MAX_VOLUME);
Mix_Volume (-1, m_soundvolume);
}
void SoundEngine_SDL::set_music_volume (double musicvol)
{
if (!m_initialized)
return; // SDL_mixer crashes without this check
m_musicvolume = ecl::round_down<int>(ecl::Clamp(musicvol, 0.0, 1.0) * MIX_MAX_VOLUME);
Mix_VolumeMusic (m_musicvolume);
}
void SoundEngine_SDL::stop_music()
{
Mix_HaltMusic();
Mix_FreeMusic(m_current_music);
m_current_music = 0;
}
bool SoundEngine_SDL::play_music (const std::string &filename)
{
if (Mix_Music *music = Mix_LoadMUS(filename.c_str())) {
if (m_current_music)
Mix_FreeMusic (m_current_music);
m_current_music = music;
Mix_PlayMusic (m_current_music, -1);
Mix_VolumeMusic (m_musicvolume);
return true;
}
return false;
}
void SoundEngine_SDL::fadeout_music()
{
while (Mix_FadingMusic() != MIX_NO_FADING)
SDL_Delay(10);
if (Mix_PlayingMusic()) {
Mix_FadeOutMusic(500);
SDL_Delay(400);
}
while (Mix_PlayingMusic())
SDL_Delay(10);
}
void SoundEngine_SDL::update_channel (int channel)
{
// If distance sound origin <= this value, play at full volume
const double fullvol_range = 0.2;
// How far can sound travel?
const double range = 30;
SoundEvent &se = m_channelinfo[channel];
double volume;
int left;
int right;
if (se.has_position) {
ecl::V2 distv = se.position - m_listenerpos;
double dist = max(0.0, length(distv) - fullvol_range);
int xdist = int(distv[0] * options::GetDouble("StereoSeparation"));
left = ecl::Clamp (255 - xdist, 0, 255);
right = ecl::Clamp (255 + xdist, 0, 255);
volume = (1 - dist/range) * se.volume;
}
else
{
volume = se.volume;
left = se.left;
right = se.right;
}
Mix_SetPanning (channel, left, right);
int mixvol = ecl::round_down<int>(volume * MIX_MAX_VOLUME);
Mix_Volume(channel, ecl::Clamp(mixvol, 0, MIX_MAX_VOLUME));
}
int SoundEngine_SDL::already_playing (const SoundEvent &s)
{
for (size_t i=0; i<m_channelinfo.size(); ++i) {
const SoundEvent &se = m_channelinfo[i];
if (se.active && se.name == s.name && se.playing_time < 0.05
&& (!se.has_position || !s.has_position ||
ecl::length(se.position - s.position) < 30))
return static_cast<int> (i);
}
return -1;
}
Mix_Chunk *SoundEngine_SDL::cache_sound(const std::string &name)
{
ecl::Dict<Mix_Chunk*>::iterator i=wav_cache.find(name);
if (i == wav_cache.end()) {
Mix_Chunk *ch = 0;
string filename;
if (app.resourceFS->findFile("soundsets/" + name + ".wav", filename))
ch = Mix_LoadWAV(filename.c_str());
if (ch != 0)
wav_cache.insert(name, ch);
else
enigma::Log << "Couldn't load sample '" << name << "': "
<< Mix_GetError() << endl;
return ch;
} else
return i->second;
}
bool SoundEngine_SDL::play_sound (const SoundEvent &s)
{
int channel = already_playing (s);
if (channel != -1) {
MutexLock (m_instance->m_mutex);
SoundEvent &se = m_channelinfo [channel];
if (se.has_position) {
se.position = (se.position + s.position) / 2;
update_channel (channel);
return true;
}
}
if (Mix_Chunk *chunk = cache_sound(s.name)) {
channel = -1; //Mix_GroupOldest(-1);
channel = Mix_PlayChannel(channel, chunk, 0);
if (channel != -1) {
{
MutexLock (m_instance->m_mutex);
SoundEvent &se = m_channelinfo[channel];
se = s;
se.active = true;
se.playing_time = 0.0;
}
update_channel (channel);
}
return true; // even if no free channel was found
} else
return false;
}
void SoundEngine_SDL::tick (double dtime)
{
MutexLock (m_instance->m_mutex);
for (size_t i=0; i<m_channelinfo.size(); ++i) {
SoundEvent &se = m_channelinfo[i];
if (se.active)
se.playing_time += dtime;
}
}
void SoundEngine_SDL::define_sound (
const SoundName &/*name*/,
const std::string &/*filename*/)
{
}
void SoundEngine_SDL::define_sound (
const SoundName &name,
const SoundData &data)
{
Uint32 bufsize = static_cast<Uint32> (data.buf.size());
Mix_Chunk *ch= ChunkFromRaw (&data.buf[0], bufsize,
data.freq, AUDIO_S8, data.nchannels);
if (ch != 0)
wav_cache.insert(name, ch);
}
void SoundEngine_SDL::channel_finished (int channel)
{
MutexLock (m_instance->m_mutex);
SoundEvent &se = m_instance->m_channelinfo[channel];
se.active = false;
}
/* -------------------- Local variables -------------------- */
namespace
{
auto_ptr<SoundEngine> sound_engine;
bool sound_enabled = true;
bool music_enabled = true;
bool sound_enabled_temp = false;
string current_music_name;
}
/* -------------------- Functions -------------------- */
void sound::Init(bool withMusic, bool withSound)
{
sound_enabled = withSound;
music_enabled = withMusic;
if (!sound_engine.get()) {
sound_engine.reset(new SoundEngine_SDL);
}
if (sound_engine->init()) {
options::UpdateVolume();
}
else {
sound_enabled = false;
music_enabled = false;
sound_engine.reset(new SoundEngine_Null);
}
}
void sound::Shutdown()
{
if (sound_engine.get())
sound_engine->shutdown();
}
void sound::Tick (double dtime)
{
sound_engine->tick (dtime);
}
void sound::TempDisableSound() {
sound_enabled_temp = sound_enabled;
sound_enabled = false;
}
void sound::TempReEnableSound() {
sound_enabled = sound_enabled_temp;
}
void sound::SetListenerPosition (const ecl::V2 &pos)
{
sound_engine->set_listenerpos (pos);
}
bool sound::PlaySound (const SoundName &name, const ecl::V2 &pos, double volume, int priority)
{
if (!sound_enabled)
return false;
SoundEvent se;
se.name = name;
se.has_position = true;
se.position = pos;
se.priority = priority;
se.volume = volume * options::GetDouble("SoundVolume");
se.left = se.right = 0;
return sound_engine->play_sound (se);
}
bool sound::PlaySoundGlobal (const SoundName &name, double volume, int priority)
{
SoundEvent se;
se.name = name;
se.has_position = false;
se.position = ecl::V2();
se.priority = priority;
se.volume = volume * options::GetDouble("SoundVolume");
se.left = 255;
se.right = 255;
return sound_engine->play_sound (se);
}
void sound::FadeoutMusic()
{
sound_engine->fadeout_music();
}
void sound::PlayMusic (const std::string &name)
{
if (!sound_enabled || !music_enabled || name==current_music_name)
return;
FadeoutMusic();
string fname;
if (app.resourceFS->findFile (name, fname) && sound_engine->play_music (fname))
current_music_name = name;
else
current_music_name = "";
}
void sound::StopMusic() {
sound_engine->stop_music();
current_music_name = "";
}
void sound::StopMusic (const std::string &name) {
if (name==current_music_name)
StopMusic();
}
void sound::ClearCache()
{
sound_engine->clear_cache();
}
void sound::DefineSound (const SoundName &name, const SoundData &data)
{
sound_engine->define_sound (name, data);
}
void sound::SetSoundVolume (double vol)
{
sound_engine->set_sound_volume (vol);
}
void sound::SetMusicVolume (double vol)
{
sound_engine->set_music_volume (vol);
}
/* SDL_ConvertAudio is only capable of changing the sound frequency by
integer powers of 2 (i.e., by a factor of ... 1/4 1/2 1 2 ...).
The sound files used by Oxyd are sampled at 6kHz which we must
convert to roughly 22kHz. This function resamples between any two
frequencies using simple linear interpolation. It is not capable
of changing the sample format or dealing with more than one
channel.
FIXME: We should apply a lowpass filter after reampling to get rid
of the artifacts introduced by linear interpolation or use a better
interpolator.
*/
namespace
{
Sint8 *resample (const Sint8 *data, Uint32 len, int oldfreq, int newfreq,
Uint32 *newlen_)
{
assert (data);
assert (len>0);
assert (oldfreq > 0);
assert (newfreq > 0);
const int sample_size = 1; // 8bit sample data
float ratio = float(oldfreq) / float(newfreq);
Uint32 newlen = ecl::round_down<int> (len / ratio);
*newlen_ = newlen;
Sint8 *newdata = (Sint8*) malloc (sample_size * newlen);
if (!newdata)
return 0;
const Sint8 *src = data;
Sint8 *dst = newdata;
float srcinc = float (len-1) / float (newlen);
for (unsigned i=0; i<newlen; ++i) {
int srcidx = ecl::round_down <int> (i * srcinc); // srcpos);
float a2 = i*srcinc - srcidx;
float a1 = 1.0f - a2;
dst[i] = static_cast<Sint8> ((a1*src[srcidx] + a2*src[srcidx+1])/2);
}
return newdata;
}
// typedef float (*FilterFunc)(float);
// inline double sinc(double x)
// {
// if (x == 0)
// return 1;
// x *= M_PI;
// return sin(x)/x;
// }
// class Sinc {
// public:
// static const int support = 4;
// static double func (double x) {
// if (x < -4 || x > 4)
// return 0;
// else
// return sinc(x);
// }
// };
// class Lanczos2 {
// public:
// static const int support = 2;
// static double func (double x) {
// if (x < -2 || x > 2)
// return 0;
// else
// return sinc(x) * sinc(x/2);
// }
// };
// class Lanczos3 {
// public:
// static const int support = 3;
// static double func (double x) {
// if (x < -3 || x > 3)
// return 0;
// else
// return sinc(x) * sinc(x/3);
// }
// };
// template <class Filter, class SampleT>
// class Resampler {
// double ratio;
// vector<double> G;
// public:
// Resampler (double ratio_)
// : ratio (ratio_)
// {
// }
// size_t calc_destlen (size_t sourcelen)
// {
// return static_cast<size_t> ((sourcelen / ratio + 1)
// * sizeof (SampleT));
// }
// void resample (const SampleT *source, size_t sourcelen,
// SampleT *dest, size_t destlen)
// {
// double factor = (double)sourcelen / destlen;
// int support = Filter::support;
// double dx = ecl::Max(factor, 1.0);
// vector<double> F (2*support + 1);
// SampleT *dp = dest;
// for (size_t t=0; t<destlen; ++t)
// {
// double center = (t+0.5)*factor; // kernel center in the source
// int start = ecl::Max<int>(0, center-support + 0.5);
// int stop = ecl::Min<int>(sourcelen, center+support+0.5);
// int n = stop-start;
// // Calculate filter coefficients
// double c = 0;
// double pos = start-center+0.5;
// for (int j=0; j<n; ++j) {
// F[j] = Filter::func(pos/dx);
// c += F[j];
// pos++;
// }
// // Normalize filter
// if (c != 0 && c!=1)
// for (int j=0; j<n; ++j)
// F[j] /= c;
// // Calculate next destination value
// const SampleT *sp = source + start;
// double accu = 0;
// for (int j=n-1; j>=0; --j)
// accu += F[j] * sp[j];
// *dp++ = static_cast<SampleT> (accu);
// // *dp++ = static_cast<SampleT> (ecl::Clamp<double> (accu, -128, 127));
// }
// }
// };
}
Mix_Chunk * ChunkFromRaw (const Uint8 *buf, Uint32 len,
int sfreq, int sformat, int schannels)
{
if (!sound_enabled || !buf)
return 0;
// Get destination format
int dfreq, dchannels;
Uint16 dformat;
Mix_QuerySpec (&dfreq, &dformat, &dchannels);
// Resample
// Resampler<Sinc, Sint8> r (double(sfreq)/dfreq);
// size_t newlen = r.calc_destlen(len);
// Uint8 *newbuf = (Uint8*) malloc (newlen);
// r.resample ((Sint8*)buf, len, (Sint8*)newbuf, newlen);
// Resample
Uint32 newlen=0;
Uint8 *newbuf = (Uint8*)resample((const Sint8*)buf, len, sfreq, dfreq, &newlen);
// Convert audio data
SDL_AudioCVT cvt;
if (!SDL_BuildAudioCVT (&cvt, sformat, schannels, dfreq,
dformat, dchannels, dfreq))
return 0;
cvt.buf = (Uint8*) malloc(newlen * cvt.len_mult);
cvt.len = newlen;
memcpy(cvt.buf, newbuf, newlen);
free(newbuf);
SDL_ConvertAudio(&cvt);
Mix_Chunk *chunk = Mix_QuickLoad_RAW(cvt.buf, cvt.len_cvt);
chunk->allocated = 1;
return chunk;
}
/* ------------- Conversion and helper functions ------------- */
/*! This function defines how to put soundset_key and eventname together to
create an eventkey. */
string SoundEngine::effectKey(string effect_name, string soundset_name)
{
if (soundset_name == "")
return getActiveSoundSetKey() + "#" + effect_name;
else
return soundset_name + "#" + effect_name;
}
/*! This function searches the known sound sets for a sound set with given
oxyd version, and returns the sound set name (or empty string if none). */
string SoundEngine::getOxydSoundSet(OxydVersion oxyd_ver)
{
for (SoundSetRepository::iterator i = sound_sets.begin();
i != sound_sets.end(); ++i)
if((*i).second.getOxydVersion() == oxyd_ver)
return (*i).first;
return "";
}
string sound::GetOxydSoundSet(OxydVersion oxyd_ver)
{
return sound_engine->getOxydSoundSet(oxyd_ver);
}
/*! Enigma 1.00 only knew the option "SoundSet", which was an integer. This
was quite unhandy if one wanted to add additional sound sets. Since 1.01
Enigma features the option "SoundSetName". Still, old "SoundSet" is needed
if user wants to switch to <= 1.00 again; so here are the conversion
functions. Any user sound set is mapped to 0 ("Default"). */
int SoundEngine::convertToOldSoundSetNumber(string soundset_name)
{
if(soundset_name == "Default") return 0;
if(soundset_name == "Enigma") return 1;
SoundSet sd = sound_sets[soundset_name];
if(sd.isOxyd())
return ((int) sd.getOxydVersion()) + 2;
return 0;
}
string SoundEngine::convertFromOldSoundSetNumber(int soundset_number)
{
if(soundset_number == 0) return "Default";
if(soundset_number == 1) return "Enigma";
return getOxydSoundSet((OxydVersion) (soundset_number - 2));
}
/* -------------------- Sound set handling -------------------- */
/*! These functions fill in data for the sound sets, initialises and
activates them. Return false, if something went wrong, e.g. when an
oxyd sound set is mentioned to not accessible oxyd version. */
bool SoundEngine::defineSoundSet(string soundset_name, string soundset_key,
int button_position)
{
sound_sets[soundset_name] = SoundSet(soundset_key, button_position);
Log << "Added sound set '" << soundset_name << "' (key '" << soundset_key
<< "') on position " << button_position << ".\n";
return true;
}
bool SoundEngine::defineSoundSetOxyd(string soundset_name, string soundset_key,
OxydVersion oxyd_ver, int button_position)
{
if(oxyd::FoundOxyd(oxyd_ver)) {
sound_sets[soundset_name] =
SoundSet(soundset_key, button_position, (OxydVersion) oxyd_ver);
Log << "Added sound set '" << soundset_name << "' (key '" << soundset_key
<< "') on position " << button_position << ".\n";
return true;
} else {
Log << "Skipped sound set '" << soundset_name << "'.\n";
return false;
}
}
bool SoundSet::activate()
{
if(getSoundSetKey() == "")
return false;
if(isOxyd() && !oxyd::InitOxydSoundSet(getOxydVersion()))
return false;
sound_engine->setActiveSoundSetKey(getSoundSetKey());
return true;
}
void sound::InitSoundSets() {
sound_engine->initSoundSets();
}
void SoundEngine::initSoundSets()
{
// Define sound sets
sound_sets.clear();
assert(sound_sets.empty());
assert(defineSoundSet ("Enigma", "Enigma", 1));
int pos = 2; // position in options menu button
if (defineSoundSetOxyd ("Oxyd", "Oxyd*", OxydVersion_Oxyd1, pos)) pos++;
if (defineSoundSetOxyd ("Magnum", "Magnum*", OxydVersion_OxydMagnum, pos)) pos++;
if (defineSoundSetOxyd ("Mag.Gold", "Magnum*", OxydVersion_OxydMagnumGold, pos)) pos++;
if (defineSoundSetOxyd ("Per.Oxyd", "Oxyd*", OxydVersion_PerOxyd, pos)) pos++;
if (defineSoundSetOxyd ("Extra", "Oxyd*", OxydVersion_OxydExtra, pos)) pos++;
// Define user sound sets, as given by sound_effects
for (SoundEffectRepository::iterator i = sound_effects.begin();
i != sound_effects.end(); ++i) {
string soundset_key = (*i).second.getSoundSetKey();
bool found = false;
// ignore Oxyd* and Magnum* sound effects, if no
if ((soundset_key != "Oxyd*") && (soundset_key != "Magnum*")) {
for (SoundSetRepository::iterator j = sound_sets.begin();
j != sound_sets.end(); ++j)
if((*j).second.getSoundSetKey() == soundset_key)
found = true;
if(!found)
if (defineSoundSet (soundset_key, soundset_key, pos)) pos++;
}
}
Log << "Found " << pos - 1 << " different sound sets.\n";
setSoundSetCount(pos - 1);
setDefaultSoundSet("Enigma");
// Extract sound set names and keys from options; activate!
string soundset_name = app.state->getString("SoundSetName");
if (soundset_name == "") { // just switched from 1.00 to higher
soundset_name = convertFromOldSoundSetNumber(options::GetInt("SoundSet"));
app.state->setProperty("SoundSetName", soundset_name);
}
if (soundset_name == "Default")
soundset_name = getDefaultSoundSet();
clear_cache();
if (sound_sets[soundset_name].activate())
Log << "Activated sound set '" << soundset_name << "'.\n";
else {
// Fallback, happens e.g. when oxyd sound set can't be established or
// a user soundset is given which doesn't exist anymore.
Log << "Warning: Soundset '" << soundset_name << "' not available.\n";
if (sound_sets["Enigma"].activate()) {
app.state->setProperty("SoundSetName", "Enigma");
options::SetOption("SoundSet", convertToOldSoundSetNumber("Enigma"));
} else
ASSERT(false, XFrontend,
"Soundsets defect and fallback 'Enigma' not available.");
}
}
void SoundEngine::setActiveSoundSet(string soundset_name)
{
string soundset_key = sound_sets[soundset_name].getSoundSetKey();
if (soundset_key == getActiveSoundSetKey())
return;
if (soundset_key == "Default") {
Log << "Warning: Tried to choose 'Default' as effective sound set.\n";
return;
}
if (soundset_key == "") {
Log << "Warning: Tried to choose empty sound set key as effective sound set.\n";
return;
}
clear_cache();
if (sound_sets[soundset_name].activate()) {
Log << "Switched to sound set '" << soundset_name << "' (key '"
<< soundset_key << "').\n";
} else
Log << "Warning: Problems loading sound set '" << soundset_name << "' (key'"
<< soundset_key << "').\n";
}
void sound::SetActiveSoundSet(string soundset_name)
{
sound_engine->setActiveSoundSet(soundset_name);
}
void sound::SetDefaultSoundSet(string soundset_name)
{
sound_engine->setDefaultSoundSet(soundset_name);
if(app.state->getString("SoundSetName") == "Default")
SetActiveSoundSet(soundset_name);
}
string SoundEngine::getSoundSetByPosition(int button_position)
{
for (SoundSetRepository::iterator i = sound_sets.begin();
i != sound_sets.end(); ++i)
if((*i).second.getButtonPosition() == button_position)
return (*i).first;
return "";
}
/* -------------------- Playing sound events -------------------- */
/*! The first function creates an interface to add sound events to Enigma.
It is accessed via sound-defaults.lua and user sound definitions.
The second method is the interface between the formal SoundEffect
and the sound engine (via PlaySound[Global]). The last two functions
define the interface between level objects and SoundEffect. */
void sound::DefineSoundEffect(string soundset_key, string name, string filename,
double volume, bool loop, bool global, int priority,
double damp_max, double damp_inc, double damp_mult,
double damp_min, double damp_tick, string silence_string) {
assert(sound_engine.get());
if(soundset_key == "") {
Log << "Warning: Tried to define sound event '" << name
<< "' without sound set key. Skipped.\n";
return;
}
sound_engine->defineSoundEffect(soundset_key, name,
SoundEffect(name, soundset_key, filename, volume, loop, global, priority,
damp_max, damp_inc, damp_mult, damp_min, damp_tick, silence_string));
}
bool SoundEffect::play(const ecl::V2 &pos, double vol, bool glob)
{
if (filename == "") {
Log << "No soundfile given for sound event " << name << ".\n";
return false;
}
if (glob || global)
return PlaySoundGlobal (filename, volume * vol, priority);
else
return PlaySound (filename, pos, volume * vol, priority);
}
bool SoundEngine::emitSoundEvent (const std::string &eventname, const ecl::V2 &pos,
double volume, bool force_global)
{
string effectkey = effectKey(eventname);
SoundEffectRepository::iterator i = sound_effects.find(effectkey);
if (i == sound_effects.end()) {
Log << "Undefined sound event " << effectkey << " @ "
<< pos[0] << "," << pos[1] << "\n";
return false;
} else {
return (*i).second.play(pos, volume, force_global);
}
}
bool sound::EmitSoundEvent (const std::string &eventname, const ecl::V2 &pos,
double volume, bool force_global)
{
return sound_engine->emitSoundEvent(eventname, pos, volume, force_global);
}
bool sound::EmitSoundEventGlobal (const std::string &eventname, double volume)
{
return sound_engine->emitSoundEvent(eventname, ecl::V2(), volume, true);
}
void SoundEngine::writeSilenceString (const std::string &eventname)
{
string effectkey = effectKey(eventname);
SoundEffectRepository::iterator i = sound_effects.find(effectkey);
if (i != sound_effects.end()) {
string silence_string = (*i).second.getSilenceString();
if (silence_string != "")
client::Msg_ShowText (silence_string, true);
}
}
void sound::WriteSilenceString (const std::string &eventname)
{
sound_engine->writeSilenceString(eventname);
}
/* -------------------- Sound damping implementation -------------------- */
/*! These methods are connected to the sound damping mechanism, designed
to reduce the noise created by some objects like st-lightpassenger. */
SoundDamping::SoundDamping(std::string effect_name_, const void *origin_)
: effect_name(effect_name_), origin(origin_)
{
damp = sound_engine->getDampingData(effect_name_);
factor = damp.incr;
//Log << "New damping entry " << effect_name << " with " << damp.incr << ".\n";
}
float SoundDamping::get_volume(float def_volume) {
if (factor < damp.maxi)
factor += damp.incr;
//Log << " Found entry " << effect_name << ". Factor is now " << i->factor << ".\n";
float q = factor * damp.mult;
if (q > 1.0)
return def_volume / q;
return def_volume;
}
bool SoundDamping::tick() {
// return true, if this entity is to be destroyed.
factor *= damp.tick;
return (factor <= damp.mini);
}
/* -------------------- Sound option helpers -------------------- */
/*! These functions are used in OptionsMenu.cc for the Soundset-Button. */
int sound::GetOptionSoundSetCount()
{
return sound_engine->getSoundSetCount() + 1;
}
int sound::GetOptionSoundSet()
{
string soundSet = app.state->getString("SoundSetName");
if (soundSet == "Default")
return 0;
int pos = sound_engine->getButtonPosition(soundSet);
assert(pos > 0);
return pos;
}
void sound::SetOptionSoundSet(int value)
{
if(value == 0) {
// settting to default sound set
if (app.state->getString("SoundSetName") == "Default")
return;
app.state->setProperty("SoundSetName", "Default");
options::SetOption("SoundSet", sound_engine->convertToOldSoundSetNumber("Default"));
SetActiveSoundSet(sound_engine->getDefaultSoundSet());
} else {
string newSet = sound_engine->getSoundSetByPosition(value);
assert(newSet != "");
if (app.state->getString("SoundSetName") == newSet)
return;
app.state->setProperty("SoundSetName", newSet);
options::SetOption("SoundSet", sound_engine->convertToOldSoundSetNumber(newSet));
SetActiveSoundSet(newSet);
}
}
string sound::GetOptionSoundSetText(int value)
{
if(value == 0)
return N_("Default");
string soundset_name = sound_engine->getSoundSetByPosition(value);
if(soundset_name == "")
return "INVALID";
return soundset_name;
}