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