1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 
21 #include "tracklib.h"
22 #include "lib/framework/frame.h"
23 #include "lib/framework/frameresource.h"
24 #include "audio_id.h"
25 #include "src/droid.h"
26 
27 //*
28 //
29 // defines
30 #define MAX_TRACKS	( 600 )
31 
32 //*
33 //
34 // static global variables
35 // array of pointers to sound effects
36 static TRACK			*g_apTrack[MAX_TRACKS];
37 
38 // number of tracks loaded
39 static SDWORD			g_iCurTracks = 0;
40 
41 // flag set when system is active (for callbacks etc)
42 static bool				g_bSystemActive = false;
43 static AUDIO_CALLBACK	g_pStopTrackCallback = nullptr;
44 
45 //*
46 // =======================================================================================================================
47 // =======================================================================================================================
48 //
sound_Init(HRTFMode hrtf)49 bool sound_Init(HRTFMode hrtf)
50 {
51 	g_iCurTracks = 0;
52 	if (!sound_InitLibrary(hrtf))
53 	{
54 		debug(LOG_ERROR, "Cannot init sound library");
55 		return false;
56 	}
57 
58 	// init audio array (with NULL pointers; which calloc ensures by setting all allocated memory to zero)
59 	memset(g_apTrack, 0, sizeof(g_apTrack));
60 
61 	// set system active flag for callbacks
62 	g_bSystemActive = true;
63 	return true;
64 }
65 
66 //*
67 // =======================================================================================================================
68 // =======================================================================================================================
69 //
sound_Shutdown(void)70 bool sound_Shutdown(void)
71 {
72 	// set inactive flag to prevent callbacks coming after shutdown
73 	g_bSystemActive = false;
74 	sound_ShutdownLibrary();
75 	return true;
76 }
77 
78 //*
79 // =======================================================================================================================
80 // =======================================================================================================================
81 //
sound_GetSystemActive(void)82 bool sound_GetSystemActive(void)
83 {
84 	return g_bSystemActive;
85 }
86 
87 /** Retrieves loaded audio files and retrieves a TRACK from them on which some values are set and returns their respective ID numbers
88  *  \param fileName the filename of the track
89  *  \param loop whether the track should be looped until explicitly stopped
90  *  \param volume the volume this track should be played on (range is 0-100)
91  *  \param audibleRadius the radius from the source of sound where it can be heard
92  *  \return a non-zero value representing the track id number when successful, zero when the file is not found or no more tracks can be loaded (i.e. the limit is reached)
93  */
sound_SetTrackVals(const char * fileName,bool loop,unsigned int volume,unsigned int audibleRadius)94 unsigned int sound_SetTrackVals(const char *fileName, bool loop, unsigned int volume, unsigned int audibleRadius)
95 {
96 	unsigned int trackID;
97 	TRACK *psTrack;
98 
99 	if (fileName == nullptr || strlen(fileName) == 0) // check for empty filename.  This is a non fatal error.
100 	{
101 		debug(LOG_WARNING, "fileName is %s", (fileName == nullptr) ? "a NULL pointer" : "empty");
102 		return 0;
103 	}
104 
105 	psTrack = (TRACK *)resGetData("WAV", fileName);
106 	if (psTrack == nullptr)
107 	{
108 		debug(LOG_WARNING, "track %s resource not found", fileName);
109 		return 0;
110 	}
111 
112 	// get pre-assigned ID or produce one
113 	trackID = audio_GetIDFromStr(fileName);
114 	if (trackID == NO_SOUND)
115 	{
116 		// No pre-assigned ID available, produce one
117 		trackID = sound_GetAvailableID();
118 		if (trackID == SAMPLE_NOT_ALLOCATED)
119 		{
120 			return 0;
121 		}
122 	}
123 
124 	if (g_apTrack[trackID] != nullptr)
125 	{
126 		debug(LOG_ERROR, "sound_SetTrackVals: track %i already set (filename: \"%s\"\n", trackID, g_apTrack[trackID]->fileName);
127 		return 0;
128 	}
129 
130 	// set track members
131 	psTrack->bLoop = loop;
132 	psTrack->iVol = volume;
133 	psTrack->iAudibleRadius = audibleRadius;
134 
135 	// RAII: Make sure to initialize all values (we don't want undefined values)!
136 	psTrack->iTime = 0;
137 	psTrack->iTimeLastFinished = 0;
138 	psTrack->iNumPlaying = 0;
139 
140 	// set global
141 	g_apTrack[trackID] = psTrack;
142 
143 	// increment counter for amount of loaded tracks
144 	++g_iCurTracks;
145 
146 	return trackID;
147 }
148 
149 //*
150 // =======================================================================================================================
151 // =======================================================================================================================
152 //
sound_ReleaseTrack(TRACK * psTrack)153 void sound_ReleaseTrack(TRACK *psTrack)
154 {
155 	TRACK **currTrack;
156 
157 	// This is here to save CPU wasted by the loop below;
158 	// Calling this function with psTrack = NULL is perfectly legal,
159 	// with or without this check
160 	if (!psTrack)
161 	{
162 		return;
163 	}
164 
165 	// Run through the list of tracks and set the pointer to the track which is to be released to NULL
166 	for (currTrack = &g_apTrack[0]; currTrack != &g_apTrack[MAX_TRACKS]; ++currTrack)
167 	{
168 		if (*currTrack == psTrack)
169 		{
170 			*currTrack = nullptr;
171 		}
172 	}
173 
174 	sound_FreeTrack(psTrack);
175 	free(psTrack);
176 }
177 
178 //*
179 // =======================================================================================================================
180 // =======================================================================================================================
181 //
sound_CheckAllUnloaded(void)182 void sound_CheckAllUnloaded(void)
183 {
184 	TRACK **currTrack;
185 
186 	for (currTrack = &g_apTrack[0]; currTrack != &g_apTrack[MAX_TRACKS]; ++currTrack)
187 	{
188 		ASSERT(*currTrack == nullptr, "A track is not unloaded yet (%s); check audio.cfg for duplicate IDs", (*currTrack)->fileName);
189 	}
190 }
191 
192 //*
193 // =======================================================================================================================
194 // =======================================================================================================================
195 //
sound_TrackLooped(SDWORD iTrack)196 bool sound_TrackLooped(SDWORD iTrack)
197 {
198 	sound_CheckTrack(iTrack);
199 	return g_apTrack[iTrack]->bLoop;
200 }
201 
202 //*
203 // =======================================================================================================================
204 // =======================================================================================================================
205 //
sound_GetNumPlaying(SDWORD iTrack)206 SDWORD sound_GetNumPlaying(SDWORD iTrack)
207 {
208 	sound_CheckTrack(iTrack);
209 	return g_apTrack[iTrack]->iNumPlaying;
210 }
211 
212 //*
213 // =======================================================================================================================
214 // =======================================================================================================================
215 //
sound_CheckTrack(SDWORD iTrack)216 bool sound_CheckTrack(SDWORD iTrack)
217 {
218 	if (iTrack < 0 || iTrack > g_iCurTracks - 1)
219 	{
220 		debug(LOG_SOUND, "Track number %i outside max %i\n", iTrack, g_iCurTracks);
221 		return false;
222 	}
223 
224 	if (g_apTrack[iTrack] == nullptr)
225 	{
226 		debug(LOG_SOUND, "Track %i NULL\n", iTrack);
227 		return false;
228 	}
229 
230 	return true;
231 }
232 
233 //*
234 // =======================================================================================================================
235 // =======================================================================================================================
236 //
sound_GetTrackTime(SDWORD iTrack)237 SDWORD sound_GetTrackTime(SDWORD iTrack)
238 {
239 	sound_CheckTrack(iTrack);
240 	return g_apTrack[iTrack]->iTime;
241 }
242 
243 //*
244 // =======================================================================================================================
245 // =======================================================================================================================
246 //
sound_GetTrackVolume(SDWORD iTrack)247 SDWORD sound_GetTrackVolume(SDWORD iTrack)
248 {
249 	sound_CheckTrack(iTrack);
250 	return g_apTrack[iTrack]->iVol;
251 }
252 
253 //*
254 // =======================================================================================================================
255 // =======================================================================================================================
256 //
sound_GetTrackAudibleRadius(SDWORD iTrack)257 SDWORD sound_GetTrackAudibleRadius(SDWORD iTrack)
258 {
259 	sound_CheckTrack(iTrack);
260 	return g_apTrack[iTrack]->iAudibleRadius;
261 }
262 
263 //*
264 // =======================================================================================================================
265 // =======================================================================================================================
266 //
sound_GetTrackName(SDWORD iTrack)267 const char *sound_GetTrackName(SDWORD iTrack)
268 {
269 	// If we get an invalid track ID or there are
270 	// currently no tracks loaded then return NULL
271 	if (iTrack <= 0
272 	    || iTrack >= MAX_TRACKS
273 	    || iTrack == SAMPLE_NOT_FOUND
274 	    || g_apTrack[iTrack] == nullptr)
275 	{
276 		return nullptr;
277 	}
278 
279 	return g_apTrack[iTrack]->fileName;
280 }
281 
282 //*
283 // =======================================================================================================================
284 // =======================================================================================================================
285 //
sound_Play2DTrack(AUDIO_SAMPLE * psSample,bool bQueued)286 bool sound_Play2DTrack(AUDIO_SAMPLE *psSample, bool bQueued)
287 {
288 	TRACK	*psTrack;
289 
290 	// Check to make sure the requested track is loaded
291 	if (!sound_CheckTrack(psSample->iTrack))
292 	{
293 		return false;
294 	}
295 
296 	psTrack = g_apTrack[psSample->iTrack];
297 	return sound_Play2DSample(psTrack, psSample, bQueued);
298 }
299 
300 //*
301 // =======================================================================================================================
302 // =======================================================================================================================
303 //
sound_Play3DTrack(AUDIO_SAMPLE * psSample)304 bool sound_Play3DTrack(AUDIO_SAMPLE *psSample)
305 {
306 	TRACK	*psTrack;
307 
308 	// Check to make sure the requested track is loaded
309 	if (!sound_CheckTrack(psSample->iTrack))
310 	{
311 		return false;
312 	}
313 
314 	psTrack = g_apTrack[psSample->iTrack];
315 	return sound_Play3DSample(psTrack, psSample);
316 }
317 
318 //*
319 // =======================================================================================================================
320 // =======================================================================================================================
321 //
sound_StopTrack(AUDIO_SAMPLE * psSample)322 void sound_StopTrack(AUDIO_SAMPLE *psSample)
323 {
324 	ASSERT_OR_RETURN(, psSample != nullptr, "Sample pointer invalid");
325 
326 	sound_StopSample(psSample);
327 
328 	// do stopped callback
329 	if (g_pStopTrackCallback != nullptr && psSample->psObj != nullptr)
330 	{
331 		g_pStopTrackCallback(psSample->psObj);
332 	}
333 }
334 
335 //*
336 // =======================================================================================================================
337 // =======================================================================================================================
338 //
sound_PauseTrack(AUDIO_SAMPLE * psSample)339 void sound_PauseTrack(AUDIO_SAMPLE *psSample)
340 {
341 	sound_StopSample(psSample);
342 }
343 
344 //*
345 // =======================================================================================================================
346 // =======================================================================================================================
347 //
sound_FinishedCallback(AUDIO_SAMPLE * psSample)348 void sound_FinishedCallback(AUDIO_SAMPLE *psSample)
349 {
350 	ASSERT(psSample != nullptr, "sound_FinishedCallback: sample pointer invalid\n");
351 
352 	if (g_apTrack[psSample->iTrack] != nullptr)
353 	{
354 		g_apTrack[psSample->iTrack]->iTimeLastFinished = sound_GetGameTime();
355 	}
356 
357 	// call user callback if specified
358 	if (psSample->pCallback != nullptr)
359 	{
360 		psSample->pCallback(psSample->psObj);
361 		// NOTE: we must invalidate the iAudioID (iTrack) so the game knows to add the
362 		// sample back into the queue.  This is a bit of a hacky way to handle "looped" samples,
363 		// since currently, the game don't have a decent way to handle this.
364 		if (psSample->psObj)
365 		{
366 			droidAudioTrackStopped((void *)psSample->psObj);
367 		}
368 	}
369 
370 	// set finished flag
371 	psSample->bFinishedPlaying = true;
372 }
373 
374 //*
375 // =======================================================================================================================
376 // =======================================================================================================================
377 //
sound_GetTrackID(TRACK * psTrack)378 SDWORD sound_GetTrackID(TRACK *psTrack)
379 {
380 	unsigned int i;
381 
382 	if (psTrack == nullptr)
383 	{
384 		return SAMPLE_NOT_FOUND;
385 	}
386 
387 	// find matching track
388 	for (i = 0; i < MAX_TRACKS; ++i)
389 	{
390 		if (g_apTrack[i] == psTrack)
391 		{
392 			break;
393 		}
394 	}
395 
396 	// if matching track found return it else find empty track
397 	if (i >= MAX_TRACKS)
398 	{
399 		return SAMPLE_NOT_FOUND;
400 	}
401 
402 	return i;
403 }
404 
405 //*
406 // =======================================================================================================================
407 // =======================================================================================================================
408 //
sound_GetAvailableID()409 SDWORD sound_GetAvailableID()
410 {
411 	unsigned int i;
412 
413 	// Iterate through the list of tracks until we find an unused ID slot
414 	for (i = ID_SOUND_NEXT; i < MAX_TRACKS; ++i)
415 	{
416 		if (g_apTrack[i] == nullptr)
417 		{
418 			break;
419 		}
420 	}
421 
422 	ASSERT(i < MAX_TRACKS, "sound_GetTrackID: unused track not found!");
423 	if (i >= MAX_TRACKS)
424 	{
425 		return SAMPLE_NOT_ALLOCATED;
426 	}
427 
428 	return i;
429 }
430 
431 //*
432 // =======================================================================================================================
433 // =======================================================================================================================
434 //
sound_SetStoppedCallback(AUDIO_CALLBACK pStopTrackCallback)435 void sound_SetStoppedCallback(AUDIO_CALLBACK pStopTrackCallback)
436 {
437 	g_pStopTrackCallback = pStopTrackCallback;
438 }
439 
440 //*
441 // =======================================================================================================================
442 // =======================================================================================================================
443 //
sound_GetTrackTimeLastFinished(SDWORD iTrack)444 UDWORD sound_GetTrackTimeLastFinished(SDWORD iTrack)
445 {
446 	sound_CheckTrack(iTrack);
447 	return g_apTrack[iTrack]->iTimeLastFinished;
448 }
449 
450 //*
451 // =======================================================================================================================
452 // =======================================================================================================================
453 //
sound_SetTrackTimeLastFinished(SDWORD iTrack,UDWORD iTime)454 void sound_SetTrackTimeLastFinished(SDWORD iTrack, UDWORD iTime)
455 {
456 	sound_CheckTrack(iTrack);
457 	g_apTrack[iTrack]->iTimeLastFinished = iTime;
458 }
459