1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4
5 Copyright (c) 2013-2016, 2019, 2021 Cong Xu
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10
11 Redistributions of source code must retain the above copyright notice, this
12 list of conditions and the following disclaimer.
13 Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "music.h"
30
31 #include <string.h>
32
33 #include <tinydir/tinydir.h>
34
35 #include "config.h"
36 #include "gamedata.h"
37 #include "log.h"
38
39 static void LoadMusic(CArray *tracks, const char *path);
MusicPlayerInit(MusicPlayer * mp)40 void MusicPlayerInit(MusicPlayer *mp)
41 {
42 memset(mp, 0, sizeof *mp);
43
44 // Load music
45 LoadMusic(&mp->generalTracks[MUSIC_MENU], "music/menu");
46 LoadMusic(&mp->generalTracks[MUSIC_BRIEFING], "music/briefing");
47 LoadMusic(&mp->generalTracks[MUSIC_GAME], "music/game");
48 LoadMusic(&mp->generalTracks[MUSIC_END], "music/end");
49 LoadMusic(&mp->generalTracks[MUSIC_LOSE], "music/lose");
50 LoadMusic(&mp->generalTracks[MUSIC_VICTORY], "music/victory");
51 }
LoadMusic(CArray * tracks,const char * path)52 static void LoadMusic(CArray *tracks, const char *path)
53 {
54 CArrayInit(tracks, sizeof(Mix_Music *));
55 tinydir_dir dir;
56 char buf[CDOGS_PATH_MAX];
57 GetDataFilePath(buf, path);
58 if (tinydir_open(&dir, buf) == -1)
59 {
60 LOG(LM_MAIN, LL_ERROR, "Cannot open music dir %s: %s", buf,
61 strerror(errno));
62 return;
63 }
64
65 for (; dir.has_next; tinydir_next(&dir))
66 {
67 Mix_Music *m;
68 tinydir_file file;
69 if (tinydir_readfile(&dir, &file) == -1)
70 {
71 goto bail;
72 }
73 if (!file.is_reg)
74 {
75 continue;
76 }
77
78 m = MusicLoad(file.path);
79 if (m == NULL)
80 {
81 continue;
82 }
83 CArrayPushBack(tracks, &m);
84 }
85
86 bail:
87 tinydir_close(&dir);
88 }
89
90 static void UnloadMusic(CArray *tracks);
MusicPlayerTerminate(MusicPlayer * mp)91 void MusicPlayerTerminate(MusicPlayer *mp)
92 {
93 for (MusicType type = MUSIC_MENU; type < MUSIC_COUNT; type++)
94 {
95 UnloadMusic(&mp->generalTracks[type]);
96 }
97 }
UnloadMusic(CArray * tracks)98 static void UnloadMusic(CArray *tracks)
99 {
100 CA_FOREACH(Mix_Music *, m, *tracks)
101 Mix_FreeMusic(*m);
102 CA_FOREACH_END()
103 CArrayTerminate(tracks);
104 }
105
MusicLoad(const char * path)106 Mix_Music *MusicLoad(const char *path)
107 {
108 // Only load music from known extensions
109 const char *ext = strrchr(path, '.');
110 if (ext == NULL ||
111 !(strcmp(ext, ".it") == 0 || strcmp(ext, ".IT") == 0 ||
112 strcmp(ext, ".mod") == 0 || strcmp(ext, ".MOD") == 0 ||
113 strcmp(ext, ".ogg") == 0 || strcmp(ext, ".OGG") == 0 ||
114 strcmp(ext, ".s3m") == 0 || strcmp(ext, ".S3M") == 0 ||
115 strcmp(ext, ".xm") == 0 || strcmp(ext, ".XM") == 0))
116 {
117 return NULL;
118 }
119 LOG(LM_MAIN, LL_TRACE, "loading music file %s", path);
120 return Mix_LoadMUS(path);
121 }
122
PlayMusic(MusicPlayer * mp)123 static void PlayMusic(MusicPlayer *mp)
124 {
125 MusicResume(mp);
126
127 if (ConfigGetInt(&gConfig, "Sound.MusicVolume") == 0)
128 {
129 MusicPause(mp);
130 }
131 }
132
Play(MusicPlayer * mp,const char * path)133 static bool Play(MusicPlayer *mp, const char *path)
134 {
135 if (!mp->isInitialised)
136 {
137 return true;
138 }
139
140 if (path == NULL || strlen(path) == 0)
141 {
142 LOG(LM_SOUND, LL_WARN, "Attempting to play song with empty name");
143 return false;
144 }
145
146 mp->type = MUSIC_SRC_DYNAMIC;
147 mp->u.dynamic = MusicLoad(path);
148 if (mp->u.dynamic == NULL)
149 {
150 strcpy(mp->errorMessage, SDL_GetError());
151 return false;
152 }
153 mp->errorMessage[0] = '\0';
154 PlayMusic(mp);
155 return true;
156 }
157
MusicPlayGeneral(MusicPlayer * mp,const MusicType type)158 void MusicPlayGeneral(MusicPlayer *mp, const MusicType type)
159 {
160 MusicStop(mp);
161 mp->type = MUSIC_SRC_GENERAL;
162 CArray *tracks = &mp->generalTracks[type];
163 if (tracks->size == 0)
164 {
165 return;
166 }
167 mp->u.general = *(Mix_Music **)CArrayGet(tracks, 0);
168 // Shuffle tracks
169 if (tracks->size > 1)
170 {
171 while (mp->u.general == *(Mix_Music **)CArrayGet(tracks, 0))
172 {
173 CArrayShuffle(tracks);
174 }
175 }
176 PlayMusic(mp);
177 }
MusicPlayFile(MusicPlayer * mp,const MusicType type,const char * missionPath,const char * music)178 void MusicPlayFile(
179 MusicPlayer *mp, const MusicType type, const char *missionPath,
180 const char *music)
181 {
182 MusicStop(mp);
183 bool played = false;
184 if (music != NULL && strlen(music) != 0)
185 {
186 char buf[CDOGS_PATH_MAX];
187 // First, try to play music from the same directory
188 // This may be a new-style directory campaign
189 GetDataFilePath(buf, missionPath);
190 strcat(buf, "/");
191 strcat(buf, music);
192 played = Play(mp, buf);
193 if (!played)
194 {
195 char buf2[CDOGS_PATH_MAX];
196 GetDataFilePath(buf2, missionPath);
197 PathGetDirname(buf, buf2);
198 strcat(buf, music);
199 played = Play(mp, buf);
200 }
201 }
202 if (!played)
203 {
204 MusicPlayGeneral(mp, type);
205 }
206 }
MusicPlayChunk(MusicPlayer * mp,const MusicType type,Mix_Chunk * chunk)207 void MusicPlayChunk(MusicPlayer *mp, const MusicType type, Mix_Chunk *chunk)
208 {
209 MusicStop(mp);
210 bool played = false;
211 if (chunk != NULL)
212 {
213 mp->type = MUSIC_SRC_CHUNK;
214 mp->u.chunk.chunk = chunk;
215 mp->u.chunk.channel = Mix_PlayChannel(-1, chunk, -1);
216 played = true;
217 }
218 if (!played)
219 {
220 MusicPlayGeneral(mp, type);
221 }
222 }
223
MusicStop(MusicPlayer * mp)224 void MusicStop(MusicPlayer *mp)
225 {
226 switch (mp->type)
227 {
228 case MUSIC_SRC_GENERAL:
229 Mix_HaltMusic();
230 mp->u.general = NULL;
231 break;
232 case MUSIC_SRC_DYNAMIC:
233 Mix_HaltMusic();
234 if (mp->u.dynamic != NULL)
235 {
236 Mix_FreeMusic(mp->u.dynamic);
237 }
238 mp->u.dynamic = NULL;
239 break;
240 case MUSIC_SRC_CHUNK:
241 if (mp->u.chunk.chunk != NULL)
242 {
243 Mix_HaltChannel(mp->u.chunk.channel);
244 }
245 mp->u.chunk.chunk = NULL;
246 break;
247 }
248 }
249
MusicPause(MusicPlayer * mp)250 void MusicPause(MusicPlayer *mp)
251 {
252 switch (mp->type)
253 {
254 case MUSIC_SRC_GENERAL:
255 case MUSIC_SRC_DYNAMIC:
256 if (Mix_PlayingMusic())
257 {
258 Mix_PauseMusic();
259 }
260 break;
261 case MUSIC_SRC_CHUNK:
262 Mix_Pause(mp->u.chunk.channel);
263 break;
264 }
265
266 }
267
MusicResume(MusicPlayer * mp)268 void MusicResume(MusicPlayer *mp)
269 {
270 switch (mp->type)
271 {
272 case MUSIC_SRC_GENERAL:
273 if (mp->u.general == NULL)
274 {
275 return;
276 }
277 if (Mix_PausedMusic())
278 {
279 Mix_ResumeMusic();
280 }
281 else if (!Mix_PlayingMusic())
282 {
283 Mix_PlayMusic(mp->u.general, -1);
284 }
285 break;
286 case MUSIC_SRC_DYNAMIC:
287 if (mp->u.dynamic == NULL)
288 {
289 return;
290 }
291 if (Mix_PausedMusic())
292 {
293 Mix_ResumeMusic();
294 }
295 else if (!Mix_PlayingMusic())
296 {
297 Mix_PlayMusic(mp->u.dynamic, -1);
298 }
299 break;
300 case MUSIC_SRC_CHUNK:
301 Mix_Resume(mp->u.chunk.channel);
302 break;
303 }
304 }
305
MusicSetPlaying(MusicPlayer * mp,const bool isPlaying)306 void MusicSetPlaying(MusicPlayer *mp, const bool isPlaying)
307 {
308 if (isPlaying)
309 {
310 MusicResume(mp);
311 }
312 else
313 {
314 MusicPause(mp);
315 }
316 }
317
MusicGetErrorMessage(const MusicPlayer * mp)318 const char *MusicGetErrorMessage(const MusicPlayer *mp)
319 {
320 return mp->errorMessage;
321 }
322
MusicChunkTerminate(MusicChunk * chunk)323 void MusicChunkTerminate(MusicChunk *chunk)
324 {
325 if (chunk->Chunk)
326 {
327 Mix_FreeChunk(chunk->Chunk);
328 }
329 CFREE(chunk->Data);
330 memset(chunk, 0, sizeof *chunk);
331 }
332
MusicPlayFromChunk(MusicPlayer * mp,const MusicType type,MusicChunk * chunk)333 void MusicPlayFromChunk(
334 MusicPlayer *mp, const MusicType type, MusicChunk *chunk)
335 {
336 if (chunk->Chunk == NULL && chunk->GetData)
337 {
338 chunk->Chunk =
339 chunk->GetData(chunk->Data);
340 CFREE(chunk->Data);
341 chunk->Data = NULL;
342 chunk->GetData = NULL;
343 }
344 MusicPlayChunk(mp, type, chunk->Chunk);
345 }
346