OpenClonk
C4SoundModifiers.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2015-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Handles the sound bank and plays effects using DSoundX */
19 
20 #include "C4Include.h"
22 
23 #include "game/C4Application.h"
25 #include "platform/C4SoundSystem.h"
26 #include "script/C4Value.h"
27 
28 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
29 static LPALGENEFFECTS alGenEffects;
30 static LPALDELETEEFFECTS alDeleteEffects;
31 static LPALISEFFECT alIsEffect;
32 static LPALEFFECTI alEffecti;
33 static LPALEFFECTIV alEffectiv;
34 static LPALEFFECTF alEffectf;
35 static LPALEFFECTFV alEffectfv;
36 static LPALGETEFFECTI alGetEffecti;
37 static LPALGETEFFECTIV alGetEffectiv;
38 static LPALGETEFFECTF alGetEffectf;
39 static LPALGETEFFECTFV alGetEffectfv;
40 static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
41 static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
42 static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
43 static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
44 static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
45 static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
46 static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
47 static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
48 static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
49 static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
50 static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
51 #endif
52 
53 C4SoundModifier::C4SoundModifier(C4PropList *in_props) : instance_count(0)
54 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
55  , effect(0u), slot(0u)
56 #endif
57 {
58  props.SetPropList(in_props);
60 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
62  alGenEffects(1, &effect);
63  alGenAuxiliaryEffectSlots(1, &slot);
64 #endif
65 }
66 
68 {
69 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
70  // failsafe effect removal
71  if (alIsEffect(effect))
72  alDeleteEffects(1, &effect);
73  if (alIsAuxiliaryEffectSlot(slot))
74  alDeleteAuxiliaryEffectSlots(1, &slot);
75 #endif
77 }
78 
80 {
81 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
82  // update AL effect slot
83  if (slot)
84  {
85  alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect);
86  }
87 #endif
88  // update from props and mark as not released
89  released = false;
90 }
91 
92 #if AUDIO_TK == AUDIO_TK_OPENAL
93 void C4SoundModifier::ApplyTo(ALuint source)
94 {
95  // apply slot to source if valid
96 #if defined(HAVE_ALEXT)
97  if (slot) alSource3i(source, AL_AUXILIARY_SEND_FILTER, slot, 0, AL_FILTER_NULL);
98 #endif
99 }
100 #endif
101 
102 float C4SoundModifier::GetFloatProp(C4PropertyName key, float ratio, float default_value)
103 {
104  // get scaled property and fill in default value
105  C4PropList *p = props._getPropList();
106  return float(p->GetPropertyInt(key, int32_t(default_value * ratio))) / ratio;
107 }
108 
109 bool C4SoundModifier::GetBoolProp(C4PropertyName key, bool default_value)
110 {
111  // get scaled property and fill in default value
112  C4PropList *p = props._getPropList();
113  return (p->GetPropertyInt(key, int32_t(default_value ? 1 : 0)) != 0);
114 }
115 
117  : C4SoundModifier(in_props)
118 {
119 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
120  alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
121 #endif
122 }
123 
125 {
126 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
127  // use the cave preset as default for the reverb modifier
129  alEffectf(effect, AL_REVERB_DENSITY, GetFloatProp(P_Reverb_Density, 1000, 1.0f));
130  alEffectf(effect, AL_REVERB_DIFFUSION, GetFloatProp(P_Reverb_Diffusion, 1000, 1.0f));
131  alEffectf(effect, AL_REVERB_GAIN, GetFloatProp(P_Reverb_Gain, 1000, 0.316f));
132  alEffectf(effect, AL_REVERB_GAINHF, GetFloatProp(P_Reverb_GainHF, 1000, 1.0f));
133  alEffectf(effect, AL_REVERB_DECAY_TIME, GetFloatProp(P_Reverb_Decay_Time, 1000, 2.91f));
134  alEffectf(effect, AL_REVERB_DECAY_HFRATIO, GetFloatProp(P_Reverb_Decay_HFRatio, 1000, 1.3f));
135  alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, GetFloatProp(P_Reverb_Reflections_Gain, 1000, 0.5f));
136  alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, GetFloatProp(P_Reverb_Reflections_Delay, 1000, 0.015f));
137  alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, GetFloatProp(P_Reverb_Late_Reverb_Gain, 1000, 0.706f));
138  alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, GetFloatProp(P_Reverb_Late_Reverb_Delay, 1000, 0.022f));
139  alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, GetFloatProp(P_Reverb_Air_Absorption_GainHF, 1000, 0.994f));
140  alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, GetFloatProp(P_Reverb_Room_Rolloff_Factor, 1000, 0.0f));
141  alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, GetBoolProp(P_Reverb_Decay_HFLimit, true) ? 1 : 0);
142 #endif
144 }
145 
147  : C4SoundModifier(in_props)
148 {
149 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
150  alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_ECHO);
151 #endif
152 }
153 
155 {
156 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
157  // use default OpenAL echo preset
159  alEffectf(effect, AL_ECHO_DELAY, GetFloatProp(P_Echo_Delay, 1000, 0.1f));
160  alEffectf(effect, AL_ECHO_LRDELAY, GetFloatProp(P_Echo_LRDelay, 1000, 0.1f));
161  alEffectf(effect, AL_ECHO_DAMPING, GetFloatProp(P_Echo_Damping, 1000, 0.5f));
162  alEffectf(effect, AL_ECHO_FEEDBACK, GetFloatProp(P_Echo_Feedback, 1000, 0.5f));
163  alEffectf(effect, AL_ECHO_SPREAD, GetFloatProp(P_Echo_Spread, 1000, -1.0f));
164 #endif
166 }
167 
169  : C4SoundModifier(in_props)
170 {
171 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
172  alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EQUALIZER);
173 #endif
174 }
175 
177 {
178 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
179  // use default OpenAL equalizer preset
181  alEffectf(effect, AL_EQUALIZER_LOW_GAIN, GetFloatProp(P_Equalizer_Low_Gain, 1000, 1.0f));
182  alEffectf(effect, AL_EQUALIZER_LOW_CUTOFF, GetFloatProp(P_Equalizer_Low_Cutoff, 1000, 200.0f));
183  alEffectf(effect, AL_EQUALIZER_MID1_GAIN, GetFloatProp(P_Equalizer_Mid1_Gain, 1000, 1.0f));
184  alEffectf(effect, AL_EQUALIZER_MID1_CENTER, GetFloatProp(P_Equalizer_Mid1_Center, 1000, 500.0f));
185  alEffectf(effect, AL_EQUALIZER_MID1_WIDTH, GetFloatProp(P_Equalizer_Mid1_Width, 1000, 1.0f));
186  alEffectf(effect, AL_EQUALIZER_MID2_GAIN, GetFloatProp(P_Equalizer_Mid2_Gain, 1000, 1.0f));
187  alEffectf(effect, AL_EQUALIZER_MID2_CENTER, GetFloatProp(P_Equalizer_Mid2_Center, 1000, 3000.0f));
188  alEffectf(effect, AL_EQUALIZER_MID2_WIDTH, GetFloatProp(P_Equalizer_Mid2_Width, 1000, 1.0f));
189  alEffectf(effect, AL_EQUALIZER_HIGH_GAIN, GetFloatProp(P_Equalizer_High_Gain, 1000, 1.0f));
190  alEffectf(effect, AL_EQUALIZER_HIGH_CUTOFF, GetFloatProp(P_Equalizer_High_Cutoff, 1000, 6000.0f));
191 #endif
193 }
194 
196 {
197  for (int32_t i = 0; i <= C4SoundModifier::C4SMT_Max; ++i)
198  is_effect_available[i] = false;
199 }
200 
202 {
203  is_initialized = false;
204 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
205 #define LOAD_ALPROC(x) ((void *&)(x) = alGetProcAddress(#x))
207  if (!alcIsExtensionPresent(Application.MusicSystem.GetDevice(), "ALC_EXT_EFX"))
208  {
209  LogF("ALExt: No efx extensions available. Sound modifications disabled.");
210  return;
211  }
212  LOAD_ALPROC(alGenEffects);
213  LOAD_ALPROC(alDeleteEffects);
214  LOAD_ALPROC(alIsEffect);
215  LOAD_ALPROC(alEffecti);
216  LOAD_ALPROC(alEffectiv);
217  LOAD_ALPROC(alEffectf);
218  LOAD_ALPROC(alEffectfv);
219  LOAD_ALPROC(alGetEffecti);
220  LOAD_ALPROC(alGetEffectiv);
221  LOAD_ALPROC(alGetEffectf);
222  LOAD_ALPROC(alGetEffectfv);
223  LOAD_ALPROC(alGenAuxiliaryEffectSlots);
224  LOAD_ALPROC(alDeleteAuxiliaryEffectSlots);
225  LOAD_ALPROC(alIsAuxiliaryEffectSlot);
226  LOAD_ALPROC(alAuxiliaryEffectSloti);
227  LOAD_ALPROC(alAuxiliaryEffectSlotiv);
228  LOAD_ALPROC(alAuxiliaryEffectSlotf);
229  LOAD_ALPROC(alAuxiliaryEffectSlotfv);
230  LOAD_ALPROC(alGetAuxiliaryEffectSloti);
231  LOAD_ALPROC(alGetAuxiliaryEffectSlotiv);
232  LOAD_ALPROC(alGetAuxiliaryEffectSlotf);
233  LOAD_ALPROC(alGetAuxiliaryEffectSlotfv);
234  if (!alGenEffects)
235  {
236  LogF("ALExt: Error loading efx extensions. Sound modifications disabled.");
237  return; // safety
238  }
239  StdStrBuf sAvailableEffects("");
240  StdStrBuf sUnavailableEffects("");
241  static const struct {
242  ALenum effect;
243  const char *name;
244  } test_effects[] = {
245 #define AL_TEST_EFFECT(effect) { effect, #effect }
246  AL_TEST_EFFECT(AL_EFFECT_REVERB),
247  AL_TEST_EFFECT(AL_EFFECT_ECHO),
248  AL_TEST_EFFECT(AL_EFFECT_EQUALIZER),
249 #undef AL_TEST_EFFECT
250  };
251  ALuint effect = 0u;
252  alGenEffects(1, &effect);
253  ALenum err = alGetError();
254  if (err != AL_NO_ERROR)
255  {
256  LogF("OpenAL alGenEffects Error: %s", alGetString(err));
257  }
258  for (auto test_effect : test_effects)
259  {
260  alEffecti(effect, AL_EFFECT_TYPE, test_effect.effect);
261  err = alGetError();
262  bool is_ok = (err == AL_NO_ERROR);
263  is_effect_available[test_effect.effect] = is_ok;
264  StdStrBuf *target_string;
265  if (!is_ok) target_string = &sUnavailableEffects; else target_string = &sAvailableEffects;
266  if (target_string->getLength()) target_string->Append(", ");
267  target_string->Append(test_effect.name);
268  if (!is_ok) target_string->AppendFormat(" (%s)", alGetString(err));
269  }
270  if (alIsEffect(effect)) alDeleteEffects(1, &effect);
271  if (sAvailableEffects.getLength() == 0) sAvailableEffects = "(none)";
272  if (sUnavailableEffects.getLength() == 0) sUnavailableEffects = "(none)";
273  LogF("OpenAL extensions loaded. Available: %s. Unavailable: %s.", sAvailableEffects.getData(), sUnavailableEffects.getData());
274 #undef LOAD_ALPROC
275 #else
276  // modifiers not supported
277  return;
278 #endif
279  is_initialized = true;
280 }
281 
283 {
284  // Release all sounds. ref count should be zero now because sound instances should have been cleared before this call.
285  for (auto iter = sound_modifiers.begin(); iter != sound_modifiers.end(); )
286  {
287  C4SoundModifier *modifier = *iter;
288  ++iter;
289  // release after iterator increase because deletion will modify the list
290  assert(modifier->GetRefCount() == 0);
291  modifier->Release();
292  }
293 }
294 
295 C4SoundModifier *C4SoundModifierList::Get(class C4PropList *props, bool create_if_not_found)
296 {
297  // find odifier by prop list
298  auto iter = std::find_if(sound_modifiers.begin(), sound_modifiers.end(),
299  [props](const C4SoundModifier *mod) { return mod->GetProps() == props; });
300  if (iter != sound_modifiers.end()) return *iter;
301  // if not found, create if desired
302  if (!create_if_not_found) return nullptr;
303  C4SoundModifier *modifier;
304  int32_t effect_type = props->GetPropertyInt(P_Type);
305  if (!is_effect_available[effect_type]) return nullptr; // Not supported D:
306  switch (effect_type)
307  {
309  modifier = new C4SoundModifierReverb(props);
310  break;
312  modifier = new C4SoundModifierEcho(props);
313  break;
315  modifier = new C4SoundModifierEqualizer(props);
316  break;
317  default:
318  // invalid modifier
319  return nullptr;
320  }
321  // initial parameter settings
322  modifier->Update();
323  return modifier;
324 }
325 
326 void C4SoundModifierList::SetGlobalModifier(C4SoundModifier *new_modifier, int32_t player_number)
327 {
328  // -1 based array access for NO_OWNER player number
329  int32_t global_modifier_index = player_number + 1;
330  if (global_modifier_index < 0) return; // safety
331  size_t index = static_cast<size_t>(global_modifier_index);
332  if (index >= global_modifiers.size()) global_modifiers.resize(index+1);
333  if (new_modifier != global_modifiers[index])
334  {
335  // Run new effects with new modifier
336  C4SoundModifier *old_modifier = global_modifiers[index];
337  global_modifiers[index] = new_modifier;
338  // update existing sound effects first
340  if (pInst->GetModifier() == old_modifier) // that check is not 100% correct but should cover all realistic use-cases
341  pInst->SetModifier(new_modifier, true);
342  // Reference counting
343  if (new_modifier) new_modifier->AddRef();
344  if (old_modifier) old_modifier->DelRef();
345  }
346 }
347 
349 {
350  // safety
351  int32_t list_index = player_index + 1;
352  if (list_index < 0 || static_cast<size_t>(list_index) >= global_modifiers.size()) return nullptr;
353  // get from player and fall back to global list
354  C4SoundModifier *result = global_modifiers[list_index];
355  if (!result && list_index) result = global_modifiers[0];
356  return result;
357 }
C4Application Application
Definition: C4Globals.cpp:44
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
C4PropertyName
@ P_Equalizer_Low_Gain
@ P_Echo_Delay
@ P_Echo_Feedback
@ P_Reverb_Diffusion
@ P_Reverb_Late_Reverb_Gain
@ P_Reverb_GainHF
@ P_Echo_Spread
@ P_Equalizer_Mid1_Gain
@ P_Reverb_Late_Reverb_Delay
@ P_Reverb_Reflections_Delay
@ P_Reverb_Room_Rolloff_Factor
@ P_Reverb_Decay_HFRatio
@ P_Reverb_Gain
@ P_Equalizer_Mid2_Center
@ P_Reverb_Density
@ P_Equalizer_Mid2_Width
@ P_Reverb_Decay_Time
@ P_Reverb_Reflections_Gain
@ P_Type
@ P_Reverb_Decay_HFLimit
@ P_Equalizer_High_Gain
@ P_Equalizer_Mid1_Center
@ P_Equalizer_Mid2_Gain
@ P_Equalizer_Mid1_Width
@ P_Equalizer_Low_Cutoff
@ P_Reverb_Air_Absorption_GainHF
@ P_Equalizer_High_Cutoff
@ P_Echo_LRDelay
@ P_Echo_Damping
C4MusicSystem MusicSystem
Definition: C4Application.h:41
C4SoundSystem SoundSystem
Definition: C4Application.h:42
void SelectContext()
ALCdevice * GetDevice() const
Definition: C4MusicSystem.h:90
int32_t GetPropertyInt(C4PropertyName k, int32_t default_val=0) const
Definition: C4PropList.cpp:855
void Update() override
C4SoundModifierEcho(C4PropList *in_props)
C4SoundModifierEqualizer(C4PropList *in_props)
float GetFloatProp(C4PropertyName key, float ratio, float default_value)
bool GetBoolProp(C4PropertyName key, bool default_value)
virtual ~C4SoundModifier()
virtual void Update()
int32_t GetRefCount() const
void ApplyTo(ALuint source)
C4SoundModifier(C4PropList *in_props)
C4SoundModifier * Get(class C4PropList *props, bool create_if_not_found)
void Add(C4SoundModifier *new_modifier)
void Remove(C4SoundModifier *prev_modifier)
C4SoundModifier * GetGlobalModifier(int32_t player_index) const
void SetGlobalModifier(C4SoundModifier *new_modifier, int32_t player_index)
C4SoundModifierReverb(C4PropList *in_props)
C4SoundInstance * GetFirstInstance() const
C4SoundInstance * GetNextInstance(C4SoundInstance *prev) const
C4SoundModifierList Modifiers
Definition: C4SoundSystem.h:54
void SetPropList(C4PropList *PropList)
Definition: C4Value.h:141
C4PropList * _getPropList() const
Definition: C4Value.h:129
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:190
const char * getData() const
Definition: StdBuf.h:442
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
size_t getLength() const
Definition: StdBuf.h:445