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 ; }