/* * 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(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(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; }