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