Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 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 | |||
| 53 | void RBAExit() |
||
| 54 | { |
||
| 55 | if (s_cd) |
||
| 56 | { |
||
| 57 | SDL_CDStop(s_cd); |
||
| 58 | SDL_CDClose(s_cd); |
||
| 59 | } |
||
| 60 | } |
||
| 61 | |||
| 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 | |||
| 117 | int RBAEnabled() |
||
| 118 | { |
||
| 119 | return initialised; |
||
| 120 | } |
||
| 121 | |||
| 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 | |||
| 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 | |||
| 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__ |
||
| 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 | |||
| 184 | void RBAPause() |
||
| 185 | { |
||
| 186 | if (!s_cd) return; |
||
| 187 | SDL_CDPause(s_cd); |
||
| 188 | con_puts(CON_VERBOSE, "RBAudio: Playback paused"); |
||
| 189 | } |
||
| 190 | |||
| 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 | |||
| 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 | |||
| 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 |
||
| 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 |
||
| 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 |
||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 | } |