1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2012 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 (at) 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 /* Treat as soft error, NEC Drive doesnt support this call */ 348 PBControlSync((ParmBlkPtr) &cdpb); 349 350 #if 1 351 /* Specify the end of audio playback */ 352 SDL_memset(&cdpb, 0, sizeof(cdpb)); 353 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 354 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 355 cdpb.csCode = kAudioStop; 356 cdpb.csParam.words[0] = kBlockPosition; /* Position Mode */ 357 *(long *) (cdpb.csParam.words + 1) = start+length-1; /* Search Address */ 358 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 359 SDL_SetError("PBControlSync() failed"); 360 return(-1); 361 } 362 363 /* Specify the start of audio playback, and start it */ 364 SDL_memset(&cdpb, 0, sizeof(cdpb)); 365 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 366 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 367 cdpb.csCode = kAudioPlay; 368 cdpb.csParam.words[0] = kBlockPosition; /* Position Mode */ 369 *(long *) (cdpb.csParam.words + 1) = start+1; /* Search Address */ 370 cdpb.csParam.words[3] = false; /* Stop address? */ 371 cdpb.csParam.words[4] = kStereoPlayMode; /* Audio Play Mode */ 372 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 373 SDL_SetError("PBControlSync() failed"); 374 return(-1); 375 } 376 #else 377 /* Specify the end of audio playback */ 378 FRAMES_TO_MSF(start+length, &m, &s, &f); 379 SDL_memset(&cdpb, 0, sizeof(cdpb)); 380 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 381 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 382 cdpb.csCode = kAudioStop; 383 cdpb.csParam.words[0] = kTrackPosition; /* Position Mode */ 384 cdpb.csParam.words[1] = 0; /* Search Address (hiword)*/ 385 cdpb.csParam.words[2] = /* Search Address (loword)*/ 386 SDL_SYS_ShortToBCD(cdrom->numtracks); 387 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 388 SDL_SetError("PBControlSync() failed"); 389 return(-1); 390 } 391 392 /* Specify the start of audio playback, and start it */ 393 FRAMES_TO_MSF(start, &m, &s, &f); 394 SDL_memset(&cdpb, 0, sizeof(cdpb)); 395 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 396 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 397 cdpb.csCode = kAudioPlay; 398 cdpb.csParam.words[0] = kTrackPosition; /* Position Mode */ 399 cdpb.csParam.words[1] = 0; /* Search Address (hiword)*/ 400 cdpb.csParam.words[2] = SDL_SYS_ShortToBCD(1); /* Search Address (loword)*/ 401 cdpb.csParam.words[3] = false; /* Stop address? */ 402 cdpb.csParam.words[4] = kStereoPlayMode; /* Audio Play Mode */ 403 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 404 SDL_SetError("PBControlSync() failed"); 405 return(-1); 406 } 407 #endif 408 409 return(0); 410 } 411 412 /* Pause play */ 413 static int SDL_SYS_CDPause(SDL_CD *cdrom) 414 { 415 CDCntrlParam cdpb; 416 417 SDL_memset(&cdpb, 0, sizeof(cdpb)); 418 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 419 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 420 cdpb.csCode = kAudioPause; 421 cdpb.csParam.words[0] = 0; /* Pause/Continue Flag (hiword) */ 422 cdpb.csParam.words[1] = 1; /* Pause/Continue Flag (loword) */ 423 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 424 SDL_SetError("PBControlSync() failed"); 425 return(-1); 426 } 427 return(0); 428 } 429 430 /* Resume play */ 431 static int SDL_SYS_CDResume(SDL_CD *cdrom) 432 { 433 CDCntrlParam cdpb; 434 435 SDL_memset(&cdpb, 0, sizeof(cdpb)); 436 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 437 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 438 cdpb.csCode = kAudioPause; 439 cdpb.csParam.words[0] = 0; /* Pause/Continue Flag (hiword) */ 440 cdpb.csParam.words[1] = 0; /* Pause/Continue Flag (loword) */ 441 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 442 SDL_SetError("PBControlSync() failed"); 443 return(-1); 444 } 445 return(0); 446 } 447 448 /* Stop play */ 449 static int SDL_SYS_CDStop(SDL_CD *cdrom) 450 { 451 CDCntrlParam cdpb; 452 453 SDL_memset(&cdpb, 0, sizeof(cdpb)); 454 cdpb.ioVRefNum = SDL_cdlist[cdrom->id].driveNum; 455 cdpb.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 456 cdpb.csCode = kAudioStop; 457 cdpb.csParam.words[0] = 0; /* Position Mode */ 458 cdpb.csParam.words[1] = 0; /* Search Address (hiword) */ 459 cdpb.csParam.words[2] = 0; /* Search Address (loword) */ 460 if ( PBControlSync((ParmBlkPtr)&cdpb) != noErr ) { 461 SDL_SetError("PBControlSync() failed"); 462 return(-1); 463 } 464 return(0); 465 } 466 467 /* Eject the CD-ROM */ 468 static int SDL_SYS_CDEject(SDL_CD *cdrom) 469 { 470 Boolean disk = false; 471 QHdr *driveQ = GetDrvQHdr(); 472 DrvQEl *driveElem; 473 HParamBlockRec hpb; 474 ParamBlockRec cpb; 475 476 for ( driveElem = (DrvQEl *) driveQ->qHead; driveElem; driveElem = 477 (driveElem) ? ((DrvQEl *) driveElem->qLink) : 478 ((DrvQEl *) driveQ->qHead) ) { 479 if ( driveQ->qTail ) { 480 driveQ->qTail->qLink = 0; 481 } 482 if ( driveElem->dQRefNum != SDL_cdlist[cdrom->id].dRefNum ) { 483 continue; 484 } 485 486 /* Does drive contain mounted volume? If not, skip */ 487 SDL_memset(&hpb, 0, sizeof(hpb)); 488 hpb.volumeParam.ioVRefNum = driveElem->dQDrive; 489 if ( PBHGetVInfoSync(&hpb) != noErr ) { 490 continue; 491 } 492 if ( (UnmountVol(0, driveElem->dQDrive) == noErr) && 493 (Eject(0, driveElem->dQDrive) == noErr) ) { 494 driveElem = 0; /* Clear pointer to reset our loop */ 495 disk = true; 496 } 497 } 498 499 /* If no disk is present, just eject the tray */ 500 if (! disk) { 501 SDL_memset(&cpb, 0, sizeof(cpb)); 502 cpb.cntrlParam.ioVRefNum = 0; /* No Drive */ 503 cpb.cntrlParam.ioCRefNum = SDL_cdlist[cdrom->id].dRefNum; 504 cpb.cntrlParam.csCode = kEjectTheDisc; 505 if ( PBControlSync((ParmBlkPtr)&cpb) != noErr ) { 506 SDL_SetError("PBControlSync() failed"); 507 return(-1); 508 } 509 } 510 return(0); 511 } 512 513 /* Close the CD-ROM handle */ 514 static void SDL_SYS_CDClose(SDL_CD *cdrom) 515 { 516 return; 517 } 518 519 void SDL_SYS_CDQuit(void) 520 { 521 while(SDL_numcds--) 522 SDL_memset(SDL_cdlist + SDL_numcds, 0, sizeof(SDL_cdlist[0])); 523 } 524 525 #endif /* SDL_CDROM_MACOS */ 526