OpenClonk
C4SoundInstance.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) 2009-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"
24 #include "game/C4Viewport.h"
25 #include "lib/C4Random.h"
26 #include "object/C4Object.h"
29 
30 using namespace C4SoundLoaders;
31 
33 {
34  Name[0]=0;
35 }
36 
38 {
39  Clear();
40 }
41 
43 {
44  while (FirstInst) RemoveInst(FirstInst);
45 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
46  if (pSample) Mix_FreeChunk(pSample);
47 #elif AUDIO_TK == AUDIO_TK_OPENAL
48  if (pSample) alDeleteBuffers(1, &pSample);
49 #endif
50  pSample = 0;
51 }
52 
53 bool C4SoundEffect::Load(const char *szFileName, C4Group &hGroup, const char *namespace_prefix)
54 {
55  // Sound check
56  if (!Config.Sound.RXSound) return false;
57  // Locate sound in file
58  StdBuf WaveBuffer;
59  if (!hGroup.LoadEntry(szFileName, &WaveBuffer)) return false;
60  // load it from mem
61  if (!Load((BYTE*)WaveBuffer.getMData(), WaveBuffer.getSize())) return false;
62  // Set name
63  if (namespace_prefix)
64  {
65  // Local sound name
66  SAppend(namespace_prefix, Name, C4MaxSoundName);
67  SAppend("::", Name, C4MaxSoundName);
68  SAppend(szFileName, Name, C4MaxSoundName);
69  }
70  else
71  {
72  // Global sound name
73  SCopy(szFileName, Name, C4MaxSoundName);
74  }
75  return true;
76 }
77 
78 bool C4SoundEffect::Load(BYTE *pData, size_t iDataLen, bool fRaw)
79 {
80  // Sound check
81  if (!Config.Sound.RXSound) return false;
82 
83  SoundInfo info;
84  int32_t options = 0;
85  if (fRaw)
86  options |= SoundLoader::OPTION_Raw;
87  for (SoundLoader* loader = SoundLoader::first_loader; loader; loader = loader->next)
88  {
89  if (loader->ReadInfo(&info, pData, iDataLen))
90  {
91  if (info.final_handle)
92  {
93  // loader supplied the handle specific to the sound system used; just assign to pSample
94  pSample = info.final_handle;
95  }
96  else
97  {
98 #if AUDIO_TK == AUDIO_TK_OPENAL
100  alGenBuffers(1, &pSample);
101  alBufferData(pSample, info.format, &info.sound_data[0], info.sound_data.size(), info.sample_rate);
102 #else
103  Log("SoundLoader does not provide a ready-made handle");
104 #endif
105  }
106  SampleRate = info.sample_rate;
107  Length = info.sample_length*1000;
108  break;
109  }
110  }
111  *Name = '\0';
112  return !!pSample;
113 }
114 
116 {
117  // check for instances that have stopped and volume changes
118  for (C4SoundInstance *pInst = FirstInst; pInst; )
119  {
120  C4SoundInstance *pNext = pInst->pNext;
121  if (!pInst->Playing())
122  RemoveInst(pInst);
123  else
124  pInst->Execute();
125  pInst = pNext;
126  }
127 }
128 
129 C4SoundInstance *C4SoundEffect::New(bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
130 {
131  // check: too many instances?
132  if (!fLoop && Instances >= C4MaxSoundInstances) return nullptr;
133  // create & init sound instance
134  C4SoundInstance *pInst = new C4SoundInstance();
135  if (!pInst->Create(this, fLoop, iVolume, pObj, 0, iCustomFalloffDistance, iPitch, modifier)) { delete pInst; return nullptr; }
136  // add to list
137  AddInst(pInst);
138  // return
139  return pInst;
140 }
141 
143 {
144  for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext)
145  if (pInst->getObj() == pObj)
146  return pInst;
147  return nullptr;
148 }
149 
151 {
152  for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext)
153  pInst->ClearPointers(pObj);
154 }
155 
156 int32_t C4SoundEffect::GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad)
157 {
158  int32_t cnt = 0;
159  for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext)
160  if (pInst->isStarted() && pInst->getObj() && pInst->Inside(iX, iY, iRad))
161  cnt++;
162  return cnt;
163 }
164 
166 {
167  int32_t cnt = 0;
168  for (C4SoundInstance *pInst = FirstInst; pInst; pInst = pInst->pNext)
169  if (pInst->isStarted() && pInst->Playing() && !pInst->getObj())
170  cnt++;
171  return cnt;
172 }
173 
175 {
176  pInst->pNext = FirstInst;
177  FirstInst = pInst;
178  Instances++;
179 }
181 {
182  if (pInst == FirstInst)
183  FirstInst = pInst->pNext;
184  else
185  {
186  C4SoundInstance *pPos = FirstInst;
187  while (pPos && pPos->pNext != pInst) pPos = pPos->pNext;
188  if (pPos)
189  pPos->pNext = pInst->pNext;
190  }
191  delete pInst;
192  Instances--;
193 }
194 
195 
198 {
199 }
200 
202 {
203  Clear();
204 }
205 
207 {
208  Stop();
209  iChannel = -1;
210  if (modifier)
211  {
212  modifier->DelRef();
213  modifier = nullptr;
214  has_local_modifier = false;
215  }
216 }
217 
218 bool C4SoundInstance::Create(C4SoundEffect *pnEffect, bool fLoop, int32_t inVolume, C4Object *pnObj, int32_t inNearInstanceMax, int32_t iFalloffDistance, int32_t inPitch, C4SoundModifier *modifier)
219 {
220  // Sound check
221  if (!Config.Sound.RXSound || !pnEffect) return false;
222  // Already playing? Stop
223  if (Playing()) { Stop(); return false; }
224  // Set effect
225  pEffect = pnEffect;
226  // Set
228  iVolume = inVolume; iPan = 0; iChannel = -1;
229  iPitch = inPitch; pitch_dirty = (iPitch != 0);
230  iNearInstanceMax = inNearInstanceMax;
231  this->iFalloffDistance = iFalloffDistance;
232  pObj = pnObj;
233  fLooping = fLoop;
234  if ((this->modifier = modifier))
235  {
236  modifier->AddRef();
237  has_local_modifier = true;
238  }
239  SetPlayer(NO_OWNER); // may be updated on first execution
240  // Start
241  Execute();
242  return true;
243 }
244 
245 void C4SoundInstance::SetPitch(int32_t inPitch)
246 {
247  // set pitch and update on next call to Execute
248  iPitch = inPitch;
249  pitch_dirty = true;
250 }
251 
253 {
254  // already started?
255  if (isStarted()) return true;
256  // don't bother if half the time is up and the sound is not looping
258  return false;
259  // do near-instances check
260  int32_t iNearInstances = pObj ? pEffect->GetStartedInstanceCount(pObj->GetX(), pObj->GetY(), C4NearSoundRadius)
262  // over maximum?
263  if (iNearInstances > iNearInstanceMax) return false;
264  // Start
265  return Start();
266 }
267 
269 {
270 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
271  // Be paranoid about SDL_Mixer initialisation
272  if (!Application.MusicSystem.MODInitialized) return false;
273  if ((iChannel = Mix_PlayChannel(-1, pEffect->pSample, fLooping? -1 : 0)) == -1)
274  return false;
275 #elif AUDIO_TK == AUDIO_TK_OPENAL
277  alGenSources(1, (ALuint*)&iChannel);
278  alSourcei(iChannel, AL_BUFFER, pEffect->pSample);
279  alSourcei(iChannel, AL_LOOPING, fLooping ? AL_TRUE : AL_FALSE);
281  alSourcePlay(iChannel);
282 #else
283  return false;
284 #endif
285  // Safety: Don't execute if start failed, or Execute() would try to start again
286  if (!isStarted()) return false;
287  // Update volume
288  Execute();
289  return true;
290 }
291 
293 {
294  if (!pEffect) return false;
295  // Stop sound
296  bool fRet = true;
297 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
298  // iChannel == -1 will halt all channels. Is that right?
299  if (Playing())
300  Mix_HaltChannel(iChannel);
301 #elif AUDIO_TK == AUDIO_TK_OPENAL
302  if (iChannel != -1)
303  {
304  if (Playing()) alSourceStop(iChannel);
305  ALuint c = iChannel;
306  alDeleteSources(1, &c);
307  }
308 #endif
309  iChannel = -1;
310  tStarted = 0;
311  fLooping = false;
312  return fRet;
313 }
314 
316 {
317  if (!pEffect) return false;
318  if (fLooping) return true;
319 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
320  return Application.MusicSystem.MODInitialized && (iChannel != -1) && Mix_Playing(iChannel);
321 #elif AUDIO_TK == AUDIO_TK_OPENAL
322  if (iChannel == -1)
323  return false;
324  else
325  {
326  ALint state;
327  alGetSourcei(iChannel, AL_SOURCE_STATE, &state);
328  return state == AL_PLAYING;
329  }
330 #endif
331  return false;
332 }
333 
335 {
336  // Object deleted?
337  if (pObj && !pObj->Status) ClearPointers(pObj);
338  // initial values
339  int32_t iVol = iVolume * 256 * Config.Sound.SoundVolume / 100, iPan = C4SoundInstance::iPan;
340  // bound to an object?
341  if (pObj)
342  {
343  int iAudibility = pObj->Audible;
345  // apply custom falloff distance
346  if (iFalloffDistance)
347  {
348  iAudibility = Clamp<int32_t>(100 + (iAudibility - 100) * C4AudibilityRadius / iFalloffDistance, 0,100);
349  }
350  iVol = iVol * iAudibility / 100;
351  iPan += pObj->AudiblePan;
352  }
353  // sound off?
354  if (!iVol)
355  {
356  // stop, if started
357  if (isStarted())
358  {
359 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
360  Mix_HaltChannel(iChannel);
361 #elif AUDIO_TK == AUDIO_TK_OPENAL
362  alDeleteSources(1, (ALuint*)&iChannel);
363 #endif
364  iChannel = -1;
365  }
366  }
367  else
368  {
369  // start
370  if (!isStarted())
371  if (!CheckStart())
372  return;
373  // set volume & panning
374 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
375  Mix_Volume(iChannel, (iVol * MIX_MAX_VOLUME) / (100 * 256));
376  Mix_SetPanning(iChannel, Clamp((100 - iPan) * 256 / 100, 0, 255), Clamp((100 + iPan) * 256 / 100, 0, 255));
377 #elif AUDIO_TK == AUDIO_TK_OPENAL
378  alSource3f(iChannel, AL_POSITION, Clamp<float>(float(iPan)/100.0f, -1.0f, +1.0f), 0, -0.7f);
379  alSourcef(iChannel, AL_GAIN, float(iVol) / (100.0f*256.0f));
380  if (pitch_dirty)
381  {
382  // set pitch; map -90..+100 range to 0.1f..2.0f
383  alSourcef(iChannel, AL_PITCH, std::max(float(iPitch + 100) / 100.0f, 0.1f));
384  pitch_dirty = false;
385  }
386 #endif
387  }
388 }
389 
390 void C4SoundInstance::SetVolumeByPos(int32_t x, int32_t y, int32_t relative_vol)
391 {
392  int32_t vol_player = NO_OWNER;
393  iVolume = ::Viewports.GetAudibility(x, y, &iPan, 0, &vol_player) * relative_vol / 100.0f;
394  if (vol_player != player) SetPlayer(vol_player);
395 }
396 
398 {
399  if (!Playing()) { Stop(); return; }
400  if (pObj == pDelete)
401  {
402  // stop if looping (would most likely loop forever)
403  if (fLooping)
404  Stop();
405  // otherwise: set volume by last position
406  else
407  SetVolumeByPos(pObj->GetX(), pObj->GetY());
408  pObj = nullptr;
409  }
410 }
411 
412 bool C4SoundInstance::Inside(int32_t iX, int32_t iY, int32_t iRad)
413 {
414  return pObj &&
415  (pObj->GetX() - iX) * (pObj->GetX() - iX) + (pObj->GetY() - iY) * (pObj->GetY() - iY) <= iRad * iRad;
416 }
417 
418 void C4SoundInstance::SetModifier(C4SoundModifier *new_modifier, bool is_global)
419 {
420  // do not overwrite local modifier with global one
421  if (is_global)
422  {
423  if (has_local_modifier) return;
424  }
425  else
426  {
427  // this sound has its own modifier now and doesn't care for global ones
428  has_local_modifier = (new_modifier != nullptr);
429  }
430  if (new_modifier != modifier)
431  {
432  // update modifier and ref-count
433  C4SoundModifier *old_modifier = modifier;
434  modifier = new_modifier;
435  if (modifier) modifier->AddRef();
436  if (old_modifier) old_modifier->DelRef();
437  // Apply new modifier
438  if (isStarted())
439  {
440  if (modifier)
441  {
442 #if AUDIO_TK == AUDIO_TK_OPENAL
444 #endif
445  }
446  else
447  {
448 #if (AUDIO_TK == AUDIO_TK_OPENAL) && defined(HAVE_ALEXT)
449  alSource3i(iChannel, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL);
450 #endif
451  }
452  }
453  }
454 }
455 
456 void C4SoundInstance::SetPlayer(int32_t new_player)
457 {
458  // update player and associated sound modifier
459  player = new_player;
461 }
C4Config Config
Definition: C4Config.cpp:930
const int NO_OWNER
Definition: C4Constants.h:137
C4Application Application
Definition: C4Globals.cpp:44
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
const int32_t C4AudibilityRadius
Definition: C4SoundSystem.h:30
const int32_t C4MaxSoundInstances
Definition: C4SoundSystem.h:28
const int32_t C4NearSoundRadius
Definition: C4SoundSystem.h:29
const int32_t C4MaxSoundName
Definition: C4SoundSystem.h:27
C4ViewportList Viewports
uint8_t BYTE
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:263
T Clamp(T bval, T lbound, T rbound)
Definition: Standard.h:44
C4MusicSystem MusicSystem
Definition: C4Application.h:41
C4SoundSystem SoundSystem
Definition: C4Application.h:42
C4ConfigSound Sound
Definition: C4Config.h:258
int32_t RXSound
Definition: C4Config.h:126
int32_t SoundVolume
Definition: C4Config.h:132
bool LoadEntry(const char *entry_name, char **buffer, size_t *size_info=nullptr, int zeros_to_append=0)
Definition: C4Group.cpp:2375
void SelectContext()
int32_t Audible
Definition: C4Object.h:119
int32_t GetX() const
Definition: C4Object.h:285
int32_t AudiblePan
Definition: C4Object.h:119
int32_t GetY() const
Definition: C4Object.h:286
int32_t AudiblePlayer
Definition: C4Object.h:119
int32_t Status
Definition: C4PropList.h:173
void RemoveInst(C4SoundInstance *pInst)
bool Load(const char *szFileName, C4Group &hGroup, const char *namespace_prefix)
int32_t GetStartedInstanceCount()
C4SoundInstance * New(bool fLoop=false, int32_t iVolume=100, C4Object *pObj=nullptr, int32_t iCustomFalloffDistance=0, int32_t iPitch=0, C4SoundModifier *modifier=nullptr)
void ClearPointers(C4Object *pObj)
C4SoundInstance * GetInstance(C4Object *pObj)
C4SoundHandle pSample
void AddInst(C4SoundInstance *pInst)
int32_t GetStartedInstanceCount(int32_t iX, int32_t iY, int32_t iRad)
void SetVolumeByPos(int32_t x, int32_t y, int32_t relative_volume=100)
void SetPitch(int32_t inPitch)
bool Create(C4SoundEffect *pEffect, bool fLoop=false, int32_t iVolume=100, C4Object *pObj=nullptr, int32_t iNearInstanceMax=0, int32_t iFalloffDistance=0, int32_t inPitch=0, C4SoundModifier *modifier=nullptr)
C4SoundInstance * pNext
int32_t iNearInstanceMax
C4SoundModifier * modifier
C4TimeMilliseconds tStarted
C4SoundEffect * pEffect
void SetModifier(C4SoundModifier *new_modifier, bool is_global)
void ClearPointers(C4Object *pObj)
int32_t iFalloffDistance
void SetPlayer(int32_t new_player)
bool isStarted() const
bool Inside(int32_t iX, int32_t iY, int32_t iRad)
static SoundLoader * first_loader
static const int OPTION_Raw
void ApplyTo(ALuint source)
C4SoundModifier * GetGlobalModifier(int32_t player_index) const
C4SoundModifierList Modifiers
Definition: C4SoundSystem.h:54
static C4TimeMilliseconds Now()
int32_t GetAudibility(int32_t x, int32_t y, int32_t *pan, int32_t audibility_radius=0, int32_t *out_player=nullptr)
Definition: StdBuf.h:30
size_t getSize() const
Definition: StdBuf.h:101
void * getMData()
Definition: StdBuf.h:100
std::vector< BYTE > sound_data