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"
21 #include "platform/C4SoundSystem.h"
22
23 #include "lib/C4Random.h"
24 #include "game/C4Application.h"
25 #include "game/C4Viewport.h"
26 #include "object/C4Object.h"
27 #include "platform/C4SoundIncludes.h"
28 #include "platform/C4SoundInstance.h"
29 #include "platform/C4SoundLoaders.h"
30
31 C4SoundSystem::C4SoundSystem() = default;
32
33 C4SoundSystem::~C4SoundSystem() = default;
34
Init()35 bool C4SoundSystem::Init()
36 {
37 if (!Application.MusicSystem.MODInitialized &&
38 !Application.MusicSystem.InitializeMOD())
39 return false;
40
41 // Might be reinitialisation
42 ClearEffects();
43 // (re)init EFX
44 Modifiers.Init();
45 // Open sound file
46 if (!SoundFile.IsOpen())
47 if (!Reloc.Open(SoundFile, C4CFN_Sound)) return false;
48 // Load static sound from Sound.ocg
49 LoadEffects(SoundFile, nullptr, false);
50 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
51 Mix_AllocateChannels(C4MaxSoundInstances);
52 #endif
53 return true;
54 }
55
Clear()56 void C4SoundSystem::Clear()
57 {
58 ClearEffects();
59 Modifiers.Clear();
60 // Close sound file
61 SoundFile.Close();
62 }
63
ClearEffects()64 void C4SoundSystem::ClearEffects()
65 {
66 // Clear sound bank
67 C4SoundEffect *csfx,*next;
68 for (csfx=FirstSound; csfx; csfx=next)
69 {
70 next=csfx->Next;
71 delete csfx;
72 }
73 FirstSound=nullptr;
74 }
75
Execute()76 void C4SoundSystem::Execute()
77 {
78 #if AUDIO_TK == AUDIO_TK_OPENAL
79 Application.MusicSystem.SelectContext();
80 #endif
81 C4SoundEffect *csfx;
82 for (csfx=FirstSound; csfx; csfx=csfx->Next)
83 {
84 // Instance removal check
85 csfx->Execute();
86 }
87 }
88
GetEffect(const char * szSndName)89 C4SoundEffect* C4SoundSystem::GetEffect(const char *szSndName)
90 {
91 // Remember wildcards before adding .* extension - if there are 2 versions with different file extensions, play the last added
92 bool bRandomSound = SCharCount('?',szSndName) || SCharCount('*',szSndName);
93 // Evaluate sound name
94 char szName[C4MaxSoundName+2+1];
95 SCopy(szSndName,szName,C4MaxSoundName);
96 // Any extension accepted
97 DefaultExtension(szName,"*");
98 // Play nth Sound. Standard: 1
99 int32_t iNumber = 1;
100 // Sound with a wildcard: determine number of available matches
101 if (bRandomSound)
102 {
103 iNumber = 0;
104 // Count matching sounds
105 for (C4SoundEffect *pSfx=FirstSound; pSfx; pSfx=pSfx->Next)
106 if (WildcardMatch(szName,pSfx->Name))
107 ++iNumber;
108 // Nothing found? Abort
109 if(iNumber == 0)
110 return nullptr;
111 iNumber=UnsyncedRandom(iNumber)+1;
112 }
113 // Find requested sound effect in bank
114 C4SoundEffect *pSfx;
115 for (pSfx=FirstSound; pSfx; pSfx=pSfx->Next)
116 if (WildcardMatch(szName,pSfx->Name))
117 if(!--iNumber)
118 break;
119 return pSfx; // Is still nullptr if nothing is found
120 }
121
NewEffect(const char * szSndName,bool fLoop,int32_t iVolume,C4Object * pObj,int32_t iCustomFalloffDistance,int32_t iPitch,C4SoundModifier * modifier)122 C4SoundInstance *C4SoundSystem::NewEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
123 {
124 // Sound not active
125 if (!Config.Sound.RXSound) return nullptr;
126 // Get sound
127 C4SoundEffect *csfx;
128 if (!(csfx = GetEffect(szSndName)))
129 {
130 // Warn about missing or incorrectly spelled sound to allow finding mistakes earlier.
131 #if !defined(USE_CONSOLE)
132 DebugLogF("Warning: could not find sound matching '%s'", szSndName);
133 #endif
134 return nullptr;
135 }
136 // Play
137 return csfx->New(fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch, modifier);
138 }
139
FindInstance(const char * szSndName,C4Object * pObj)140 C4SoundInstance *C4SoundSystem::FindInstance(const char *szSndName, C4Object *pObj)
141 {
142 char szName[C4MaxSoundName+2+1];
143 // Evaluate sound name (see GetEffect)
144 SCopy(szSndName,szName,C4MaxSoundName);
145 DefaultExtension(szName,"*");
146 // Find an effect with a matching instance
147 for (C4SoundEffect *csfx = FirstSound; csfx; csfx = csfx->Next)
148 if (WildcardMatch(szName, csfx->Name))
149 {
150 C4SoundInstance *pInst = csfx->GetInstance(pObj);
151 if (pInst) return pInst;
152 }
153 return nullptr;
154 }
155
156 // LoadEffects will load all sound effects of all known sound types (i.e. *.wav and *.ogg) as defined in C4CFN_SoundFiles
157
LoadEffects(C4Group & hGroup,const char * namespace_prefix,bool group_is_root)158 int32_t C4SoundSystem::LoadEffects(C4Group &hGroup, const char *namespace_prefix, bool group_is_root)
159 {
160 // Local definition sounds: If there is a Sound.ocg in the group, load the sound from there
161 if(group_is_root && hGroup.FindEntry(C4CFN_Sound))
162 {
163 C4Group g;
164 g.OpenAsChild(&hGroup, C4CFN_Sound, false, false);
165 return LoadEffects(g, namespace_prefix, false);
166 }
167 int32_t iNum=0;
168 char szFilename[_MAX_FNAME+1];
169 char szFileType[_MAX_FNAME+1];
170 C4SoundEffect *nsfx;
171 // Process segmented list of file types
172 for (int32_t i = 0; SCopySegment(C4CFN_SoundFiles, i, szFileType, '|', _MAX_FNAME); i++)
173 {
174 // Search all sound files in group
175 hGroup.ResetSearch();
176 while (hGroup.FindNextEntry(szFileType, szFilename))
177 // Create and load effect
178 if ((nsfx = new C4SoundEffect))
179 {
180 if (nsfx->Load(szFilename, hGroup, namespace_prefix))
181 {
182 // Overload same name effects
183 RemoveEffect(nsfx->Name);
184 // Add effect
185 nsfx->Next=FirstSound;
186 FirstSound=nsfx;
187 iNum++;
188 }
189 else
190 delete nsfx;
191 }
192 }
193 // Load subgroups from Sound.ocg and other subgroups
194 if (!group_is_root)
195 {
196 hGroup.ResetSearch();
197 while (hGroup.FindNextEntry(C4CFN_SoundSubgroups, szFilename))
198 {
199 // Load from subgroup as a sub-namespace
200 // get namespace name
201 StdStrBuf sub_namespace;
202 if (namespace_prefix)
203 {
204 sub_namespace.Copy(namespace_prefix);
205 sub_namespace.Append("::");
206 }
207 sub_namespace.Append(szFilename, strlen(szFilename) - strlen(C4CFN_SoundSubgroups) + 1);
208 // load from child group
209 C4Group subgroup;
210 if (subgroup.OpenAsChild(&hGroup, szFilename, false, false))
211 {
212 iNum += LoadEffects(subgroup, sub_namespace.getData(), false);
213 }
214 }
215 }
216 return iNum;
217 }
218
RemoveEffect(const char * szFilename)219 int32_t C4SoundSystem::RemoveEffect(const char *szFilename)
220 {
221 int32_t iResult=0;
222 C4SoundEffect *pNext,*pPrev=nullptr;
223 for (C4SoundEffect *pSfx=FirstSound; pSfx; pSfx=pNext)
224 {
225 pNext=pSfx->Next;
226 if (WildcardMatch(szFilename,pSfx->Name))
227 {
228 delete pSfx;
229 if (pPrev) pPrev->Next=pNext;
230 else FirstSound=pNext;
231 iResult++;
232 }
233 else
234 pPrev=pSfx;
235 }
236 return iResult;
237 }
238
ClearPointers(C4Object * pObj)239 void C4SoundSystem::ClearPointers(C4Object *pObj)
240 {
241 for (C4SoundEffect *pEff=FirstSound; pEff; pEff=pEff->Next)
242 pEff->ClearPointers(pObj);
243 }
244
GetFirstInstance() const245 C4SoundInstance *C4SoundSystem::GetFirstInstance() const
246 {
247 // Return by searching through effect linked list.
248 for (C4SoundEffect *pSfx = FirstSound; pSfx; pSfx = pSfx->Next)
249 if (pSfx->FirstInst) return pSfx->FirstInst;
250 return nullptr;
251 }
252
GetNextInstance(C4SoundInstance * prev) const253 C4SoundInstance *C4SoundSystem::GetNextInstance(C4SoundInstance *prev) const
254 {
255 // Return by searching through instance linked list and parent linked list of effects
256 assert(prev && prev->pEffect);
257 if (prev->pNext) return prev->pNext;
258 for (C4SoundEffect *pSfx = prev->pEffect->Next; pSfx; pSfx = pSfx->Next)
259 if (pSfx->FirstInst) return pSfx->FirstInst;
260 return nullptr;
261 }
262
StartSoundEffect(const char * szSndName,bool fLoop,int32_t iVolume,C4Object * pObj,int32_t iCustomFalloffDistance,int32_t iPitch,C4SoundModifier * modifier)263 C4SoundInstance *StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
264 {
265 // Sound check
266 if (!Config.Sound.RXSound) return nullptr;
267 // Start new
268 return Application.SoundSystem.NewEffect(szSndName, fLoop, iVolume, pObj, iCustomFalloffDistance, iPitch, modifier);
269 }
270
StartSoundEffectAt(const char * szSndName,int32_t iX,int32_t iY,int32_t iVolume,int32_t iCustomFallofDistance,int32_t iPitch,C4SoundModifier * modifier)271 C4SoundInstance *StartSoundEffectAt(const char *szSndName, int32_t iX, int32_t iY, int32_t iVolume, int32_t iCustomFallofDistance, int32_t iPitch, C4SoundModifier *modifier)
272 {
273 // Sound check
274 if (!Config.Sound.RXSound) return nullptr;
275 // Create
276 C4SoundInstance *pInst = StartSoundEffect(szSndName, false, iVolume, nullptr, iCustomFallofDistance, iPitch, modifier);
277 // Set volume by position
278 if (pInst) pInst->SetVolumeByPos(iX, iY, iVolume);
279 // Return
280 return pInst;
281 }
282
GetSoundInstance(const char * szSndName,C4Object * pObj)283 C4SoundInstance *GetSoundInstance(const char *szSndName, C4Object *pObj)
284 {
285 return Application.SoundSystem.FindInstance(szSndName, pObj);
286 }
287
StopSoundEffect(const char * szSndName,C4Object * pObj)288 void StopSoundEffect(const char *szSndName, C4Object *pObj)
289 {
290 // Find instance
291 C4SoundInstance *pInst = Application.SoundSystem.FindInstance(szSndName, pObj);
292 if (!pInst) return;
293 // Stop
294 pInst->Stop();
295 }
SoundLevel(const char * szSndName,C4Object * pObj,int32_t iLevel)296 void SoundLevel(const char *szSndName, C4Object *pObj, int32_t iLevel)
297 {
298 // Sound level zero? Stop
299 if (!iLevel) { StopSoundEffect(szSndName, pObj); return; }
300 // Find or create instance
301 C4SoundInstance *pInst = Application.SoundSystem.FindInstance(szSndName, pObj);
302 if (!pInst) pInst = StartSoundEffect(szSndName, true, iLevel, pObj);
303 if (!pInst) return;
304 // Set volume
305 pInst->SetVolume(iLevel);
306 pInst->Execute();
307 }
308
SoundPan(const char * szSndName,C4Object * pObj,int32_t iPan)309 void SoundPan(const char *szSndName, C4Object *pObj, int32_t iPan)
310 {
311 // Find instance
312 C4SoundInstance *pInst = Application.SoundSystem.FindInstance(szSndName, pObj);
313 if (!pInst) return;
314 // Set pan
315 pInst->SetPan(iPan);
316 pInst->Execute();
317 }
318
SoundPitch(const char * szSndName,C4Object * pObj,int32_t iPitch)319 void SoundPitch(const char *szSndName, C4Object *pObj, int32_t iPitch)
320 {
321 // Find instance
322 C4SoundInstance *pInst = Application.SoundSystem.FindInstance(szSndName, pObj);
323 if (!pInst) return;
324 // Set pitch
325 pInst->SetPitch(iPitch);
326 pInst->Execute();
327 }
328
SoundUpdate(C4SoundInstance * pInst,int32_t iLevel,int32_t iPitch)329 void SoundUpdate(C4SoundInstance *pInst, int32_t iLevel, int32_t iPitch)
330 {
331 // Set sound data
332 pInst->SetVolume(iLevel);
333 pInst->SetPitch(iPitch);
334 // Ensure it's reflected in audio engine
335 pInst->Execute();
336 }
337