| 1 | /* |
| 2 | SDL - Simple DirectMedia Layer |
| 3 | Copyright (C) 1997-2009 Sam Lantinga |
| 4 | |
| 5 | This library is free software; you can redistribute it and/or |
| 6 | modify it under the terms of the GNU Lesser General Public |
| 7 | License as published by the Free Software Foundation; either |
| 8 | version 2.1 of the License, or (at your option) any later version. |
| 9 | |
| 10 | This library is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | Lesser General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU Lesser General Public |
| 16 | License along with this library; if not, write to the Free Software |
| 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 18 | |
| 19 | Sam Lantinga |
| 20 | slouken@libsdl.org |
| 21 | */ |
| 22 | #include "SDL_config.h" |
| 23 | |
| 24 | #ifdef SDL_CDROM_MACOS |
| 25 | |
| 26 | /* MacOS functions for system-level CD-ROM audio control */ |
| 27 | |
| 28 | #include <Devices.h> |
| 29 | #include <Files.h> |
| 30 | #include <LowMem.h> /* Use entry table macros, not functions in InterfaceLib */ |
| 31 | |
| 32 | #include "SDL_cdrom.h" |
| 33 | #include "../SDL_syscdrom.h" |
| 34 | #include "SDL_syscdrom_c.h" |
| 35 | |
| 36 | /* Added by Matt Slot */ |
| 37 | #if !defined(LMGetUnitTableEntryCount) |
| 38 | #define LMGetUnitTableEntryCount() *(short *)0x01D2 |
| 39 | #endif |
| 40 | |
| 41 | /* The maximum number of CD-ROM drives we'll detect */ |
| 42 | #define MAX_DRIVES 26 |
| 43 | |
| 44 | /* A list of available CD-ROM drives */ |
| 45 | static long SDL_cdversion = 0; |
| 46 | static struct { |
| 47 | short dRefNum; |
| 48 | short driveNum; |
| 49 | long frames; |
| 50 | char name[256]; |
| 51 | Boolean hasAudio; |
| 52 | } SDL_cdlist[MAX_DRIVES]; |
| 53 | static StringPtr gDriverName = "\p.AppleCD"; |
| 54 | |
| 55 | /* The system-dependent CD control functions */ |
| 56 | static const char *SDL_SYS_CDName(int drive); |
| 57 | static int SDL_SYS_CDOpen(int drive); |
| 58 | static int SDL_SYS_CDGetTOC(SDL_CD *cdrom); |
| 59 | static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position); |
| 60 | static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length); |
| 61 | static int SDL_SYS_CDPause(SDL_CD *cdrom); |
| 62 | static int SDL_SYS_CDResume(SDL_CD *cdrom); |
| 63 | static int SDL_SYS_CDStop(SDL_CD *cdrom); |
| 64 | static int SDL_SYS_CDEject(SDL_CD *cdrom); |
| 65 | static void SDL_SYS_CDClose(SDL_CD *cdrom); |
| 66 | |
| 67 | static short SDL_SYS_ShortToBCD(short value) |
| 68 | { |
| 69 | return((value % 10) + (value / 10) * 0x10); /* Convert value to BCD */ |
| 70 | } |
| 71 | |
| 72 | static short SDL_SYS_BCDToShort(short value) |
| 73 | { |
| 74 | return((value % 0x10) + (value / 0x10) * 10); /* Convert value from BCD */ |
| 75 | } |
| 76 | |
| 77 | int SDL_SYS_CDInit(void) |
| 78 | { |
| 79 | SInt16 dRefNum = 0; |
| 80 | SInt16 first, last; |
| 81 | |
| 82 | SDL_numcds = 0; |
| 83 | |
| 84 | /* Check that the software is available */ |
| 85 | if (Gestalt(kGestaltAudioCDSelector, &SDL_cdversion) || |
| 86 | !SDL_cdversion) return(0); |
| 87 | |
| 88 | /* Fill in our driver capabilities */ |
| 89 | SDL_CDcaps.Name = SDL_SYS_CDName; |
| 90 | SDL_CDcaps.Open = SDL_SYS_CDOpen; |
| 91 | SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC; |
| 92 | SDL_CDcaps.Status = SDL_SYS_CDStatus; |
| 93 | SDL_CDcaps.Play = SDL_SYS_CDPlay; |
| 94 | SDL_CDcaps.Pause = SDL_SYS_CDPause; |
| 95 | SDL_CDcaps.Resume = SDL_SYS_CDResume; |
| 96 | SDL_CDcaps.Stop = SDL_SYS_CDStop; |
| 97 | SDL_CDcaps.Eject = SDL_SYS_CDEject; |
| 98 | SDL_CDcaps.Close = SDL_SYS_CDClose; |
| 99 | |
| 100 | /* Walk the list, count each AudioCD driver, and save the refnums */ |
| 101 | first = -1; |
| 102 | last = 0 - LMGetUnitTableEntryCount(); |
| 103 | for(dRefNum = first; dRefNum >= last; dRefNum--) { |
| 104 | Str255 driverName; |
| 105 | StringPtr namePtr; |
| 106 | DCtlHandle deviceEntry; |
| 107 | |
| 108 | deviceEntry = GetDCtlEntry(dRefNum); |
| 109 | if (! deviceEntry) continue; |
| 110 | |
| 111 | /* Is this an .AppleCD ? */ |
| 112 | namePtr = (*deviceEntry)->dCtlFlags & (1L << dRAMBased) ? |
| 113 | ((StringPtr) ((DCtlPtr) deviceEntry)->dCtlDriver + 18) : |
| 114 | ((StringPtr) (*deviceEntry)->dCtlDriver + 18); |
| 115 | BlockMoveData(namePtr, driverName, namePtr[0]+1); |
| 116 | if (driverName[0] > gDriverName[0]) driverName[0] = gDriverName[0]; |
| 117 | if (! EqualString(driverName, gDriverName, false, false)) continue; |
| 118 | |
| 119 | /* Record the basic info for each drive */ |
| 120 | SDL_cdlist[SDL_numcds].dRefNum = dRefNum; |
| 121 | BlockMoveData(namePtr + 1, SDL_cdlist[SDL_numcds].name, namePtr[0]); |
| 122 | SDL_cdlist[SDL_numcds].name[namePtr[0]] = 0; |
| 123 | SDL_cdlist[SDL_numcds].hasAudio = false; |
| 124 | SDL_numcds++; |
| 125 | } |
| 126 | return(0); |
| 127 | } |
| 128 | |
| 129 | static const char *SDL_SYS_CDName(int drive) |
| 130 | { |
| 131 | return(SDL_cdlist[drive].name); |
| 132 | } |
| 133 | |
| 134 | static int get_drivenum(int drive) |
| 135 | { |
| 136 | QHdr *driveQ = GetDrvQHdr(); |
| 137 | DrvQEl *driveElem; |
| 138 | |
| 139 | /* Update the drive number */ |
| 140 | SDL_cdlist[drive].driveNum = 0; |
| 141 | if ( driveQ->qTail ) { |
| 142 | driveQ->qTail->qLink = 0; |
| 143 | } |
| 144 | for ( driveElem=(DrvQEl *)driveQ->qHead; driveElem; |
| 145 | driveElem = (DrvQEl *)driveElem->qLink ) { |
| 146 | if ( driveElem->dQRefNum == SDL_cdlist[drive].dRefNum ) { |
| 147 | SDL_cdlist[drive].driveNum = driveElem->dQDrive; |
| 148 | break; |
| 149 | } |
| 150 | } |
| 151 | return(SDL_cdlist[drive].driveNum); |
| 152 | } |
| 153 | |
| 154 | static int SDL_SYS_CDOpen(int drive) |
| 155 | { |
| 156 | return(drive); |
| 157 | } |
| 158 | |
| 159 | static int SDL_SYS_CDGetTOC(SDL_CD *cdrom) |
| 160 | { |
| 161 | CDCntrlParam cdpb; |
| 162 | CDTrackData tracks[SDL_MAX_TRACKS]; |
| 163 | long i, leadout; |
| 164 | |
| 165 | /* Get the number of tracks on the CD by examining the TOC */ |
| 166 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 167 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 168 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 169 | cdpb.csCode = kReadTOC; |
| 170 | cdpb.csParam.words[0] = kGetTrackRange; |
| 171 | if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { |
| 172 | SDL_SetError("PBControlSync() failed"); |
| 173 | return(-1); |
| 174 | } |
| 175 | |
| 176 | cdrom->numtracks = |
| 177 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[1]) - |
| 178 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[0]) + 1; |
| 179 | if ( cdrom->numtracks > SDL_MAX_TRACKS ) |
| 180 | cdrom->numtracks = SDL_MAX_TRACKS; |
| 181 | cdrom->status = CD_STOPPED; |
| 182 | cdrom->cur_track = 0; /* Apparently these are set elsewhere */ |
| 183 | cdrom->cur_frame = 0; /* Apparently these are set elsewhere */ |
| 184 | |
| 185 | |
| 186 | /* Get the lead out area of the CD by examining the TOC */ |
| 187 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 188 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 189 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 190 | cdpb.csCode = kReadTOC; |
| 191 | cdpb.csParam.words[0] = kGetLeadOutArea; |
| 192 | if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { |
| 193 | SDL_SetError("PBControlSync() failed"); |
| 194 | return(-1); |
| 195 | } |
| 196 | |
| 197 | leadout = MSF_TO_FRAMES( |
| 198 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[0]), |
| 199 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[1]), |
| 200 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[2])); |
| 201 | |
| 202 | /* Get an array of track locations by examining the TOC */ |
| 203 | SDL_memset(tracks, 0, sizeof(tracks)); |
| 204 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 205 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 206 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 207 | cdpb.csCode = kReadTOC; |
| 208 | cdpb.csParam.words[0] = kGetTrackEntries; /* Type of Query */ |
| 209 | * ((long *) (cdpb.csParam.words+1)) = (long) tracks; |
| 210 | cdpb.csParam.words[3] = cdrom->numtracks * sizeof(tracks[0]); |
| 211 | * ((char *) (cdpb.csParam.words+4)) = 1; /* First track */ |
| 212 | if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { |
| 213 | SDL_SetError("PBControlSync() failed"); |
| 214 | return(-1); |
| 215 | } |
| 216 | |
| 217 | /* Read all the track TOC entries */ |
| 218 | SDL_cdlist[cdrom->id].hasAudio = false; |
| 219 | for ( i=0; i<cdrom->numtracks; ++i ) |
| 220 | { |
| 221 | cdrom->track[i].id = i+1; |
| 222 | if (tracks[i].entry.control & kDataTrackMask) |
| 223 | cdrom->track[i].type = SDL_DATA_TRACK; |
| 224 | else |
| 225 | { |
| 226 | cdrom->track[i].type = SDL_AUDIO_TRACK; |
| 227 | SDL_cdlist[SDL_numcds].hasAudio = true; |
| 228 | } |
| 229 | |
| 230 | cdrom->track[i].offset = MSF_TO_FRAMES( |
| 231 | SDL_SYS_BCDToShort(tracks[i].entry.min), |
| 232 | SDL_SYS_BCDToShort(tracks[i].entry.min), |
| 233 | SDL_SYS_BCDToShort(tracks[i].entry.frame)); |
| 234 | cdrom->track[i].length = MSF_TO_FRAMES( |
| 235 | SDL_SYS_BCDToShort(tracks[i+1].entry.min), |
| 236 | SDL_SYS_BCDToShort(tracks[i+1].entry.min), |
| 237 | SDL_SYS_BCDToShort(tracks[i+1].entry.frame)) - |
| 238 | cdrom->track[i].offset; |
| 239 | } |
| 240 | |
| 241 | /* Apparently SDL wants a fake last entry */ |
| 242 | cdrom->track[i].offset = leadout; |
| 243 | cdrom->track[i].length = 0; |
| 244 | |
| 245 | return(0); |
| 246 | } |
| 247 | |
| 248 | /* Get CD-ROM status */ |
| 249 | static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position) |
| 250 | { |
| 251 | CDCntrlParam cdpb; |
| 252 | CDstatus status = CD_ERROR; |
| 253 | Boolean spinning = false; |
| 254 | |
| 255 | if (position) *position = 0; |
| 256 | |
| 257 | /* Get the number of tracks on the CD by examining the TOC */ |
| 258 | if ( ! get_drivenum(cdrom->id) ) { |
| 259 | return(CD_TRAYEMPTY); |
| 260 | } |
| 261 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 262 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 263 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 264 | cdpb.csCode = kReadTOC; |
| 265 | cdpb.csParam.words[0] = kGetTrackRange; |
| 266 | if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { |
| 267 | SDL_SetError("PBControlSync() failed"); |
| 268 | return(CD_ERROR); |
| 269 | } |
| 270 | |
| 271 | cdrom->numtracks = |
| 272 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[1]) - |
| 273 | SDL_SYS_BCDToShort(cdpb.csParam.bytes[0]) + 1; |
| 274 | if ( cdrom->numtracks > SDL_MAX_TRACKS ) |
| 275 | cdrom->numtracks = SDL_MAX_TRACKS; |
| 276 | cdrom->cur_track = 0; /* Apparently these are set elsewhere */ |
| 277 | cdrom->cur_frame = 0; /* Apparently these are set elsewhere */ |
| 278 | |
| 279 | |
| 280 | if (1 || SDL_cdlist[cdrom->id].hasAudio) { |
| 281 | /* Get the current playback status */ |
| 282 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 283 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 284 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 285 | cdpb.csCode = kAudioStatus; |
| 286 | if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { |
| 287 | SDL_SetError("PBControlSync() failed"); |
| 288 | return(-1); |
| 289 | } |
| 290 | |
| 291 | switch(cdpb.csParam.cd.status) { |
| 292 | case kStatusPlaying: |
| 293 | status = CD_PLAYING; |
| 294 | spinning = true; |
| 295 | break; |
| 296 | case kStatusPaused: |
| 297 | status = CD_PAUSED; |
| 298 | spinning = true; |
| 299 | break; |
| 300 | case kStatusMuted: |
| 301 | status = CD_PLAYING; /* What should I do here? */ |
| 302 | spinning = true; |
| 303 | break; |
| 304 | case kStatusDone: |
| 305 | status = CD_STOPPED; |
| 306 | spinning = true; |
| 307 | break; |
| 308 | case kStatusStopped: |
| 309 | status = CD_STOPPED; |
| 310 | spinning = false; |
| 311 | break; |
| 312 | case kStatusError: |
| 313 | default: |
| 314 | status = CD_ERROR; |
| 315 | spinning = false; |
| 316 | break; |
| 317 | } |
| 318 | |
| 319 | if (spinning && position) *position = MSF_TO_FRAMES( |
| 320 | SDL_SYS_BCDToShort(cdpb.csParam.cd.minute), |
| 321 | SDL_SYS_BCDToShort(cdpb.csParam.cd.second), |
| 322 | SDL_SYS_BCDToShort(cdpb.csParam.cd.frame)); |
| 323 | } |
| 324 | else |
| 325 | status = CD_ERROR; /* What should I do here? */ |
| 326 | |
| 327 | return(status); |
| 328 | } |
| 329 | |
| 330 | /* Start play */ |
| 331 | static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) |
| 332 | { |
| 333 | CDCntrlParam cdpb; |
| 334 | |
| 335 | /* Pause the current audio playback to avoid audible artifacts */ |
| 336 | if ( SDL_SYS_CDPause(cdrom) < 0 ) { |
| 337 | return(-1); |
| 338 | } |
| 339 | |
| 340 | /* Specify the AudioCD playback mode */ |
| 341 | SDL_memset(&cdpb, 0, sizeof(cdpb)); |
| 342 | cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; |
| 343 | cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; |
| 344 | cdpb.csCode = kSetPlayMode; |
| 345 | cdpb.csParam.bytes[0] = false; /* Repeat? */ |
| 346 | cdpb.csParam.bytes[1] = kPlayModeSequential; /* Play mode */ |
| 347 |