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