www.pudn.com > cdx.zip > CDROM.CPP


/*C4*/ 
//**************************************************************** 
//	Author:	Jethro Wright, III 			TS :  1/20/1994 19:39 
//	Date:	01/01/1994 
// 
//			cdrom.cpp :  cd-rom audio interface class for dos 
//			and windows (as a dll) via mscdex.... 
// 
//	History: 
//		01/01/1994  jw3	 also sprach zarathustra.... 
//****************************************************************/ 
 
 
				// 
				//	the principal purpose of this simple c++ utility 
				//	framework is to provide the basis for a simple 
				//	audio cd util under dos as well as supplement the 
				//	multimedia cd audio facils under windows.  as of 
				//	this writing, msoft hasn't provided a means of 
				//	id'ing audio cds for windows. 
				//	unfortunately, audio cd manufacuturers don't 
				//	always include a upc on audio cds, which would 
				//	provide an irrefutable and universal means of 
				//	identifying cds.  therefore, this project has 
				//	been undertaken w/ the hope that it'll be 
				//	possible to use basic mscdex services to address 
				//	this oversight. (famous last words....)  if 
				//	nothing else, like myself, you'll learn about 
				//	the basics of audio cd pgmg by digging into this 
				//	code, as i knew nothing about it prior to this 
				//	effort. 
				// 
				//	the code started life from an incomplete C pgm 
				//	discovered in the bbs ether, by an anonymous 
				//	author.  i've attempted to document some of the 
				//	less obvious details of mscdex audio cd pgmg 
				//	and complete the work, by providing a neat little 
				//	shell pgm, which demonstrates the use of the classes. 
				//	the pgm and classes are hereby committed to the 
				//	public domain and you are free to use them, in 
				//	whatever way you see fit. 
				// 
				//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
				// 
				//	IF YOU DO USE THIS CODE IN ANY PROJECT, YOU'RE 
				//	COMPLETELY RESPONSIBLE FOR ALL DAMAGES INCURRED BY 
				//	ITS USE.  IN OTHER WORDS, WHETHER MODIFIED OR NOT, 
				//	I ACCEPT NO RESPONSIBILITY FOR ANY USE OF THE CODE 
				//	OR THE PROGRAM PROVIDED WITH THIS KIT. 
				// 
				//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
				// 
				//	perhaps you may want to clean the code up a bit. 
				//	personally, i've gotten tired of playing w/ it 
				//	(under dos), but it's pretty modular as c++ class 
				//	libs go, however there is room for improvement. 
				// 
				//	essentially, one simply instantiates a CDRom object 
				//	it's off to the races, sending commands to the 
				//	object and watching the results.  main.cpp illustrates 
				//	a simple pgm that uses most, if not all, of the 
				//	functionality of the class lib.  prior to instantiating 
				//	the CDRom object, one must call CDRom::InitClass() to 
				//	initialize the lib -> mscdex interface.  this fn 
				//	returns the nbr of drives mscdex knows about.  had 
				//	i been a little more fastidious, i might have added 
				//	a fn to present a list of drives from which one  
				//	could choose a particular cdrom drive, if more than 
				//	one unit were attached to a system.  since i don't 
				//	have a means of testing this facil, it wasn't 
				//	included, altho it would be very simple to write. 
				//	who knows, as i'm writing this prior to setting up 
				//	the final archive, such a routine *might* even make it 
				//	into the kit.... 
				// 
				//	also to be found in this kit is the mscdex 2.20 
				//	spec.  this was the spec that was the basis of the 
				//	work provided.  among other things, it has an rtf 
				//	(microsoft word) version of the spec, which will 
				//	make it possible to generate a pretty-printed 
				//	copy of the document. 
				// 
				//	the principal CDRom mbr fns are generally very simple 
				//	and could have been written as inline fns, but i 
				//	wasn't sure how complex i'd want to make to the command 
				//	dispatching when i started coding them.  again, for 
				//	those who're a bit more anal retentive than myself, 
				//	the src code is available for add'l modification. 
				// 
				//	commands are dispatched to the cdrom driver via 
				//	an ioctl object.  the public mbr fns it supports 
				//	are all related to cd audio operations. an ioctl 
				//	object only needs to know which unit (to be precise, 
				//	the sub-unit nbr) and the addresses of the cdrom 
				//	driver's strategy and interrupt functions.  most 
				//	of these operations could've been inlined, but 
				//	besides my lack of enthusiasm to labor on this too 
				//	much more, i also don't want to see the executable 
				//	size grow anymore.  the exe is currently 33 kb, of 
				//	which 90+ % is the library itself.  inlining more 
				//	of the code wouldn't have helped the size situation 
				//	and couldn't have done much (if anything) to improve 
				//	performance. 
				// 
				//	things to look out for:  audio cd track timing isn't 
				//	as perfect as one might expect.  that is, i've 
				//	noted minor discrepencies bet what's written on 
				//	cd liner and what's actually on the disc.  nothing 
				//	more than two or three seconds per track and, in my 
				//	opinion, not worth the bother of splitting hairs 
				//	for code that *might* make things absolutely perfect. 
				//	-  *no* error handling code.  since the consumer (the 
				//	user of the classes) can interrogate any meaningful 
				//	status information before initiating any cmd and 
				//	bec the cmds are *so* straight-fwd, it was decided to 
				//	leave out a safety net.  as noted elsewhere, it might 
				//	not be a good idea to interrogate the toc while a play 
				//	operation is underway, as this might interrupt the 
				//	audio pgm, depending on the cd driver sware.  my 
				//	limited testing hasn't encountered this prob, but a 
				//	full-blown cd audio player app should have the option to 
				//	deal w/ this contingency.  btw, any CDRom play fn will 
				//	stop the current audio pgm, in order to start a new pgm. 
				//	but this was dictated by the cdrom sub-system used to 
				//	develop this code (a sony cdu-31a, a sub-system to be 
				//	avoided, if at all possible.) 
				// 
 
 
