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