1 /*
2  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3  * It is copyright by its individual contributors, as recorded in the
4  * project's Git history.  See COPYING.txt at the top level for license
5  * terms and a link to the Git history.
6  */
7 /*
8  *
9  * SDL CD Audio functions
10  *
11  *
12  */
13 
14 #include <algorithm>
15 #include <stdio.h>
16 #include <stdlib.h>
17 
18 #include <SDL.h>
19 
20 #ifdef __linux__
21 #include <sys/ioctl.h>
22 #include <linux/cdrom.h>
23 #endif
24 
25 #include "pstypes.h"
26 #include "dxxerror.h"
27 #include "args.h"
28 #include "rbaudio.h"
29 #include "console.h"
30 #include "timer.h"
31 #include "partial_range.h"
32 #include "compiler-range_for.h"
33 
34 #define DXX_CHECK_CD_INDRIVE_0(S)	\
35 	static_assert(!CD_INDRIVE(S), #S)
36 #define DXX_CHECK_CD_INDRIVE_1(S)	\
37 	static_assert(CD_INDRIVE(S), #S)
38 #undef CD_INDRIVE
39 #define CD_INDRIVE(s)	(static_cast<int>(s) > 0)
40 DXX_CHECK_CD_INDRIVE_0(CD_ERROR);
41 DXX_CHECK_CD_INDRIVE_0(CD_TRAYEMPTY);
42 DXX_CHECK_CD_INDRIVE_1(CD_STOPPED);
43 DXX_CHECK_CD_INDRIVE_1(CD_PLAYING);
44 DXX_CHECK_CD_INDRIVE_1(CD_PAUSED);
45 
46 namespace dcx {
47 
48 #define REDBOOK_VOLUME_SCALE 255
49 
50 static SDL_CD *s_cd = NULL;
51 static int initialised = 0;
52 
RBAExit()53 void RBAExit()
54 {
55 	if (s_cd)
56 	{
57 		SDL_CDStop(s_cd);
58 		SDL_CDClose(s_cd);
59 	}
60 }
61 
RBAInit()62 void RBAInit()
63 {
64 	int num_cds;
65 	if (initialised) return;
66 
67 	if (SDL_Init(SDL_INIT_CDROM) < 0)
68 	{
69 		Warning("RBAudio: SDL library initialisation failed: %s.",SDL_GetError());
70 		return;
71 	}
72 
73 	num_cds = SDL_CDNumDrives();
74 	if (num_cds < 1)
75 	{
76 		con_puts(CON_NORMAL, "RBAudio: No cdrom drives found!");
77 #if defined(__APPLE__) || defined(macintosh)
78 		SDL_QuitSubSystem(SDL_INIT_CDROM);	// necessary for rescanning CDROMs
79 #endif
80 		return;
81 	}
82 
83 	for (int i = 0; i < num_cds; i++)
84 	{
85 		if (s_cd)
86 			SDL_CDClose(s_cd);
87 		s_cd = SDL_CDOpen(i);
88 
89 		if (s_cd && CD_INDRIVE(SDL_CDStatus(s_cd)))
90 		{
91 			const auto &&r = partial_const_range(s_cd->track, static_cast<unsigned>(s_cd->numtracks));
92 			if (std::find_if(r.begin(), r.end(), [](const SDL_CDtrack &t){ return t.type == SDL_AUDIO_TRACK; }) != r.end())
93 			{
94 				initialised = 1;
95 				RBAList();
96 				return;	// we've found an audio CD
97 			}
98 		}
99 		else if (s_cd == NULL)
100 			Warning("RBAudio: Could not open cdrom %i for redbook audio:%s\n", i, SDL_GetError());
101 	}
102 
103 	{
104 		con_puts(CON_NORMAL, "RBAudio: No audio CDs found");
105 		if (s_cd)	// if there's no audio CD, say that there's no redbook and hence play MIDI instead
106 		{
107 			SDL_CDClose(s_cd);
108 			s_cd = NULL;
109 		}
110 #if defined(__APPLE__) || defined(macintosh)
111 		SDL_QuitSubSystem(SDL_INIT_CDROM);	// necessary for rescanning CDROMs
112 #endif
113 		return;
114 	}
115 }
116 
RBAEnabled()117 int RBAEnabled()
118 {
119 	return initialised;
120 }
121 
RBAPlayTrack(int a)122 int RBAPlayTrack(int a)
123 {
124 	if (!s_cd) return -1;
125 
126 	if (CD_INDRIVE(SDL_CDStatus(s_cd)) ) {
127 		if (SDL_CDPlayTracks(s_cd, a-1, 0, 0, 0) == 0)
128 		{
129 			con_printf(CON_VERBOSE, "RBAudio: Playing track %i", a);
130 			return a;
131 		}
132 	}
133 	return -1;
134 }
135 
136 static void (*redbook_finished_hook)() = NULL;
137 
RBAStop()138 void RBAStop()
139 {
140 	if (!s_cd) return;
141 	SDL_CDStop(s_cd);
142 	redbook_finished_hook = NULL;	// no calling the finished hook - stopped means stopped here
143 	con_puts(CON_VERBOSE, "RBAudio: Playback stopped");
144 }
145 
RBAEjectDisk()146 void RBAEjectDisk()
147 {
148 	if (!s_cd) return;
149 	SDL_CDEject(s_cd);	// play nothing until it tries to load a song
150 #if defined(__APPLE__) || defined(macintosh)
151 	SDL_QuitSubSystem(SDL_INIT_CDROM);	// necessary for rescanning CDROMs
152 #endif
153 	initialised = 0;
154 }
155 
156 #ifdef __linux__
RBASetVolume(int volume)157 void RBASetVolume(int volume)
158 {
159 	int cdfile, level;
160 	struct cdrom_volctrl volctrl;
161 
162 	if (!s_cd) return;
163 
164 	cdfile = s_cd->id;
165 	level = volume*REDBOOK_VOLUME_SCALE/8;
166 
167 	if ((level<0) || (level>REDBOOK_VOLUME_SCALE)) {
168 		con_printf(CON_CRITICAL, "RBAudio: illegal volume value (allowed values 0-%i)",REDBOOK_VOLUME_SCALE);
169 		return;
170 	}
171 
172 	volctrl.channel0
173 		= volctrl.channel1
174 		= volctrl.channel2
175 		= volctrl.channel3
176 		= level;
177 	if ( ioctl(cdfile, CDROMVOLCTRL, &volctrl) == -1 ) {
178 		con_puts(CON_CRITICAL, "RBAudio: CDROMVOLCTRL ioctl failed");
179 		return;
180 	}
181 }
182 #endif
183 
RBAPause()184 void RBAPause()
185 {
186 	if (!s_cd) return;
187 	SDL_CDPause(s_cd);
188 	con_puts(CON_VERBOSE, "RBAudio: Playback paused");
189 }
190 
RBAResume()191 int RBAResume()
192 {
193 	if (!s_cd) return -1;
194 	SDL_CDResume(s_cd);
195 	con_puts(CON_VERBOSE, "RBAudio: Playback resumed");
196 	return 1;
197 }
198 
RBAPauseResume()199 int RBAPauseResume()
200 {
201 	if (!s_cd) return 0;
202 
203 	if (SDL_CDStatus(s_cd) == CD_PLAYING)
204 	{
205 		SDL_CDPause(s_cd);
206 		con_puts(CON_VERBOSE, "RBAudio: Toggle Playback pause");
207 	}
208 	else if (SDL_CDStatus(s_cd) == CD_PAUSED)
209 	{
210 		SDL_CDResume(s_cd);
211 		con_puts(CON_VERBOSE, "RBAudio: Toggle Playback resume");
212 	}
213 	else
214 		return 0;
215 
216 	return 1;
217 }
218 
RBAGetNumberOfTracks()219 int RBAGetNumberOfTracks()
220 {
221 	if (!s_cd) return -1;
222 	SDL_CDStatus(s_cd);
223 	con_printf(CON_VERBOSE, "RBAudio: Found %i tracks on CD", s_cd->numtracks);
224 	return s_cd->numtracks;
225 }
226 
227 // check if we need to call the 'finished' hook
228 // needs to go in all event loops
229 // a real hook would be ideal, but is currently unsupported by SDL Audio CD
RBACheckFinishedHook()230 void RBACheckFinishedHook()
231 {
232 	static fix64 last_check_time = 0;
233 
234 	if (!s_cd) return;
235 
236 	if ((timer_query() - last_check_time) >= F2_0)
237 	{
238 		if ((SDL_CDStatus(s_cd) == CD_STOPPED) && redbook_finished_hook)
239 		{
240 			con_puts(CON_VERBOSE, "RBAudio: Playback done, calling finished-hook");
241 			redbook_finished_hook();
242 		}
243 		last_check_time = timer_query();
244 	}
245 }
246 
247 // plays tracks first through last, inclusive
RBAPlayTracks(int first,int last,void (* hook_finished)(void))248 int RBAPlayTracks(int first, int last, void (*hook_finished)(void))
249 {
250 	if (!s_cd)
251 		return 0;
252 
253 	if (CD_INDRIVE(SDL_CDStatus(s_cd)))
254 	{
255 		redbook_finished_hook = hook_finished;
256 		con_printf(CON_VERBOSE, "RBAudio: Playing tracks %i to %i", first, last);
257 		return SDL_CDPlayTracks(s_cd, first - 1, 0, last - first + 1, 0) == 0;
258 	}
259 	return 0;
260 }
261 
262 // return the track number currently playing.  Useful if RBAPlayTracks()
263 // is called.  Returns 0 if no track playing, else track number
RBAGetTrackNum()264 int RBAGetTrackNum()
265 {
266 	if (!s_cd)
267 		return 0;
268 
269 	if (SDL_CDStatus(s_cd) != CD_PLAYING)
270 		return 0;
271 
272 	return s_cd->cur_track + 1;
273 }
274 
RBAPeekPlayStatus()275 int RBAPeekPlayStatus()
276 {
277 	if (!s_cd)
278 		return 0;
279 
280 	if (SDL_CDStatus(s_cd) == CD_PLAYING)
281 		return 1;
282 	else if (SDL_CDStatus(s_cd) == CD_PAUSED)	// hack so it doesn't keep restarting paused music
283 		return -1;
284 
285 	return 0;
286 }
287 
cddb_sum(int n)288 static int cddb_sum(int n)
289 {
290 	int ret;
291 
292 	/* For backward compatibility this algorithm must not change */
293 
294 	ret = 0;
295 
296 	while (n > 0) {
297 		ret = ret + (n % 10);
298 		n = n / 10;
299 	}
300 
301 	return (ret);
302 }
303 
304 
RBAGetDiscID()305 unsigned long RBAGetDiscID()
306 {
307 	int i, t = 0, n = 0;
308 
309 	if (!s_cd)
310 		return 0;
311 
312 	/* For backward compatibility this algorithm must not change */
313 
314 	i = 0;
315 
316 	while (i < s_cd->numtracks) {
317 		n += cddb_sum(s_cd->track[i].offset / CD_FPS);
318 		i++;
319 	}
320 
321 	t = (s_cd->track[s_cd->numtracks].offset / CD_FPS) -
322 	    (s_cd->track[0].offset / CD_FPS);
323 
324 	return ((n % 0xff) << 24 | t << 8 | s_cd->numtracks);
325 }
326 
RBAList(void)327 void RBAList(void)
328 {
329 
330 	if (!s_cd)
331 		return;
332 
333 	range_for (auto &i, partial_const_range(s_cd->track, static_cast<unsigned>(s_cd->numtracks)))
334 		con_printf(CON_VERBOSE, "RBAudio: CD track %d, type %s, length %d, offset %d", i.id, (i.type == SDL_AUDIO_TRACK) ? "audio" : "data", i.length, i.offset);
335 }
336 
337 }
338