forked from Mirrors/openclonk
358 lines
13 KiB
C++
358 lines
13 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 1998-2000, Matthes Bender
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2015-2016, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
/* Handles the sound bank and plays effects using DSoundX */
|
|
|
|
#include "C4Include.h"
|
|
#include "platform/C4SoundModifiers.h"
|
|
|
|
#include "game/C4Application.h"
|
|
#include "platform/C4SoundInstance.h"
|
|
#include "platform/C4SoundSystem.h"
|
|
#include "script/C4Value.h"
|
|
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
static LPALGENEFFECTS alGenEffects;
|
|
static LPALDELETEEFFECTS alDeleteEffects;
|
|
static LPALISEFFECT alIsEffect;
|
|
static LPALEFFECTI alEffecti;
|
|
static LPALEFFECTIV alEffectiv;
|
|
static LPALEFFECTF alEffectf;
|
|
static LPALEFFECTFV alEffectfv;
|
|
static LPALGETEFFECTI alGetEffecti;
|
|
static LPALGETEFFECTIV alGetEffectiv;
|
|
static LPALGETEFFECTF alGetEffectf;
|
|
static LPALGETEFFECTFV alGetEffectfv;
|
|
static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
|
|
static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
|
|
static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
|
|
static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
|
|
static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
|
|
static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
|
|
static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
|
|
static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
|
|
static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
|
|
static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
|
|
static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
|
|
#endif
|
|
|
|
C4SoundModifier::C4SoundModifier(C4PropList *in_props) : instance_count(0)
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
, effect(0u), slot(0u)
|
|
#endif
|
|
{
|
|
props.SetPropList(in_props);
|
|
Application.SoundSystem.Modifiers.Add(this);
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
Application.MusicSystem.SelectContext();
|
|
alGenEffects(1, &effect);
|
|
alGenAuxiliaryEffectSlots(1, &slot);
|
|
#endif
|
|
}
|
|
|
|
C4SoundModifier::~C4SoundModifier()
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
// failsafe effect removal
|
|
if (alIsEffect(effect))
|
|
alDeleteEffects(1, &effect);
|
|
if (alIsAuxiliaryEffectSlot(slot))
|
|
alDeleteAuxiliaryEffectSlots(1, &slot);
|
|
#endif
|
|
Application.SoundSystem.Modifiers.Remove(this);
|
|
}
|
|
|
|
void C4SoundModifier::Update()
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
// update AL effect slot
|
|
if (slot)
|
|
{
|
|
alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect);
|
|
}
|
|
#endif
|
|
// update from props and mark as not released
|
|
released = false;
|
|
}
|
|
|
|
#if AUDIO_TK == AUDIO_TK_OPENAL
|
|
void C4SoundModifier::ApplyTo(ALuint source)
|
|
{
|
|
// apply slot to source if valid
|
|
#if defined(HAVE_ALEXT)
|
|
if (slot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, slot, 0, AL_FILTER_NULL);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
float C4SoundModifier::GetFloatProp(C4PropertyName key, float ratio, float default_value)
|
|
{
|
|
// get scaled property and fill in default value
|
|
C4PropList *p = props._getPropList();
|
|
return float(p->GetPropertyInt(key, int32_t(default_value * ratio))) / ratio;
|
|
}
|
|
|
|
bool C4SoundModifier::GetBoolProp(C4PropertyName key, bool default_value)
|
|
{
|
|
// get scaled property and fill in default value
|
|
C4PropList *p = props._getPropList();
|
|
return (p->GetPropertyInt(key, int32_t(default_value ? 1 : 0)) != 0);
|
|
}
|
|
|
|
C4SoundModifierReverb::C4SoundModifierReverb(C4PropList *in_props)
|
|
: C4SoundModifier(in_props)
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
|
|
#endif
|
|
}
|
|
|
|
void C4SoundModifierReverb::Update()
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
// use the cave preset as default for the reverb modifier
|
|
Application.MusicSystem.SelectContext();
|
|
alEffectf(effect, AL_REVERB_DENSITY, GetFloatProp(P_Reverb_Density, 1000, 1.0f));
|
|
alEffectf(effect, AL_REVERB_DIFFUSION, GetFloatProp(P_Reverb_Diffusion, 1000, 1.0f));
|
|
alEffectf(effect, AL_REVERB_GAIN, GetFloatProp(P_Reverb_Gain, 1000, 0.316f));
|
|
alEffectf(effect, AL_REVERB_GAINHF, GetFloatProp(P_Reverb_GainHF, 1000, 1.0f));
|
|
alEffectf(effect, AL_REVERB_DECAY_TIME, GetFloatProp(P_Reverb_Decay_Time, 1000, 2.91f));
|
|
alEffectf(effect, AL_REVERB_DECAY_HFRATIO, GetFloatProp(P_Reverb_Decay_HFRatio, 1000, 1.3f));
|
|
alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, GetFloatProp(P_Reverb_Reflections_Gain, 1000, 0.5f));
|
|
alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, GetFloatProp(P_Reverb_Reflections_Delay, 1000, 0.015f));
|
|
alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, GetFloatProp(P_Reverb_Late_Reverb_Gain, 1000, 0.706f));
|
|
alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, GetFloatProp(P_Reverb_Late_Reverb_Delay, 1000, 0.022f));
|
|
alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, GetFloatProp(P_Reverb_Air_Absorption_GainHF, 1000, 0.994f));
|
|
alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, GetFloatProp(P_Reverb_Room_Rolloff_Factor, 1000, 0.0f));
|
|
alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, GetBoolProp(P_Reverb_Decay_HFLimit, true) ? 1 : 0);
|
|
#endif
|
|
C4SoundModifier::Update();
|
|
}
|
|
|
|
C4SoundModifierEcho::C4SoundModifierEcho(C4PropList *in_props)
|
|
: C4SoundModifier(in_props)
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_ECHO);
|
|
#endif
|
|
}
|
|
|
|
void C4SoundModifierEcho::Update()
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
// use default OpenAL echo preset
|
|
Application.MusicSystem.SelectContext();
|
|
alEffectf(effect, AL_ECHO_DELAY, GetFloatProp(P_Echo_Delay, 1000, 0.1f));
|
|
alEffectf(effect, AL_ECHO_LRDELAY, GetFloatProp(P_Echo_LRDelay, 1000, 0.1f));
|
|
alEffectf(effect, AL_ECHO_DAMPING, GetFloatProp(P_Echo_Damping, 1000, 0.5f));
|
|
alEffectf(effect, AL_ECHO_FEEDBACK, GetFloatProp(P_Echo_Feedback, 1000, 0.5f));
|
|
alEffectf(effect, AL_ECHO_SPREAD, GetFloatProp(P_Echo_Spread, 1000, -1.0f));
|
|
#endif
|
|
C4SoundModifier::Update();
|
|
}
|
|
|
|
C4SoundModifierEqualizer::C4SoundModifierEqualizer(C4PropList *in_props)
|
|
: C4SoundModifier(in_props)
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EQUALIZER);
|
|
#endif
|
|
}
|
|
|
|
void C4SoundModifierEqualizer::Update()
|
|
{
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
// use default OpenAL equalizer preset
|
|
Application.MusicSystem.SelectContext();
|
|
alEffectf(effect, AL_EQUALIZER_LOW_GAIN, GetFloatProp(P_Equalizer_Low_Gain, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_LOW_CUTOFF, GetFloatProp(P_Equalizer_Low_Cutoff, 1000, 200.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID1_GAIN, GetFloatProp(P_Equalizer_Mid1_Gain, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID1_CENTER, GetFloatProp(P_Equalizer_Mid1_Center, 1000, 500.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID1_WIDTH, GetFloatProp(P_Equalizer_Mid1_Width, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID2_GAIN, GetFloatProp(P_Equalizer_Mid2_Gain, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID2_CENTER, GetFloatProp(P_Equalizer_Mid2_Center, 1000, 3000.0f));
|
|
alEffectf(effect, AL_EQUALIZER_MID2_WIDTH, GetFloatProp(P_Equalizer_Mid2_Width, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_HIGH_GAIN, GetFloatProp(P_Equalizer_High_Gain, 1000, 1.0f));
|
|
alEffectf(effect, AL_EQUALIZER_HIGH_CUTOFF, GetFloatProp(P_Equalizer_High_Cutoff, 1000, 6000.0f));
|
|
#endif
|
|
C4SoundModifier::Update();
|
|
}
|
|
|
|
C4SoundModifierList::C4SoundModifierList()
|
|
{
|
|
for (int32_t i = 0; i <= C4SoundModifier::C4SMT_Max; ++i)
|
|
is_effect_available[i] = false;
|
|
}
|
|
|
|
void C4SoundModifierList::Init()
|
|
{
|
|
is_initialized = false;
|
|
#if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
|
|
#define LOAD_ALPROC(x) ((void *&)(x) = alGetProcAddress(#x))
|
|
Application.MusicSystem.SelectContext();
|
|
if (!alcIsExtensionPresent(Application.MusicSystem.GetDevice(), "ALC_EXT_EFX"))
|
|
{
|
|
LogF("ALExt: No efx extensions available. Sound modifications disabled.");
|
|
return;
|
|
}
|
|
LOAD_ALPROC(alGenEffects);
|
|
LOAD_ALPROC(alDeleteEffects);
|
|
LOAD_ALPROC(alIsEffect);
|
|
LOAD_ALPROC(alEffecti);
|
|
LOAD_ALPROC(alEffectiv);
|
|
LOAD_ALPROC(alEffectf);
|
|
LOAD_ALPROC(alEffectfv);
|
|
LOAD_ALPROC(alGetEffecti);
|
|
LOAD_ALPROC(alGetEffectiv);
|
|
LOAD_ALPROC(alGetEffectf);
|
|
LOAD_ALPROC(alGetEffectfv);
|
|
LOAD_ALPROC(alGenAuxiliaryEffectSlots);
|
|
LOAD_ALPROC(alDeleteAuxiliaryEffectSlots);
|
|
LOAD_ALPROC(alIsAuxiliaryEffectSlot);
|
|
LOAD_ALPROC(alAuxiliaryEffectSloti);
|
|
LOAD_ALPROC(alAuxiliaryEffectSlotiv);
|
|
LOAD_ALPROC(alAuxiliaryEffectSlotf);
|
|
LOAD_ALPROC(alAuxiliaryEffectSlotfv);
|
|
LOAD_ALPROC(alGetAuxiliaryEffectSloti);
|
|
LOAD_ALPROC(alGetAuxiliaryEffectSlotiv);
|
|
LOAD_ALPROC(alGetAuxiliaryEffectSlotf);
|
|
LOAD_ALPROC(alGetAuxiliaryEffectSlotfv);
|
|
if (!alGenEffects)
|
|
{
|
|
LogF("ALExt: Error loading efx extensions. Sound modifications disabled.");
|
|
return; // safety
|
|
}
|
|
StdStrBuf sAvailableEffects("");
|
|
StdStrBuf sUnavailableEffects("");
|
|
static const struct {
|
|
ALenum effect;
|
|
const char *name;
|
|
} test_effects[] = {
|
|
#define AL_TEST_EFFECT(effect) { effect, #effect }
|
|
AL_TEST_EFFECT(AL_EFFECT_REVERB),
|
|
AL_TEST_EFFECT(AL_EFFECT_ECHO),
|
|
AL_TEST_EFFECT(AL_EFFECT_EQUALIZER),
|
|
#undef AL_TEST_EFFECT
|
|
};
|
|
ALuint effect = 0u;
|
|
alGenEffects(1, &effect);
|
|
ALenum err = alGetError();
|
|
if (err != AL_NO_ERROR)
|
|
{
|
|
LogF("OpenAL alGenEffects Error: %s", alGetString(err));
|
|
}
|
|
for (auto test_effect : test_effects)
|
|
{
|
|
alEffecti(effect, AL_EFFECT_TYPE, test_effect.effect);
|
|
err = alGetError();
|
|
bool is_ok = (err == AL_NO_ERROR);
|
|
is_effect_available[test_effect.effect] = is_ok;
|
|
StdStrBuf *target_string;
|
|
if (!is_ok) target_string = &sUnavailableEffects; else target_string = &sAvailableEffects;
|
|
if (target_string->getLength()) target_string->Append(", ");
|
|
target_string->Append(test_effect.name);
|
|
if (!is_ok) target_string->AppendFormat(" (%s)", alGetString(err));
|
|
}
|
|
if (alIsEffect(effect)) alDeleteEffects(1, &effect);
|
|
if (sAvailableEffects.getLength() == 0) sAvailableEffects = "(none)";
|
|
if (sUnavailableEffects.getLength() == 0) sUnavailableEffects = "(none)";
|
|
LogF("OpenAL extensions loaded. Available: %s. Unavailable: %s.", sAvailableEffects.getData(), sUnavailableEffects.getData());
|
|
#undef LOAD_ALPROC
|
|
#else
|
|
// modifiers not supported
|
|
return;
|
|
#endif
|
|
is_initialized = true;
|
|
}
|
|
|
|
void C4SoundModifierList::Clear()
|
|
{
|
|
// Release all sounds. ref count should be zero now because sound instances should have been cleared before this call.
|
|
for (auto iter = sound_modifiers.begin(); iter != sound_modifiers.end(); )
|
|
{
|
|
C4SoundModifier *modifier = *iter;
|
|
++iter;
|
|
// release after iterator increase because deletion will modify the list
|
|
assert(modifier->GetRefCount() == 0);
|
|
modifier->Release();
|
|
}
|
|
}
|
|
|
|
C4SoundModifier *C4SoundModifierList::Get(class C4PropList *props, bool create_if_not_found)
|
|
{
|
|
// find odifier by prop list
|
|
auto iter = std::find_if(sound_modifiers.begin(), sound_modifiers.end(),
|
|
[props](const C4SoundModifier *mod) { return mod->GetProps() == props; });
|
|
if (iter != sound_modifiers.end()) return *iter;
|
|
// if not found, create if desired
|
|
if (!create_if_not_found) return nullptr;
|
|
C4SoundModifier *modifier;
|
|
int32_t effect_type = props->GetPropertyInt(P_Type);
|
|
if (!is_effect_available[effect_type]) return nullptr; // Not supported D:
|
|
switch (effect_type)
|
|
{
|
|
case C4SoundModifier::C4SMT_Reverb:
|
|
modifier = new C4SoundModifierReverb(props);
|
|
break;
|
|
case C4SoundModifier::C4SMT_Echo:
|
|
modifier = new C4SoundModifierEcho(props);
|
|
break;
|
|
case C4SoundModifier::C4SMT_Equalizer:
|
|
modifier = new C4SoundModifierEqualizer(props);
|
|
break;
|
|
default:
|
|
// invalid modifier
|
|
return nullptr;
|
|
}
|
|
// initial parameter settings
|
|
modifier->Update();
|
|
return modifier;
|
|
}
|
|
|
|
void C4SoundModifierList::SetGlobalModifier(C4SoundModifier *new_modifier, int32_t player_number)
|
|
{
|
|
// -1 based array access for NO_OWNER player number
|
|
int32_t global_modifier_index = player_number + 1;
|
|
if (global_modifier_index < 0) return; // safety
|
|
size_t index = static_cast<size_t>(global_modifier_index);
|
|
if (index >= global_modifiers.size()) global_modifiers.resize(index+1);
|
|
if (new_modifier != global_modifiers[index])
|
|
{
|
|
// Run new effects with new modifier
|
|
C4SoundModifier *old_modifier = global_modifiers[index];
|
|
global_modifiers[index] = new_modifier;
|
|
// update existing sound effects first
|
|
for (C4SoundInstance *pInst = ::Application.SoundSystem.GetFirstInstance(); pInst; pInst = ::Application.SoundSystem.GetNextInstance(pInst))
|
|
if (pInst->GetModifier() == old_modifier) // that check is not 100% correct but should cover all realistic use-cases
|
|
pInst->SetModifier(new_modifier, true);
|
|
// Reference counting
|
|
if (new_modifier) new_modifier->AddRef();
|
|
if (old_modifier) old_modifier->DelRef();
|
|
}
|
|
}
|
|
|
|
C4SoundModifier *C4SoundModifierList::GetGlobalModifier(int32_t player_index) const
|
|
{
|
|
// safety
|
|
int32_t list_index = player_index + 1;
|
|
if (list_index < 0 || static_cast<size_t>(list_index) >= global_modifiers.size()) return nullptr;
|
|
// get from player and fall back to global list
|
|
C4SoundModifier *result = global_modifiers[list_index];
|
|
if (!result && list_index) result = global_modifiers[0];
|
|
return result;
|
|
}
|