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@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 */
45static long SDL_cdversion = 0;
46static struct {
47	short		dRefNum;
48	short		driveNum;
49	long		frames;
50	char		name[256];
51	Boolean		hasAudio;
52	} SDL_cdlist[MAX_DRIVES];
53static StringPtr gDriverName = "\p.AppleCD";
54
55/* The system-dependent CD control functions */
56static const char *SDL_SYS_CDName(int drive);
57static int SDL_SYS_CDOpen(int drive);
58static int SDL_SYS_CDGetTOC(SDL_CD *cdrom);
59static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position);
60static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length);
61static int SDL_SYS_CDPause(SDL_CD *cdrom);
62static int SDL_SYS_CDResume(SDL_CD *cdrom);
63static int SDL_SYS_CDStop(SDL_CD *cdrom);
64static int SDL_SYS_CDEject(SDL_CD *cdrom);
65static void SDL_SYS_CDClose(SDL_CD *cdrom);
66
67static short SDL_SYS_ShortToBCD(short value)
68{
69	return((value % 10) + (value / 10) * 0x10); /* Convert value to BCD */
70}
71
72static short SDL_SYS_BCDToShort(short value)
73{
74	return((value % 0x10) + (value / 0x10) * 10); /* Convert value from BCD */
75}
76
77int  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
129static const char *SDL_SYS_CDName(int drive)
130{
131	return(SDL_cdlist[drive].name);
132}
133
134static 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
154static int SDL_SYS_CDOpen(int drive)
155{
156	return(drive);
157}
158
159static 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 */
249static 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 */
331static 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 */
413static 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 */
431static 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 */
449static 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 */
468static 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 */
514static void SDL_SYS_CDClose(SDL_CD *cdrom)
515{
516	return;
517}
518
519void 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