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