#include		 
#include		 
#include		 
#include		 
#include	 	 
 
#include		"types.h"				// needed for our basic data types 
#include		"device.h"				// device driver and device cmd stuff 
#include		"ioctl.hpp"				// to communicate w/ the drivers 
										//	themselves 
#include		"cdrom.hpp" 
 
 
static union REGS 	inRegs, outRegs ;	// == used by the DOS interface 
static struct SREGS	segRegs ;			//	routines 
static DeviceList	deviceTable[ 26 ] ;	// == up to 26 cdrom drives supported 
 
 
			//	the obligatory dummy initializations that c++ demands for 
			//	static (class) mbr varbls.... 
 
int				CDRom::version = 0, 
				CDRom::firstLetter = 0, 
				CDRom::nbrOfDrives = 0 ; 
 
 
//******************************************************** 
// 
//		CDRom::CDRom  :- 
// 
//		prior to using this cstor the consumer must use 
//		the static (class) InitClass mbr fn to setup the 
//		internal list of cdrom drives, which will also 
//		return the nbr of available drives.  as w/ all 
//		operations covered by this system, there is *no* 
//		error checking for procedural foul-ups, so let the 
//		consumer beware.... 
// 
//********************************************************/ 
 
CDRom::CDRom( int cdDrv ) :  
	cmdInterface( cdDrv, ( int ) deviceTable[ cdDrv ].subUnit, 
		   		MK_FP( FP_SEG( deviceTable[ cdDrv ].deviceAddress ), 
					   deviceTable[ cdDrv ].deviceAddress->deviceStrat ), 
		   		MK_FP( FP_SEG( deviceTable[ cdDrv ].deviceAddress ), 
		   			   deviceTable[ cdDrv ].deviceAddress->deviceEntry ) ) 
{ 
 
	//	this shudn't be needed, but let's keep it around anyway.... 
	cdDriveNbr = cdDrv ; 
	GetDeviceStatus() ;					// find out if anything is happening 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::~CDRom  :- 
// 
//		don't really need an explicit dstor for the 
//		moment.... 
// 
//********************************************************/ 
 
CDRom::~CDRom() 
{ 
} 
 
 
//******************************************************** 
// 
//		CDRom::InitClass  :- 
// 
//		this static (class) mbr fn is to be called prior 
//		to instancing the 1st CDRom obj, in order to extract 
//		the cdrom device table from dos/mscdex.  returns 
//		the nbr of drives or zero if mscdex isn't loaded. 
// 
//********************************************************/ 
 
int		CDRom::InitClass( void )  
{ 
 
	//	get number of cdrom drive letters from mscdex, via the dos multiplex 
	//	interrupt 
	inRegs.x.ax = 0x1500 ; 
	inRegs.x.bx = 0 ;					// will be unmodified if mscdex isn't 
	int86( 0x2f, &inRegs, &outRegs ) ;	//   loaded 
	if( outRegs.x.bx == 0 ) { 
		return( 0 ) ; 
	} 
	 
	CDRom::nbrOfDrives = outRegs.x.bx ; 
	CDRom::firstLetter = outRegs.x.cx ; 
 
	//	get cdrom drive device list 
	inRegs.x.ax = 0x1501 ; 
	inRegs.x.bx = FP_OFF( ( DeviceList far * ) &deviceTable ) ; 
	segRegs.es = FP_SEG( ( DeviceList far * ) &deviceTable ) ; 
	int86x( 0x2f, &inRegs, &outRegs, &segRegs ) ; 
 
	//	finally, get the mscdex version code, but it only works for 
	//	mscdex 2.00 and above.... 
	inRegs.x.ax = 0x150C ; 
	inRegs.x.bx = 0 ; 
	int86x( 0x2f, &inRegs, &outRegs, &segRegs ) ; 
	CDRom::version = outRegs.x.bx ; 
 
	//	returns the nbr of drives or zero if this failed for some bizzare 
	//	reason, bec mscdex is in fact loaded and it wouldn't be around if 
	//	the cdrom device driver didn't load and normally they don't seem 
	//	to load if a working drive isn't detected.... 
	return( ! outRegs.x.cflag ? CDRom::nbrOfDrives : 0 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetUPC  :- 
// 
//		get the upc code from the disk, if it exists. 
//		the function returns non-zero if found or 
//		zero if not found.  the consumer can subsequently 
//		interrogate the driver for the precise failure (per 
//		the mscdex spec by calling GetErrorCode() immediately 
//		after calling GetUPC().... 
//		we really have no way of knowing if all of this works 
//		bec there are apparently so few disks that possess the 
//		code.... 
// 
//********************************************************/ 
 
int		CDRom::GetUPC( DWORD far * upcCode ) 
{ 
 
	int			i ; 
	WORD		st ; 
	DWORD		tmp = 0 ; 
	UPCCommand	upc ; 
 
	memset( &upc, 0, sizeof( UPCCommand ) ) ; 
	lastStatus = cmdInterface.GetUPC( ( struct UPCCommand far * ) &upc ) ; 
 
	//	mscdex sez that the cmd will retn an sect not fnd err if the operation 
	//	is supported by the driver, but the upc was missing/not found or 
	//	an unknwn cmd if not supported 
	st = GetErrorCode() ; 
	if ( st == ERROR_UNKWN_CMD || st == ERROR_SECT_NOT_FND ) 
		return( 0 ) ; 
 
	//	assumes the upc is a bcd value, which isn't entirely clear from the 
	//	mscdex spec.... 
	for ( i = 0 ; i <= 7 ; i++ ) { 
		tmp *= 10L ; 
		tmp = ( ( upc.upc[ i ] >> 4 ) * 10 ) + ( upc.upc[ i ] & 0x0f ) + tmp ; 
	} 
 
	//	if the control byte is 0 or if the upc itself is 0, then no UPC was 
	//	found, so synthesize the sector not fnd error 
	if ( upc.controlAddress == 0 || tmp == 0L ) { 
		st = lastStatus & 0xff00 ; 
		lastStatus = st | ERROR_SECT_NOT_FND ; 
		return 0 ; 
	} 
 
	*upcCode = tmp ; 
	return( 1 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetDeviceStatus  :- 
// 
//		get the most up-to-date device status from the 
//		cd and return it to the caller.... 
// 
//********************************************************/ 
 
DWORD	CDRom::GetDeviceStatus( void ) 
{ 
 
	DWORD	cdStatus ; 
	lastStatus = cmdInterface.GetDeviceStatus( ( DWORD far * ) &cdStatus ) ; 
	return cdStatus ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::IsItPlaying  :- 
// 
//		determine if the drive is playing by sampling the 
//		q-channel and the audio pause status sub-fn. 
// 
//********************************************************/ 
 
Boolean	CDRom::IsItPlaying( void ) 
{ 
 
	DWORD				detail, itTime, etTime, idTime, edTime ; 
	BYTE				iSec, eSec ; 
	struct dostime_t	iTime, eTime ; 
 
	//	sample the q-channel for up to a couple seconds, to determine if 
	//	something is happening.... 
	GetQInfo( ( DWORD far * ) &detail, ( DWORD far * ) &itTime, 
			  ( DWORD far * ) &idTime ) ; 
	_dos_gettime( &iTime ) ; 
	while ( 1 ) { 
		_dos_gettime( &eTime ) ; 
		if ( abs( eTime.second - iTime.second ) > 2 ) 
			break ; 
	} 
 
	GetQInfo( ( DWORD far * ) &detail, ( DWORD far * ) &etTime, 
			  ( DWORD far * ) &edTime ) ; 
	iSec = HIBYTE( LOWORD( itTime ) ) ; 
	eSec = HIBYTE( LOWORD( etTime ) ) ; 
 
	//	if the sampled values are different, then the disk is playing.... 
	if ( iSec != eSec ) 
		return ( 1 ) ; 
	return( 0 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetPlayStatus  :- 
// 
//		gets the current play status (is the audio pgm 
//		paused and the start/end address of the audio pgm.) 
//		returns the desired info only if the corresponding 
//		ptrs are non-null.... 
// 
//********************************************************/ 
 
void	CDRom::GetPlayStatus( WORD far * paused, DWORD far * stAddr, 
							  DWORD far * endAddr ) 
{ 
 
	PlayStatusCmd	plStatus ; 
	lastStatus = cmdInterface.GetPlayStatus( 
								( struct PlayStatusCmd far * ) &plStatus ) ; 
 
	if ( paused ) { 
		//	simplify it for 'em 
		if ( plStatus.statusBits & 1 ) 
			*paused = 1 ; 
		else *paused = 0 ; 
	} 
	if ( stAddr ) 
		*stAddr = plStatus.startAddress ; 
	if ( endAddr ) 
		*endAddr = plStatus.endAddress ; 
 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PrintPlayStatus  :- 
// 
//		a diagnostic for testing the GetPlayStatus() mbr 
//		fn.... 
// 
//********************************************************/ 
 
void	CDRom::PrintPlayStatus( DWORD stAddr, DWORD endAddr ) 
{ 
	DisplayAddress( "Start Address == ", stAddr ) ; 
	printf( "\n" ) ; 
	DisplayAddress( "End Address == ", endAddr ) ; 
	printf( "\n" ) ; 
	return ; 
} 
 
 
//******************************************************** 
// 
//		CDRom::LockDoor  :- 
// 
//		if the drive supports the fn, lock the door. 
//		no check.... 
// 
//********************************************************/ 
 
void		CDRom::LockDoor( int lock ) 
{ 
 
	lastStatus = cmdInterface.LockDoor( lock ) ; 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::CloseTray  :- 
// 
//		do the opposite of Eject, that is if the drive 
//		supports the fn.  no check.... 
// 
//********************************************************/ 
 
void	CDRom::CloseTray( void ) 
{ 
 
	lastStatus = cmdInterface.CloseTray() ; 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::Eject  :- 
// 
//		eject the disk (obviously....) 
// 
//********************************************************/ 
 
void	CDRom::Eject( void ) 
{ 
 
	lastStatus = cmdInterface.EjectDisk() ; 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::Reset  :- 
// 
//		tell the cdrom driver to reset itself and therefore 
//		stop any operation in progress.... 
// 
//********************************************************/ 
 
void	CDRom::Reset( void ) 
{ 
 
	lastStatus = cmdInterface.ResetDisk() ; 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::Stop  :- 
// 
//		stop/resume the current audio pgm immdiately. 
// 
//********************************************************/ 
 
void	CDRom::Stop( Boolean fStop ) 
{ 
 
	//	unnecessary to save the status for stop and probably 
	//  the same for pause, but do it for the sake of consistency.... 
	lastStatus = cmdInterface.Stop( fStop ) ; 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PlayDisk  :- 
// 
//		play an audio pgm starting at a certain track. 
//		must have called CDRom::ReadTOC() prior to using 
//		this member fn.... 
// 
//********************************************************/ 
 
int		CDRom::PlayDisk( int trackNumber ) 
{ 
 
	TrackInfoCmd	* ptr ; 
	DWORD			num ; 
	Str80			theBuff ; 
	int				mins, secs ; 
 
	if ( ( trackNumber < diskInfo.lowTrackNumber ) ||  
		 ( trackNumber > diskInfo.highTrackNumber ) ) { 
#if		! defined( _WINDOWS ) 
		printf( "%%%%%%  Invalid track number: %d.\n" 
				"This disk has track numbers between %d and %d.\n", 
				trackNumber, diskInfo.lowTrackNumber, 
				diskInfo.highTrackNumber ) ; 
#endif  
		return( 1 ) ; 
	} 
 
#if		! defined( _WINDOWS ) 
	printf( "Playing from " ) ; 
	FormatDuration( trackNumber, TRACK_TIME | REM_DISK_TIME, theBuff ) ; 
	printf( "%s\n", theBuff ) ; 
#endif 
 
	//	what's bizarre here is the addressing mode we request is 
	//	supposedly red-book, but the conversion to hsg is real.  tried 
	//	simply changing the addressing mode to ADDR_HSG, which failed 
	//	completely.  i can only imagine that true hsg-formatted disks have 
	//	a different geometry from redbook disks, but mscdex only supports 
	//	one true addressing mode and the addressing mode byte is simply 
	//	a way of indicating the consumer's agreement that the media in 
	//	the drive actually conforms red-book geometry.... 
	num = RedToHsg( diskInfo.leadOut ) - 
		  RedToHsg( trackInfo[ trackNumber ].startAddress ) ; 
	return( Play( trackInfo[ trackNumber ].startAddress, num, ADDR_RED ) ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PlayTrack  :- 
// 
//		play a sgl track, probably as part of a complex 
//		audio pgm.... 
// 
//********************************************************/ 
 
int		CDRom::PlayTrack( int trackNbr ) 
{ 
 
	DWORD		num ; 
	Str80		theBuff ; 
 
	if ( ( trackNbr < diskInfo.lowTrackNumber ) ||  
		 ( trackNbr > diskInfo.highTrackNumber ) ) { 
#if		! defined( _WINDOWS ) 
		printf( "%%%%%%  Invalid track number: %d.\n" 
				"This disk has track numbers between %d and %d.\n", 
				trackNbr, diskInfo.lowTrackNumber, 
				diskInfo.highTrackNumber ) ; 
#endif  
		return( 1 ) ; 
	} 
 
#if		! defined( _WINDOWS ) 
	printf( "Playing Sgl " ) ; 
	FormatDuration( trackNbr, TRACK_TIME, theBuff ) ; 
	printf( "%s\n", theBuff ) ; 
#endif 
 
	num = RedToHsg( trackInfo[ trackNbr + 1 ].startAddress ) - 
		  RedToHsg( trackInfo[ trackNbr ].startAddress ) ; 
	return( Play( trackInfo[ trackNbr ].startAddress, num, ADDR_RED ) ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PlayPreview  :- 
// 
//		play a preview of the entire disk, by sampling the 
//		1st 10 seconds of each track on the disk.... 
// 
//********************************************************/ 
 
void	CDRom::PlayPreview( void ) 
{ 
 
	int	nTracks = diskInfo.highTrackNumber - diskInfo.lowTrackNumber + 1, i, 
				prlgth, prmins, prsecs ; 
	DWORD		num, details, tTime, dTime ; 
	BYTE		secs, lsecs ; 
 
	//	do n seconds worth of music for each track on the disk, but 
	//	setup n + 1 secs worth and clip it usg GetQInfo(), where n 
	//	is determined by the _PRVW_LGTH symbol.... 
	for ( i = diskInfo.lowTrackNumber ; nTracks > 0 ; nTracks--, i++ ) { 
		//	hang in a tight loop until the track is done 
		lsecs = secs = 0 ; 
 
		//	must make sure that the track time is more than the preview lgth 
		//	(which must be less than a minute.)  if the preview lgth > 
		//	the current track's duration, then reduce the lgth of the preview 
		prlgth = _PRVW_LGTH ; 
		ComputeDuration( i, TRACK_TIME, ( int far * ) &prmins, 
						 ( int far * ) &prsecs ) ; 
		prmins = prmins * 60 + prsecs ; 
		if ( prmins > ( prlgth + 1 ) ) 
			num = ( prlgth + 1 ) * 75L ; 
		else { 
			prlgth = prmins - 2 ; 
			num = ( prlgth + 1 ) * 75L ; 
		} 
		Play( trackInfo[ i ].startAddress, num, ADDR_RED ) ; 
 
		//	any key will abort the operation.... 
		while ( secs != prlgth ) { 
			GetQInfo( ( DWORD far * ) &details, ( DWORD far * ) &tTime, 
					  ( DWORD far * ) &dTime ) ; 
			secs = HIBYTE( LOWORD( tTime ) ) ; 
#if		! defined( _WINDOWS ) 
			if ( lsecs != secs ) { 
				printf( "Previewing TNø: %2d  ðð  (00:%02d)\r", i, secs ) ; 
				lsecs = secs ; 
			} 
#if		0 
			//	a final sanity check which will get us back on track in case 
			//	the pgm ends when we're not looking.... 
			if ( ! IsBusy() ) 
				break ; 
#endif 
			if ( kbhit() ) { 
				//	swallow this keystroke and leave.... 
				getch() ; 
				putch( '\n' ) ; 
				return ; 
			} 
#endif 
		} 
	} 
 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::ReadTOC  :- 
// 
//		read and store the toc of the current cd.  normally 
//		the toc shud be read at a time when the drive isn't 
//		busy, to avoid having to disrupt an operation in 
//		progress or an error due to a drive busy 
//		condition.... 
// 
//********************************************************/ 
 
int		CDRom::ReadTOC( void ) 
{ 
 
	TrackInfoCmd	far * ptr ; 
	DiskInfoCmd		far * inf = ( DiskInfoCmd far * ) &diskInfo ; 
	int				idx ; 
 
	if ( ( cmdInterface.GetDiskInfo( inf ) ) & IOS_ERROR ) 
		//	if the disk info operation failed, try it again, bec the 
		//	underlying driver may be like the crap from sony, which 
		//	always fails the initial attempt to read the cd, after 
		//	insertion/power-up/etc.... 
		if ( ( cmdInterface.GetDiskInfo( inf ) ) & IOS_ERROR ) { 
			//	this can now be considered an authentic failure, so 
			//	reset the drive 
			Reset() ; 
			//	and try the operation one final time.... 
			if ( ( cmdInterface.GetDiskInfo( inf ) ) & IOS_ERROR ) 
				   return( 1 ) ; 
		} 
 
	//	for each track on the disk, get the starting track address.... 
	for ( ptr = ( TrackInfoCmd far * ) &trackInfo[ diskInfo.lowTrackNumber ], 
		  idx = diskInfo.lowTrackNumber ; idx <= diskInfo.highTrackNumber ; 
		  idx++ ) { 
		ptr->trackNumber = idx ; 
		cmdInterface.GetTrackInfo( ptr ) ; 
		ptr++ ; 
	} 
 
	//	the final entry in the track info table contains the leadout  
	//	track as its starting address.  this is the last track on the 
	//	disk and permits the correct calculation of the duration of 
	//	the last track on the disk.... 
	ptr->startAddress = diskInfo.leadOut ; 
	return( 0 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PrintTOC  :- 
// 
//		print the toc for the current cd, read during a 
//		preceding call to ReadTOC().... 
// 
//********************************************************/ 
 
void	CDRom::PrintTOC( void ) 
{ 
 
#if		! defined( _WINDOWS ) 
 
	int				idx ; 
	WORD			st ; 
	DWORD			upc ; 
	char			* pszSep ; 
	Str80			theBuff ; 
	TrackInfoCmd	* ptr ; 
 
	//	print the heading 
	printf( "Table Of Contents   ðð" ) ; 
	FormatDuration( 0, TOTAL_TIME, theBuff ) ; 
 
#if		defined( _PRINT_UPC ) 
	printf( "  %s ðð", theBuff ) ; 
	if ( GetUPC( ( DWORD far * ) &upc ) ) 
		sprintf( theBuff, "  UPC:  %08ld\n", upc ) ; 
	else { 
		st = GetErrorCode() ; 
		if ( st == ERROR_UNKWN_CMD ) 
			strcpy( theBuff, "  UPC not supported\n" ) ; 
		else strcpy( theBuff, "  UPC not available\n" ) ; 
	} 
	printf( theBuff ) ; 
#else 
	//	not printing the UPC makes certain drive systems display the 
	//	toc a couple seconds faster.... 
	printf( "  %s ðð\n", theBuff ) ; 
#endif 
 
	//	now do the actual toc, 3-up 
	ptr = &trackInfo[ diskInfo.lowTrackNumber ] ; 
	for( idx = diskInfo.lowTrackNumber ; idx <= diskInfo.highTrackNumber ; 
		 idx++ ) { 
		FormatDuration( idx, TRACK_TIME, theBuff ) ; 
		if ( idx % 3 == 0 ) 
			pszSep = "ðð\n" ; 
		else pszSep = "  ðð  " ; 
		printf( "%s%s", theBuff, pszSep ) ; 
		ptr++ ; 
	} 
 
	if ( ( idx - 1 ) % 3 != 0 ) 
		printf( "\n" ) ; 
 
	return ; 
 
#endif 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetQInfo  :- 
// 
//		poll the cd's q info channel and return the data 
//		to the caller in three dwords.... 
// 
//********************************************************/ 
 
int		CDRom::GetQInfo( DWORD far * trackDetails, DWORD far * trackTime, 
						 DWORD far * diskTime ) 
{ 
 
	int				i, j ; 
	QChannelInfoCmd	qInfo ; 
 
	memset( &qInfo, 0, sizeof( QChannelInfoCmd ) ) ; 
	lastStatus = cmdInterface.GetQInfo( 
								( struct QChannelInfoCmd far * ) &qInfo ) ; 
	if ( lastStatus & IOS_ERROR ) 
		return( 1 ) ; 
 
	//	pack the info into incoming dwords, in the same order as documented 
	//	in the mscdex spec, but decode the track nbr and x/index since 
	//	they're really only useful as binary data.... 
	*trackDetails = qInfo.control ; 
	i = ( ( qInfo.trackNumber >> 4 ) & 0x0f ) * 10 ;  
	j = ( qInfo.trackNumber & 0x0f ) ; 
	*trackDetails = ( *trackDetails << 8 ) + i + j ; 
	i = ( ( qInfo.x >> 4 ) & 0x0f ) * 10 ;  
	j = ( qInfo.x & 0x0f ) ; 
	*trackDetails = ( *trackDetails << 8 ) + i + j ; 
 
	*trackTime = qInfo.minute ; 
	*trackTime = ( *trackTime << 8 ) + qInfo.second ; 
	*trackTime = ( *trackTime << 8 ) + qInfo.frame ; 
 
	*diskTime = qInfo.pMin ; 
	*diskTime = ( *diskTime << 8 ) + qInfo.pSec ; 
	*diskTime = ( *diskTime << 8 ) + qInfo.pFrame ; 
	return( 0 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::PrintQInfo  :- 
// 
//		display q channel info under dos.  the mscdex spec 
//		indicates that *if* the ctrl/adr BYTE is not 1, 
//		then *if* the ctrl/adr BYTE isn't 1, then all 
//		data is passed back to the caller in its raw (bcd ?) 
//		form.  otherwise, only the track details are passed 
//		back in raw form.  now under which circumstances can 
//		one expect ctrl/adr != 1.... 
// 
//********************************************************/ 
 
void	CDRom::PrintQInfo( DWORD trackDetails, DWORD trackTime, 
						   DWORD diskTime ) 
{ 
 
#if		! defined( _WINDOWS ) 
	int		control, trackNumber, x, minute, second, frame, pMin, 
			pSec, pFrame ; 
 
	control = LOBYTE( HIWORD( trackDetails ) ) ; 
	trackNumber = HIBYTE( LOWORD( trackDetails ) ) ; 
	x = LOBYTE( LOWORD( trackDetails ) ) ; 
	printf( "Control/ADR: 0x%02x ðð TNø: %02d ðð Point/Index: %02d\n", 
			control, trackNumber, x ) ; 
 
	minute = LOBYTE( HIWORD( trackTime ) ) ; 
	second = HIBYTE( LOWORD( trackTime ) ) ; 
	frame = LOBYTE( LOWORD( trackTime ) ) ; 
	printf( "Time into ðð track: %2dm:%02ds.%02df ðð ", minute, second, 
			frame ) ; 
 
	pMin = LOBYTE( HIWORD( diskTime ) ) ; 
	pSec = HIBYTE( LOWORD( diskTime ) ) ; 
	pFrame = LOBYTE( LOWORD( diskTime ) ) ; 
	printf( "disk: %2dm:%02ds.%02df\n", pMin, pSec, pFrame ) ; 
#endif 
 
	return ; 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetTimeRemaining  :- 
// 
//		Get the time remaining for this program.  the 
//		consumer *must* make sure that the audio pgm is 
//		is already underway, the disk is in the drive, 
//		etc., as the routine assumes this is the total 
//		responsibility of the consumer.... 
// 
//********************************************************/ 
 
void	CDRom::GetTimeRemaining( int far * ptMins, int far * ptSecs, 
								 int far * pdMins, int far * pdSecs ) 
{ 
 
	int			tNbr, mins, secs ; 
	DWORD		details, track, disk, endPgm ; 
 
	//	the calculation is relative to where we are at present 
	GetQInfo( ( DWORD far * ) &details, ( DWORD far * ) &track, 
			  ( DWORD far * ) &disk ) ; 
 
	//	time remaining for a given track is the curr pos in the disk 
	//	subtracted from the start of the next track.... 
	tNbr = HIBYTE( LOWORD( details ) ) ; 
	ComputeSpan( disk, trackInfo[ tNbr + 1 ].startAddress, ptMins, ptSecs ) ; 
 
	//	while the time remaining on disk is the curr pos subtracted from 
	//	the end of the last play request.... 
	GetPlayStatus( NULL, NULL, ( DWORD far * ) &endPgm ) ; 
	ComputeSpan( disk, endPgm, pdMins, pdSecs ) ; 
	return ;							// trop simple, n'est pas ? 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::FormatDuration  :- 
// 
//		format the consumer's string w/ the desired 
//		duration info.  any combination of duration fmtg 
//		bits can be used.  all info will be packed into 
//		a continuous string.... 
// 
//********************************************************/ 
 
void	CDRom::FormatDuration( int theTrack, WORD fmtType, Str80 theBuff ) 
{ 
 
	char	* pszT = theBuff ; 
	int		i = TRACK_TIME, mins, secs ; 
 
	//	this loop shifts thru each bit of fmtType, in order to attach 
	//	the desired type of info to theBuff, hence the reason these bits 
	//	must remain in the order shown in the hdr file.... 
	while ( fmtType ) { 
		if ( fmtType & 1 ) { 
			ComputeDuration( theTrack, i, ( int far * ) &mins, 
							 ( int far * ) &secs ) ; 
			switch ( i ) { 
				case TRACK_TIME: 
					sprintf( pszT, "TNø %-2d: (%2dm:%02ds) ", theTrack, mins, 
							 secs ) ; 
					break ; 
 
				case REM_DISK_TIME: 
					sprintf( pszT, "Time remaining: (%2dm:%02ds) ", mins, 
							 secs ) ; 
					break ; 
 
				case TOTAL_TIME: 
					sprintf( pszT, "Total time: (%2dm:%02ds) ", mins, secs ) ; 
					break ; 
			} 
			//	re-cycle the ptr for the next element to be attached 
			pszT = &theBuff[ strlen( pszT ) ] ; 
		} 
		//	get the next duration type to do.... 
		i <<= 1 ; fmtType >>= 1 ; 
	} 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetTrackInfo  :- 
// 
//		get the duration of the specified track.  naturally, 
//		the consumer must have called ReadTOC() prior to 
//		using this fn.  if trackNbr == 0, then return the 
//		lgth of all audio tracks.... 
// 
//********************************************************/ 
 
void	CDRom::GetTrackInfo( int trackNbr, int far * pMins, int far * pSecs ) 
{ 
	int	i ; 
	if ( trackNbr ) 
		i = TRACK_TIME ; 
	else i = TOTAL_TIME ; 
	ComputeDuration( trackNbr, i, pMins, pSecs ) ; 
	return ; 
} 
 
 
//******************************************************** 
// 
//		CDRom::GetDiskInfo  :- 
// 
//		get the starting and ending tracks on the disc.... 
// 
//********************************************************/ 
 
void	CDRom::GetDiskInfo( int far * psTrack, int far * peTrack ) 
{ 
	*psTrack = diskInfo.lowTrackNumber ; 
	*peTrack = diskInfo.highTrackNumber ; 
	return ; 
} 
 
 
/***************************************************************** 
 
	CDRom class private methods 
 
*****************************************************************/ 
 
 
//******************************************************** 
// 
//		CDRom::Play  :- 
// 
//		play some audio at a particular address for a 
//		particular nbr of frames.  stops the current audio 
//		pgm, if one is already underway.... 
// 
//********************************************************/ 
 
int 	CDRom::Play( DWORD startAddress, DWORD frames, BYTE addressMode ) 
{ 
 
	if ( IsBusy() ) 
		Stop() ; 
 
	lastStatus = cmdInterface.Play( startAddress, frames, addressMode ) ; 
 
	//	we know this cmd will make the drive become busy, but did it 
	//	cause/induce a device error ? 
	if ( lastStatus & IOS_ERROR ) 
		return( 1 ) ; 
 
	return( 0 ) ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::RedToHsg  :- 
// 
//		convert a red-book cd rom address into the 
//		corresponding hi-sierra address.... 
// 
//********************************************************/ 
 
DWORD CDRom::RedToHsg(DWORD theValue) 
{ 
	return( ( DWORD ) ( LOBYTE( HIWORD( theValue ) ) ) * 60 * 75 + 
			( DWORD ) ( HIBYTE( LOWORD( theValue ) ) ) * 75 + 
			( DWORD ) ( LOBYTE( LOWORD( theValue ) ) ) ) ; 
} 
 
 
//******************************************************** 
// 
//		CDRom::DisplayAddress  :- 
// 
//		display a binary red-book address in min:sec.frames 
//		format.... 
// 
//********************************************************/ 
 
void	CDRom::DisplayAddress( Str255 theMessage, DWORD theAddress ) 
{ 
 
#if		! defined( _WINDOWS ) 
	printf( theMessage ) ; 
	printf( "%2d:", LOBYTE( HIWORD( theAddress ) ) ) ; 
	printf( "%02d.", HIBYTE( LOWORD( theAddress ) ) ) ; 
	printf( "%02d", LOBYTE( LOWORD( theAddress ) ) ) ; 
#endif 
 
	return ; 
 
} 
 
 
//******************************************************** 
// 
//		CDRom::ComputeSpan  :- 
// 
//		compute the time span between two audio disk 
//		addresses.... 
// 
//********************************************************/ 
 
void	CDRom::ComputeSpan( DWORD iAddr, DWORD eAddr, int far * pMins, 
							int far * pSecs ) 
{ 
 
	WORD		stMins, stSecs, endMins, endSecs ; 
	int			min, sec ; 
 
	stMins = LOBYTE( HIWORD( iAddr ) ) ; 
	stSecs = HIBYTE( LOWORD( iAddr ) ) ; 
 
	endMins = LOBYTE( HIWORD( eAddr ) ); 
	endSecs = HIBYTE( LOWORD( eAddr ) ) ; 
 
	sec = endSecs - stSecs ; 
	min = endMins - stMins ; 
 
	//	do a little modulo-60 arithmetic, if needed.... 
	if ( sec < 0 ) { 
		sec += 60 ; 
		min-- ; 
		if ( min < 0 ) { 
			min = 0 ; 
		} 
	} 
 
	*pMins = min ; 
	*pSecs = sec ; 
	return ; 
} 
 
 
//******************************************************** 
// 
//		CDRom::ComputeDuration  :- 
// 
//		compute the duration of the specified range of 
//		tracks.  unlike FormatDuration(), ComputeDuration() 
//		only deals w/ one type of computation at a time. 
//		see FormatDuration() for addl details.... 
// 
//********************************************************/ 
 
void	CDRom::ComputeDuration( int theTrack, WORD compType, int far * pMins, 
								int far * pSecs ) 
{ 
 
	TrackInfoCmd 	* first, * second ; 
	int				stTrk, endTrk ; 
 
	switch ( compType ) { 
		case TOTAL_TIME: 
			//	we can disregard theTrack here, for what shud be obvious 
			//	reasons.... 
			stTrk = diskInfo.lowTrackNumber ; 
			endTrk = diskInfo.highTrackNumber + 1 ; 
			break ; 
 
		case REM_DISK_TIME: 
			//	not really remaining time on the disk, since this 
			//	will normally be called when play is about to 
			//	commence.... 
			stTrk = theTrack ; 
			endTrk = diskInfo.highTrackNumber + 1 ; 
			break ; 
 
		case TRACK_TIME: 
		default: 
			stTrk = theTrack ; 
			endTrk = theTrack + 1 ; 
			break ; 
	} 
 
	ComputeSpan( trackInfo[ stTrk ].startAddress, 
				 trackInfo[ endTrk ].startAddress, pMins, pSecs ) ; 
	return ; 
 
}