/******************************Module*Header*******************************\ * Module Name: trklst.c * * This module manipulates the cdrom track list. The table of contents MUST * be locked for ALL cdrom devices before calling any functions in this module. * * Created: 02-11-93 * Author: Stephen Estrop [StephenE] * * Copyright (c) 1993 Microsoft Corporation \**************************************************************************/ #pragma warning( once : 4201 4214 ) #define NOOLE #include /* required for all Windows applications */ #include #include #include #include /* contains portable ascii/unicode macros */ #include "resource.h" #include "cdplayer.h" #include "cdapi.h" #include "scan.h" #include "database.h" #include "trklst.h" /******************************Public*Routine******************************\ * ComputeDriveComboBox * * This routine deletes and then reads all the drive (artist) selections * to the drive combobox. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void ComputeDriveComboBox( void ) { int i,index; HWND hwnd; hwnd = g_hwndControls[INDEX(IDC_ARTIST_NAME)]; SetWindowRedraw( hwnd, FALSE ); ComboBox_ResetContent( hwnd ); index = 0; for( i = 0; i < g_NumCdDevices; i++ ) { ComboBox_InsertString( hwnd, -1, i ); if ( i == g_CurrCdrom ) { index = i; } } SetWindowRedraw( hwnd, TRUE ); ComboBox_SetCurSel( hwnd, index ); RedrawWindow( hwnd, NULL, NULL, RDW_INVALIDATE ); UpdateWindow( hwnd ); } /*****************************Private*Routine******************************\ * SwitchToCdrom * * This routine is called when the used selects a new cdrom device * to access. It handles reset the state of both the "old" and "new" * chosen cdroms. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void SwitchToCdrom( int NewCdrom, BOOL prompt ) { int oldState, oldState2; TCHAR s1[256], s2[256]; oldState = g_Devices[g_LastCdrom]->State; oldState2 = g_Devices[g_CurrCdrom]->State; if (NewCdrom != g_LastCdrom) { if (prompt) { if (g_Devices[g_CurrCdrom]->State & CD_PLAYING) { _tcscpy( s1, IdStr( STR_CANCEL_PLAY ) ); _tcscpy( s2, IdStr( STR_CHANGE_CDROM ) ); if ( MessageBox( g_hwndApp, s1, s2, MB_APPLMODAL | MB_DEFBUTTON1 | MB_ICONQUESTION | MB_YESNO) != IDYES ) { return; } } } /* ** stop the drive we're leaving */ g_CurrCdrom = g_LastCdrom; if (prompt && (g_State & (CD_PLAYING | CD_PAUSED)) ) { HWND hwndButton; hwndButton = g_hwndControls[INDEX(IDM_PLAYBAR_STOP)]; SendMessage( hwndButton, WM_LBUTTONDOWN, 0, 0L ); SendMessage( hwndButton, WM_LBUTTONUP, 0, 0L ); } else { if ( StopTheCdromDrive( g_LastCdrom ) ) { g_State &= (~(CD_PLAYING | CD_PAUSED)); g_State |= CD_STOPPED; } } /* ** Set new cdrom drive and initialize time fields */ g_LastCdrom = g_CurrCdrom = NewCdrom; TimeAdjustInitialize( g_CurrCdrom ); if ( (oldState & CD_PAUSED) || (oldState2 & CD_PAUSED) ) { SendMessage( g_hwndApp, WM_COMMAND, IDM_PLAYBAR_PLAY, 0L ); SendMessage( g_hwndApp, WM_COMMAND, IDM_PLAYBAR_PAUSE, 0L ); } } } /*****************************Private*Routine******************************\ * FindTrackNodeFromTocIndex * * This routine returns the node in the listed pointed to by listhead which * has the TocIndex equal to tocindex. NULL is returned if it is not * found. Returning NULL can easily bomb out the program -- but we should * never be calling this routine with an invalid tocindex, and thus really * never SHOULD return NULL. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ PTRACK_INF FindTrackNodeFromTocIndex( int tocindex, PTRACK_INF listhead ) { PTRACK_INF t; for( t = listhead; ((t!=NULL) && (t->TocIndex!=tocindex)); t=t->next ); return t; } /*****************************Private*Routine******************************\ * FindFirstTrack * * This routine computes the first "playable" track on a disc by * scanning the the play order of the tracks * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ PTRACK_PLAY FindFirstTrack( int cdrom ) { if ( (g_Devices[cdrom]->State & CD_NO_CD) || (g_Devices[cdrom]->State & CD_DATA_CD_LOADED) ) { return NULL; } return PLAYLIST(cdrom); } /*****************************Private*Routine******************************\ * FindLastTrack * * This routine computes the last "playable" track on a disc by * scanning the the play order of the tracks * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ PTRACK_PLAY FindLastTrack( IN INT cdrom ) { PTRACK_PLAY tr; if ( PLAYLIST(cdrom) == NULL ) { return NULL; } for( tr = PLAYLIST(cdrom); tr->nextplay != NULL; tr = tr->nextplay ); return tr; } /*****************************Private*Routine******************************\ * AllTracksPlayed * * This routine searches the play lists for all cdrom drives and * returns a flag as to whether all tracks on all cdrom drives have * been played. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ BOOL AllTracksPlayed( void ) { INT i; BOOL result = TRUE; for( i = 0; i < g_NumCdDevices; i++ ) { result &= (CURRTRACK(i) == NULL); } return result; } /*****************************Private*Routine******************************\ * FindNextTrack * * This routine computes the next "playable" track. This is a * one way door...i.e., the structures are manipulated. It uses * the following algorithms: * * Single Disc Play: * * * if next track is not NULL, return next track * * If next track is NULL, and wrap==TRUE, return * first track * * return NULL * * Multi-Disc Play: * * * if we're in random play, select a random drive to play from. * * if next track on current cdrom != NULL, return next track * * if it is NULL: * * * check next cdrom device, if current track is not NULL * return CURRTRACK for that device and set gCurrCdrom to * that device * * if NULL, go to next drive * * last drive, check wrap * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ PTRACK_PLAY FindNextTrack( BOOL wrap ) { int i; /* ** First, bump current track pointer */ if ( CURRTRACK(g_CurrCdrom) != NULL ) { CURRTRACK(g_CurrCdrom) = CURRTRACK(g_CurrCdrom)->nextplay; } else { if ( g_fSingleDisk ) { return NULL; } } /* ** Do we need to switch drives? */ if ( (!g_fSelectedOrder) && (!g_fSingleDisk) ) { /* ** Need to random to new cdrom */ g_CurrCdrom = rand() % g_NumCdDevices; } /* ** Is chosen track playable? */ if ( CURRTRACK(g_CurrCdrom) != NULL ) { /* ** Yep, so this is the easy case */ return CURRTRACK(g_CurrCdrom); } /* ** Ok, CURRENT track on this device is not defined, ** so are we in multi-disc mode? */ if ( !g_fSingleDisk ) { /* ** have all tracks played? */ if ( AllTracksPlayed() ) { /* ** if wrap, reset all drives to front of their playlist */ if ( wrap ) { /* ** If we are in random play mode we need to re-shuffle the ** track list so that people don't get the same tracks repeated ** again. */ if (!g_fSelectedOrder) { RestorePlayListsFromShuffleLists(); ComputeAndUseShufflePlayLists(); } for ( i = 0; i < g_NumCdDevices; i++ ) { CURRTRACK(i) = FindFirstTrack(i); } } else { /* ** All tracks on all drives have played, and we are NOT ** in continuous mode, so we are done playing. Signify ** this by returning NULL (no playable tracks left). */ return NULL; } } /* ** We're in mulit-disc play mode, and all the play lists should ** be reset now. Cycle through cdrom drives looking for a playable ** track. */ i = g_CurrCdrom; do { g_CurrCdrom++; if ( g_CurrCdrom >= g_NumCdDevices ) { /* ** We hit the end of the list of devices, if we're ** in continuous play mode, we need to wrap to the ** first cdrom drive. Otherwise, we are done playing ** as there are no tracks left to play. */ if ( wrap || (!g_fSelectedOrder) ) { g_CurrCdrom = 0; } else { g_CurrCdrom--; return NULL; } } } while( (CURRTRACK(g_CurrCdrom) == NULL) && (i != g_CurrCdrom) ); /* ** At this point we either have a playable track, or we ** are back where we started from and we're going to return ** NULL because there are no playable tracks left. */ return CURRTRACK(g_CurrCdrom); } else { /* ** We're in single disc mode, and current track is NULL, ** which means we hit the end of the playlist. So, check ** to see if we should wrap back to the first track, or ** return NULL to show that we're done playing. */ if (wrap) { /* ** If we are in random play mode we need to re-shuffle the ** track list so that people don't get the same tracks repeated ** again. */ if (!g_fSelectedOrder) { RestorePlayListsFromShuffleLists(); ComputeAndUseShufflePlayLists(); } /* ** wrap to start of the play list */ CURRTRACK(g_CurrCdrom) = FindFirstTrack(g_CurrCdrom); } return CURRTRACK(g_CurrCdrom); } } /*****************************Private*Routine******************************\ * FindPrevTrack * * This routine computes the previous "playable" track on a disc by * scanning the play order of the tracks from the current * track to the start of the play list. If we are at the start * of the play list, then move to the end of the list if we * are in "wrap" (i.e., continuous) play mode, otherwise return * the current track. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ PTRACK_PLAY FindPrevTrack( int cdrom, BOOL wrap ) { /* ** Is current track valid? */ if ( CURRTRACK(cdrom) == NULL ) { return NULL; } /* ** If we're in multi disc play && random, the previous track ** is undefined since we could be jumping around on ** multiple discs. ** ** Should Fix -- do we want to allow users to back up in the random ** list of a particular drive? */ if ((!g_fSingleDisk) && (!g_fSelectedOrder)) { return CURRTRACK(cdrom); } /* ** Did we hit the start of the play list? */ if ( CURRTRACK(cdrom)->prevplay == NULL ) { /* ** We hit the start of the list, check to see if we should ** wrap to end of list or not... */ if ( wrap && g_fSingleDisk ) { return FindLastTrack(cdrom); } else { return CURRTRACK(cdrom); } } return CURRTRACK(cdrom)->prevplay; } /*****************************Private*Routine******************************\ * FindContiguousEnd * * This routine returns the node of the track within PlayList which makes * the largest contiguous block of tracks starting w/the track pointed * to by "tr." It is used to play multiple tracks at as one track * when they are programmed to be played in sequence. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ int FindContiguousEnd( int cdrom, PTRACK_PLAY tr ) { int i; PTRACK_PLAY trend; /* ** If we're in muti-disc random play, we only play ** one track at a time, so just return next track. */ if ( (!g_fSelectedOrder) && (!g_fSingleDisk) ) { return tr->TocIndex + 1; } /* ** go forward in the play list looking for contiguous blocks ** of tracks to play together. We need to check the TocIndex ** of each track to see if they are in a "run" [ like 2-5, etc. ] */ i= tr->TocIndex + 1; trend = tr; while ( (trend->nextplay != NULL) && (trend->nextplay->TocIndex == i) ) { trend = trend->nextplay; i++; } return trend->TocIndex + 1; } /*****************************Private*Routine******************************\ * FlipBetweenShuffleAndOrder * * This routine handles going from ordered play to shuffle play and vica\versa. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void FlipBetweenShuffleAndOrder( void ) { if ( (!g_fSelectedOrder) ) { /* ** Transitioning from Random to Ordered Play */ RestorePlayListsFromShuffleLists(); } else { /* ** Transitioning from Ordered to Random Play */ ComputeAndUseShufflePlayLists(); } /* ** If we were playing, we need to restart the play to make sure ** we don't play past where we should. */ if ( g_State & CD_PLAYING ) { SeekToCurrSecond( g_CurrCdrom ); } ResetTrackComboBox( g_CurrCdrom); } /*****************************Private*Routine******************************\ * ComputeAndUseShufflePlayLists * * This routine computes shuffled play lists for each drive, and sets * the current PLAYLIST for erach drive to the newly computed shuffled * PLAYLIST. The old PLAYLIST for each drive is saved in SAVELIST. * * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void ComputeAndUseShufflePlayLists( void ) { int i; for ( i = 0; i < g_NumCdDevices; i++ ) { ComputeSingleShufflePlayList( i ); } } /*****************************Private*Routine******************************\ * ComputeSingleShufflePlayList * * This routine computes shuffled play lists for drive i, and sets * the current PLAYLIST for it the newly computed shuffled * PLAYLIST. The old PLAYLIST is saved in SAVELIST. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void ComputeSingleShufflePlayList( int i ) { int j, index, numnodes; PTRACK_PLAY temp, temp1, duplist, prev, OldPlayList; /* ** First, delete the existing playlist */ OldPlayList = PLAYLIST(i); PLAYLIST(i) = NULL; /* ** Now, go through each drive and create a shuffled play list ** First step is to duplicate the old play list, then we will ** randomly pick off nodes and put them on the shuffle play list. */ duplist = prev = NULL; numnodes = 0; for( temp = SAVELIST(i); temp != NULL; temp = temp->nextplay ) { temp1 = AllocMemory( sizeof(TRACK_PLAY) ); *temp1 = *temp; temp1->nextplay = NULL; if (duplist) { temp1->prevplay = prev; prev->nextplay = temp1; prev = temp1; } else { duplist = temp1; temp1->prevplay = NULL; prev = temp1; } numnodes++; } /* ** Now, randomly pick off nodes */ prev = NULL; for( j = 0; j < numnodes; j++ ) { index = rand() % (numnodes - j + 1); temp = duplist; while( --index>0 ) { temp = temp->nextplay; } /* ** Got the node to transfer to playlist (temp), ** so we need to detach it from duplist so we ** can tack it onto the end of the playlist. */ if ( temp != NULL ) { /* ** Detach temp from playlist. */ if ( temp == duplist ) { duplist = temp->nextplay; } if ( temp->nextplay ) { temp->nextplay->prevplay = temp->prevplay; } if ( temp->prevplay ) { temp->prevplay->nextplay = temp->nextplay; } /* ** Now, tack it onto the end of the PLAYLIST */ if ( PLAYLIST(i) ) { prev->nextplay = temp; temp->prevplay = prev; temp->nextplay = NULL; prev = temp; } else { PLAYLIST(i) = temp; temp->prevplay = NULL; prev = temp; temp->nextplay = NULL; } } } /* ** we need to reset the CURRTRACK pointer so ** that it points to a node in PLAYLIST instead of SAVELIST */ if ( (g_Devices[i]->State & CD_PLAYING) && (CURRTRACK(i) != NULL) ) { index = CURRTRACK(i)->TocIndex; for( temp = PLAYLIST(i); temp->TocIndex!=index; temp=temp->nextplay ); CURRTRACK(i) = temp; } else { CURRTRACK(i) = PLAYLIST(i); if ( PLAYLIST(i) != NULL ) { CDTIME(i).TrackTotalMin = PLAYLIST(i)->min; CDTIME(i).TrackTotalSec = PLAYLIST(i)->sec; CDTIME(i).TrackRemMin = PLAYLIST(i)->min; CDTIME(i).TrackRemSec = PLAYLIST(i)->sec; } } /* ** if this is the current drive, we need to redo the tracks in ** the track list combobox. */ if ( i == g_CurrCdrom ) { ResetTrackComboBox( i ); } /* ** Finally, free up the memory from the old playlist. */ temp = OldPlayList; while ( temp != NULL ) { temp1 = temp->nextplay; LocalFree( (HLOCAL)temp ); temp = temp1; } } /*****************************Private*Routine******************************\ * RestorePlayListsFromShuffleLists * * This routine restores the PLAYLIST for each drive to it's "pre-shuffled" * state. This should be stored in SAVELIST. Once the restoration is done, * un-needed node are released. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void RestorePlayListsFromShuffleLists( void ) { int i,index; PTRACK_PLAY temp; for ( i = 0; i < g_NumCdDevices; i++ ) { if ( SAVELIST(i) ) { if ( CURRTRACK(i) != NULL ) { index = CURRTRACK(i)->TocIndex; } else { index = -1; } ErasePlayList(i); PLAYLIST(i) = CopyPlayList( SAVELIST(i) ); /* ** Reset CURRTRACK pointer */ if ( (g_Devices[i]->State & CD_PLAYING) && (index != -1) ) { for( temp = PLAYLIST(i); temp->TocIndex != index; temp=temp->nextplay ); CURRTRACK(i) = temp; } else { CURRTRACK(i) = PLAYLIST(i); if ( PLAYLIST(i) != NULL ) { CDTIME(i).TrackRemMin = PLAYLIST(i)->min; CDTIME(i).TrackRemSec = PLAYLIST(i)->sec; CDTIME(i).TrackTotalMin = PLAYLIST(i)->min; CDTIME(i).TrackTotalSec = PLAYLIST(i)->sec; } } } if ( i == g_CurrCdrom ) { ResetTrackComboBox( i ); } } } /*****************************Private*Routine******************************\ * FigureTrackTime * * This routine computes the length of a given track, in terms * of minutes and seconds. * * cdrom - supplies an index into the global structure gDevices * * index - supplies an index to the track which should have its * length computed. This is an index into the * gDevices[cdrom]->CdInfo.Tracks[...] structure * * min - supplies a pointer to an INT which will hold the minute * portion of the track length. * * sec - supplies a pointer to an INT which will hold the seconds * portion of the track length. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void FigureTrackTime( int cdrom, int index, int * min, int * sec ) { DWORD start, end, diff; start = ((TRACK_M(cdrom,index) * FRAMES_PER_MINUTE) + (TRACK_S(cdrom,index) * FRAMES_PER_SECOND) + TRACK_F(cdrom,index)); end = ((TRACK_M(cdrom,index+1) * FRAMES_PER_MINUTE) + (TRACK_S(cdrom,index+1) * FRAMES_PER_SECOND) + TRACK_F(cdrom,index+1)); diff = end - start; (*min) = (diff / FRAMES_PER_MINUTE); (*sec) = (diff % FRAMES_PER_MINUTE) / FRAMES_PER_SECOND; } /*****************************Private*Routine******************************\ * TimeAdjustInitialize * * Initializes the time, track, and title fields of a given * disc. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void TimeAdjustInitialize( int cdrom ) { int m, s, mtemp, stemp, ts, tm; PTRACK_PLAY tr; /* ** Is there even a cd loaded? */ if (g_Devices[cdrom]->State & (CD_BEING_SCANNED | CD_IN_USE | CD_NO_CD | CD_DATA_CD_LOADED)) { /* ** Fake some information */ g_Devices[cdrom]->CdInfo.NumTracks = 0; g_Devices[cdrom]->toc.FirstTrack = 0; g_Devices[cdrom]->CdInfo.Id = 0; if (g_Devices[cdrom]->State & CD_IN_USE) { _tcscpy( (LPTSTR)TITLE(cdrom), IdStr(STR_WAITING) ); _tcscpy( (LPTSTR)ARTIST(cdrom), IdStr(STR_DISC_INUSE) ); } else if (g_Devices[cdrom]->State & CD_BEING_SCANNED) { _tcscpy( (LPTSTR)TITLE(cdrom), IdStr(STR_WAITING) ); _tcscpy( (LPTSTR)ARTIST(cdrom), IdStr(STR_BEING_SCANNED) ); } else { _tcscpy( (LPTSTR)TITLE(cdrom), IdStr(STR_INSERT_DISC) ); _tcscpy( (LPTSTR)ARTIST(cdrom), IdStr(STR_DATA_NO_DISC) ); } /* ** Kill off play list */ ErasePlayList( cdrom ); EraseSaveList( cdrom ); EraseTrackList( cdrom ); tr = NULL; } else { /* ** Find track to use as first track */ tr = FindFirstTrack( cdrom ); } /* ** Set current position information */ CURRTRACK(cdrom) = tr; CDTIME(cdrom).TrackCurMin = 0; CDTIME(cdrom).TrackCurSec = 0; /* ** Compute PLAY length */ mtemp = stemp = m = s = ts = tm =0; for( tr = PLAYLIST(cdrom); tr != NULL; tr = tr->nextplay ) { FigureTrackTime( cdrom, tr->TocIndex, &mtemp, &stemp ); m+=mtemp; s+=stemp; tr->min = mtemp; tr->sec = stemp; } /* ** to be safe, recalculate the SAVE list each time as well. */ for( tr = SAVELIST(cdrom); tr != NULL; tr = tr->nextplay ) { FigureTrackTime( cdrom, tr->TocIndex, &mtemp, &stemp ); tr->min = mtemp; tr->sec = stemp; } m += (s / 60); s = (s % 60); CDTIME(cdrom).TotalMin = m; CDTIME(cdrom).TotalSec = s; CDTIME(cdrom).RemMin = m; CDTIME(cdrom).RemSec = s; /* ** Fill in track length and information */ if ( CURRTRACK(cdrom) != NULL ) { CDTIME(cdrom).TrackTotalMin = CDTIME(cdrom).TrackRemMin = CURRTRACK(cdrom)->min; CDTIME(cdrom).TrackTotalSec = CDTIME(cdrom).TrackRemSec = CURRTRACK(cdrom)->sec; } else { CDTIME(cdrom).TrackTotalMin = CDTIME(cdrom).TrackRemMin = 0; CDTIME(cdrom).TrackTotalSec = CDTIME(cdrom).TrackRemSec = 0; } /* ** Fill in track list combo box */ if ( cdrom == g_CurrCdrom ) { ResetTrackComboBox( cdrom ); /* ** Update display if this is the disc currently ** being displayed. */ UpdateDisplay( DISPLAY_UPD_LED | DISPLAY_UPD_DISC_TIME | DISPLAY_UPD_TRACK_TIME | DISPLAY_UPD_TITLE_NAME | DISPLAY_UPD_TRACK_NAME ); } } /*****************************Private*Routine******************************\ * TimeAdjustIncSecond * * Adds one second onto current position ("time") of disc * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void TimeAdjustIncSecond( int cdrom ) { PTRACK_PLAY tr; /* ** If there is no current track just return */ if ( CURRTRACK(g_CurrCdrom) == NULL ) { return; } /* ** Update current track time */ CDTIME(cdrom).TrackCurSec++; if ( CDTIME(cdrom).TrackCurSec > 59 ) { CDTIME(cdrom).TrackCurMin++; CDTIME(cdrom).TrackCurSec = 0; } /* ** Now, check to see if we skipped any track boundaries */ if ( ((CDTIME(cdrom).TrackCurMin >= CDTIME(cdrom).TrackTotalMin) && (CDTIME(cdrom).TrackCurSec >= CDTIME(cdrom).TrackTotalSec)) || ((g_fIntroPlay) && ((CDTIME(cdrom).TrackCurMin > 0) || (CDTIME(cdrom).TrackCurSec > g_IntroPlayLength)) ) ) { /* ** We did, so skip to next track */ /* ** Should Fix for new FindNextTrack */ tr = FindNextTrack( g_fContinuous ); if ( tr == NULL ) { /* ** Hit end of playlist, so stay at end of current ** track. */ if (!g_fIntroPlay) { CDTIME(cdrom).TrackCurMin = CDTIME(cdrom).TrackTotalMin; CDTIME(cdrom).TrackCurSec = CDTIME(cdrom).TrackTotalSec; } else { CDTIME(cdrom).TrackCurMin = 0; CDTIME(cdrom).TrackCurSec = g_IntroPlayLength; } return; } if ( g_CurrCdrom != g_LastCdrom) { SwitchToCdrom(g_CurrCdrom, FALSE ); } TimeAdjustSkipToTrack( cdrom, tr ); } else { /* ** Update current track remaining time */ CDTIME(cdrom).TrackRemSec--; if ( CDTIME(cdrom).TrackRemSec < 0 ) { CDTIME(cdrom).TrackRemMin--; CDTIME(cdrom).TrackRemSec = 59; } /* ** Update total remaining time */ CDTIME(cdrom).RemSec--; if ( CDTIME(cdrom).RemSec < 0 ) { CDTIME(cdrom).RemMin--; CDTIME(cdrom).RemSec = 59; } } /* ** Update Display */ UpdateDisplay( DISPLAY_UPD_LED ); } /*****************************Private*Routine******************************\ * TimeAdjustDecSecond * * Subtracts one second from current position ("time") of disc * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void TimeAdjustDecSecond( int cdrom ) { int min,sec; PTRACK_PLAY prev,tr; /* ** If there is no current track, just return */ if ( CURRTRACK(g_CurrCdrom) == NULL ) { return; } /* ** Update current track */ CDTIME(cdrom).TrackCurSec--; if ( CDTIME(cdrom).TrackCurSec < 0 ) { CDTIME(cdrom).TrackCurMin--; CDTIME(cdrom).TrackCurSec = 59; } /* ** Update current track remaining */ CDTIME(cdrom).TrackRemSec++; if ( CDTIME(cdrom).TrackRemSec > 59 ) { CDTIME(cdrom).TrackRemMin++; CDTIME(cdrom).TrackRemSec = 0; } /* ** Update total remaining time */ CDTIME(cdrom).RemSec++; if ( CDTIME(cdrom).RemSec > 59 ) { CDTIME(cdrom).RemMin++; CDTIME(cdrom).RemSec = 0; } /* ** Now, check to see if we skipped any boundaries we shouldn't have! */ if ( CDTIME(cdrom).TrackCurMin < 0 ) { /* ** We went "off" the front end of the track, ** so we need to see what to do now. Options ** are: ** ** (1) Go to end of track before us. ** (2) If intro play, go to 0:10 of ** track before us. ** (3) If not in continuous play, and ** this is the first track, then ** just sit at 0:00 */ prev = FindPrevTrack( cdrom, g_fContinuous ); if ( prev == CURRTRACK(cdrom) ) { /* ** We are on the first track, and not in ** continuous mode, so just go to 0:00 */ CDTIME(cdrom).TrackCurSec = 0; CDTIME(cdrom).TrackCurMin = 0; CDTIME(cdrom).TrackRemMin = CDTIME(cdrom).TrackTotalMin; CDTIME(cdrom).TrackRemSec = CDTIME(cdrom).TrackTotalSec; min = sec = 0; for( tr = PLAYLIST( cdrom ); tr != NULL; tr = tr->nextplay ) { min += tr->min; sec += tr->sec; } min += (sec / 60); sec = (sec % 60); CDTIME(cdrom).RemMin = min; CDTIME(cdrom).RemSec = sec; UpdateDisplay( DISPLAY_UPD_LED ); } else { /* ** Valid previous track */ if ( !g_fIntroPlay ) { /* ** We need to place the current play position ** at the end of the previous track. */ CDTIME(cdrom).TrackCurMin = CDTIME(cdrom).TrackTotalMin = prev->min; CDTIME(cdrom).TrackCurSec = CDTIME(cdrom).TrackTotalSec = prev->sec; CDTIME(cdrom).TrackRemMin = CDTIME(cdrom).TrackRemSec = 0; min = sec = 0; for( tr = prev->nextplay; tr != NULL; tr = tr->nextplay ) { min += tr->min; sec += tr->sec; } min += (sec / 60); sec = (sec % 60); CDTIME(cdrom).RemMin = min; CDTIME(cdrom).RemSec = sec; } else { /* ** Intro play -- instead of end of track, ** jump to 00:10... */ CDTIME(cdrom).TrackCurMin = 0; CDTIME(cdrom).TrackCurSec = min( g_IntroPlayLength, prev->sec ); CDTIME(cdrom).TrackTotalMin = prev->min; CDTIME(cdrom).TrackTotalSec = prev->sec; CDTIME(cdrom).TrackRemMin = CDTIME(cdrom).TrackTotalMin; CDTIME(cdrom).TrackRemSec = CDTIME(cdrom).TrackTotalSec - min( g_IntroPlayLength, prev->sec ); if ( CDTIME(cdrom).TrackRemSec < 0 ) { CDTIME(cdrom).TrackRemSec += 60; CDTIME(cdrom).TrackRemMin--; } min = sec = 0; for( tr = prev; tr != NULL; tr = tr->nextplay ) { min += tr->min; sec += tr->sec; } sec -= min( g_IntroPlayLength, prev->sec ); if ( sec < 0 ) { sec+=60; min--; } min += (sec / 60); sec = (sec % 60); CDTIME(cdrom).RemMin = min; CDTIME(cdrom).RemSec = sec; } CURRTRACK(cdrom) = prev; UpdateDisplay( DISPLAY_UPD_LED | DISPLAY_UPD_TRACK_NAME | DISPLAY_UPD_TRACK_TIME ); } } else { UpdateDisplay( DISPLAY_UPD_LED ); } } /*****************************Private*Routine******************************\ * InitializeNewTrackTime * * Updates track/time information for gDevices array. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void InitializeNewTrackTime( int cdrom, PTRACK_PLAY tr, BOOL fUpdateDisplay ) { int min,sec; /* ** Update time information in gDevices structure */ CDTIME(cdrom).CurrTrack = tr; CDTIME(cdrom).TrackCurMin = 0; CDTIME(cdrom).TrackCurSec = 0; if (tr == NULL) { CDTIME(cdrom).TrackTotalMin = 0; CDTIME(cdrom).TrackTotalSec = 0; } else { CDTIME(cdrom).TrackTotalMin = CDTIME(cdrom).TrackRemMin = tr->min; CDTIME(cdrom).TrackTotalSec = CDTIME(cdrom).TrackRemSec = tr->sec; } min = sec = 0; for( tr = PLAYLIST(cdrom); tr!=NULL; tr = tr->nextplay ) { min += tr->min; sec += tr->sec; } min += (sec / 60); sec = (sec % 60); CDTIME(cdrom).RemMin = min; CDTIME(cdrom).RemSec = sec; /* ** Update LED box */ if (fUpdateDisplay) { UpdateDisplay( DISPLAY_UPD_LED | DISPLAY_UPD_TRACK_NAME | DISPLAY_UPD_TRACK_TIME ); } } /*****************************Private*Routine******************************\ * TimeAdjustSkipToTrack * * Updates time/track information for gDevices array and then * issues skip to track commands to cdrom device. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void TimeAdjustSkipToTrack( int cdrom, PTRACK_PLAY tr ) { /* ** Update time information in gDevices structure */ InitializeNewTrackTime( cdrom, tr, TRUE ); /* ** Actually seek to the track, and play it if appropriate */ if ((g_Devices[cdrom]->State & CD_PLAYING) || (g_Devices[cdrom]->State & CD_PAUSED)) { PlayCurrTrack( cdrom ); if (g_Devices[cdrom]->State & CD_PAUSED) { PauseTheCdromDrive( cdrom ); } } else if (tr) { SeekToTrackAndHold( cdrom, tr->TocIndex ); } } /*****************************Private*Routine******************************\ * SyncDisplay * * Queries the cdrom device for its current position, and then * updates the display accordingly. Also, detects when a track has * finished playing, or when intro segment is over, and skips to the * next track. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void SyncDisplay( void ) { int m,s; PTRACK_PLAY next; CURRPOS cp; PCURRPOS pCurr = &cp; /* ** If there isn't a disc in the drive, ignore this ** request */ if ( (g_Devices[g_CurrCdrom]->State & CD_NO_CD) || (g_Devices[g_CurrCdrom]->State & CD_DATA_CD_LOADED) ) { return; } /* ** Query cdrom device for current position */ if ( !GetCurrPos( g_CurrCdrom, pCurr ) ) { /* ** If there was an error, it will already have been ** reported in CheckStatus of cdapi.c...so, we don't need ** to tell anything more here. When an error occurs, the ** fields of the pCurr structure are zeroed, so we don't ** need to clean those up either */ return; } /* ** Has the current play selection finished playing? */ #ifdef USE_IOCTLS if ((pCurr->AudioStatus == AUDIO_STATUS_PLAY_COMPLETE) && ( !(g_State & CD_SEEKING) )) { #else if ((pCurr->AudioStatus == MCI_MODE_STOP) && ( !(g_State & CD_SEEKING) )) { #endif Play_Complete: /* ** Yep, so skip to the next track. */ next = FindNextTrack( g_fContinuous ); if ( next == NULL ) { /* ** There are no more tracks to play, so ** fake a press on the "stop" button. But, ** we want to set gCurrCdrom back to the "playing" ** drive 'cause it may have changed in our call ** to FindNextTrack. */ g_CurrCdrom = g_LastCdrom; SendMessage( g_hwndApp, WM_COMMAND, IDM_PLAYBAR_STOP, 0L ); } else { if ( g_CurrCdrom != g_LastCdrom ) { SwitchToCdrom( g_CurrCdrom, FALSE ); /* ** We use to start the disc play by sending the play command. ** SendMessage( g_hwndApp, WM_COMMAND, IDM_PLAYBAR_PLAY, 0L ); ** However, all we realy need to put the drives state into ** playing and let TimeAdjustSkipToTrack take care of starting ** playing. If we don't do this when the app is in multi-disc ** random play mode, we get a fraction of a second of the ** first track in the playlist played before we seek to the ** the correct track and start playing it. This sounds really ** bad. */ g_State &= ~CD_STOPPED; g_State |= CD_PLAYING; } TimeAdjustSkipToTrack( g_CurrCdrom, next ); } return; } /* ** Check to see if we need to update the display */ if ( (pCurr->Track < 100) && ( pCurr->Track > (CURRTRACK(g_CurrCdrom)->TocIndex + FIRSTTRACK(g_CurrCdrom)) )) { /* ** We got to the next track in a multi-track ** play, so mark per track information for ** new track */ if ((CURRTRACK(g_CurrCdrom)->nextplay != NULL) && ((CURRTRACK(g_CurrCdrom)->TocIndex + 1) == CURRTRACK(g_CurrCdrom)->nextplay->TocIndex)) { next = FindNextTrack( g_fContinuous ); if ( next == NULL ) { /* ** There are no more tracks to play, so ** fake a press on the "stop" button. But, ** we want to set gCurrCdrom back to the "playing" ** drive 'cause it may have changed in our call ** to FindNextTrack. */ g_CurrCdrom = g_LastCdrom; SendMessage( g_hwndControls[INDEX(IDM_PLAYBAR_STOP)], WM_LBUTTONDOWN, 0, 0L ); SendMessage( g_hwndControls[INDEX(IDM_PLAYBAR_STOP)], WM_LBUTTONUP, 0, 0L ); } else { if ( g_CurrCdrom != g_LastCdrom ) { SwitchToCdrom( g_CurrCdrom, FALSE ); SendMessage( g_hwndControls[INDEX(IDM_PLAYBAR_PLAY)], WM_LBUTTONDOWN, 0, 0L ); SendMessage( g_hwndControls[INDEX(IDM_PLAYBAR_PLAY)], WM_LBUTTONUP, 0, 0L ); } InitializeNewTrackTime( g_CurrCdrom, next, FALSE ); UpdateDisplay( DISPLAY_UPD_TRACK_NAME | DISPLAY_UPD_TRACK_TIME ); } } else { /* ** If we get here it is probably the result of starting ** CD Player whislt the current disc was still playing. ** We look for the currently playing track in the current ** playlist. */ next = FindFirstTrack(g_CurrCdrom); while ( (next != NULL) && (pCurr->Track != (next->TocIndex + 1)) ) { #if DBG dprintf("trying track %d", (next->TocIndex + 1)); #endif next = next->nextplay; } /* ** If next is NULL it means that we are playing a track that ** is currently not on the users playlist. So, we put up a ** message box informing the user of this fact and that we are ** going to temporarily add the track to the current playlist ** as the first track. Otherwise, we found the track in the ** playlist so just update the track time for this track. */ if (next == NULL) { // Removed this line of code, so that tracks don't get //accidentally added. After a track is played, the STOP state is sensed, //and this function (see above) advances the track. However, under a rare //(about 1-5% of time) circumstance, this thread will sense that the cd's //time value means it's on the next track, and the cd is still in play mode. //This code used to interpret that as a new play command, and added the track //to the playlist. Hence, I commented out the following line. If the actual //case where a new play command is received, the new track is added to the //playlist in the HandlePassedCommandLine() call. //AddTemporaryTrackToPlayList(pCurr); } else { InitializeNewTrackTime( g_CurrCdrom, next, TRUE ); } } return; } if ( pCurr->Track < (CURRTRACK(g_CurrCdrom)->TocIndex + FIRSTTRACK(g_CurrCdrom)) ) return; if ( (pCurr->Index != 0) && (pCurr->m <= CDTIME(g_CurrCdrom).TrackCurMin) && (pCurr->s <= CDTIME(g_CurrCdrom).TrackCurSec) ) return; /* ** Set track elapsed time */ CDTIME(g_CurrCdrom).TrackCurMin = pCurr->m; CDTIME(g_CurrCdrom).TrackCurSec = pCurr->s; /* ** Set track remaining time */ m = pCurr->m; if ( (pCurr->s) <= CDTIME(g_CurrCdrom).TrackTotalSec ) { s = CDTIME(g_CurrCdrom).TrackTotalSec - pCurr->s; } else { s = 60 - (pCurr->s - CDTIME(g_CurrCdrom).TrackTotalSec); m++; } CDTIME(g_CurrCdrom).TrackRemMin = CDTIME(g_CurrCdrom).TrackTotalMin - m; CDTIME(g_CurrCdrom).TrackRemSec = s; /* ** Set disc remaining time ** ** Should Fix -- for now, just decrement by 1 second */ CDTIME(g_CurrCdrom).RemSec--; if (CDTIME(g_CurrCdrom).RemSec < 0) { CDTIME(g_CurrCdrom).RemSec = 59; CDTIME(g_CurrCdrom).RemMin--; } /* ** Update LED box */ if ( (pCurr->Index != 0) || ((pCurr->m == 0) && (pCurr->s == 0)) ) { UpdateDisplay( DISPLAY_UPD_LED ); } else { UpdateDisplay( DISPLAY_UPD_LED | DISPLAY_UPD_LEADOUT_TIME ); } /* ** Check to see if we are intro play and have played ** intro segment...if so, skip to next track */ if ( ((pCurr->s >= (g_IntroPlayLength + 1)) || (pCurr->m > 0)) && g_fIntroPlay ) { goto Play_Complete; } } /*****************************Private*Routine******************************\ * ValidatePosition * * Checks the current position on the CD, then verifies that the * relative offset in the track + the beginning of the track's * position is the same as the absolute position on the CD. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ void ValidatePosition( int cdrom ) { int Mult, Frames; CURRPOS cp; PCURRPOS pCurr = &cp; LPTSTR s1,s2; if (!GetCurrPos( cdrom, pCurr )) /* ** If there was an error, it will already have been ** reported in CheckStatus of cdapi.c...so, we don't need ** to tell anything more here. When an error occurs, the ** fields of the pCurr structure are zeroed, so we don't ** need to clean those up either */ return; /* ** Make sure the position returned is consistent with ** what we know about the CD. By comparing the relative time ** on this track to the absolute time on the CD, we should be ** able to make sure we're still on the right disc. This is ** a failsafe for when polling fails to notice an ejected ** disc. */ if ((cp.Track > 0)&&(cp.Track < 101)) { Frames = cp.ab_m * 60 * 75; Frames += cp.ab_s * 75; Frames += cp.ab_f; Frames -= TRACK_M(cdrom,cp.Track-1) * 60 * 75; Frames -= TRACK_S(cdrom,cp.Track-1) * 75; Frames -= TRACK_F(cdrom,cp.Track-1); if (pCurr->Index) { Mult = 1; } else { Mult = -1; } Frames -= Mult*cp.m * 60 * 75; Frames -= Mult*cp.s * 75; Frames -= Mult*cp.f; if (g_Devices[cdrom]->CdInfo.iFrameOffset == NEW_FRAMEOFFSET) { g_Devices[cdrom]->CdInfo.iFrameOffset = Frames; } if ((ABS(Frames - g_Devices[ cdrom ]->CdInfo.iFrameOffset) > 4) && (ABS(Frames) > 4)) { HWND hwndStop; hwndStop = g_hwndControls[INDEX(IDM_PLAYBAR_STOP)]; s1 = AllocMemory( _tcslen(IdStr(STR_BAD_DISC)) + 1 ); _tcscpy( s1, IdStr(STR_BAD_DISC) ); s2 = AllocMemory( _tcslen(IdStr(STR_CDPLAYER)) + 1); _tcscpy(s2,IdStr(STR_CDPLAYER)); MessageBox( g_hwndApp, s1, s2, MB_APPLMODAL|MB_ICONSTOP|MB_OK ); SendMessage( hwndStop,WM_LBUTTONDOWN, 1,0L ); SendMessage( hwndStop,WM_LBUTTONUP, 1, 0L ); RescanDevice(g_hwndApp, cdrom ); LocalFree( (HLOCAL)s1 ); LocalFree( (HLOCAL)s2 ); return; } } } /*****************************Private*Routine******************************\ * ResetTrackComboBox * * This routine deletes and then resets the track name combobox based * on the contents of the PLAYLIST for the specified cdrom drive. * * History: * 18-11-93 - StephenE - Created * \**************************************************************************/ VOID ResetTrackComboBox( int cdrom ) { int j,index; PTRACK_PLAY temp; HWND hwnd; hwnd = g_hwndControls[INDEX(IDC_TRACK_LIST)]; SetWindowRedraw( hwnd, FALSE ); ComboBox_ResetContent( hwnd ); /* ** Add new playlist, and select correct entry for current track */ j = index = 0; for( temp = PLAYLIST(cdrom); temp != NULL; temp = temp->nextplay ) { ComboBox_InsertString( hwnd, -1, temp->TocIndex ); if ( temp == CURRTRACK(cdrom) ) { index = j; } j++; } ComboBox_SetCurSel( hwnd, index ); SetWindowRedraw( hwnd, TRUE ); RedrawWindow( hwnd, NULL, NULL, RDW_INVALIDATE ); UpdateWindow( hwnd ); UpdateDisplay( DISPLAY_UPD_LED | DISPLAY_UPD_TRACK_TIME ); } /******************************Public*Routine******************************\ * PlayListMatchesAvailList * * Compares the current play list with the default play list to if they match. * * History: * dd-mm-94 - StephenE - Created * \**************************************************************************/ BOOL PlayListMatchesAvailList( void ) { PTRACK_PLAY pl = SAVELIST(g_CurrCdrom); int i = 0; while (pl && i < NUMTRACKS(g_CurrCdrom)) { if ( pl->TocIndex != i) { return FALSE; } pl = pl->nextplay; i++; } return pl == NULL && i == NUMTRACKS(g_CurrCdrom); } /*****************************Private*Routine******************************\ * AddTemporaryTrackToPlayList * * This functions adds the currently playing track to the playlist. * pCurr contains the toc index of the track that is required to be added. * * History: * dd-mm-94 - StephenE - Created * \**************************************************************************/ void AddTemporaryTrackToPlayList( PCURRPOS pCurr ) { // LPTSTR lpstrTitle; // LPTSTR lpstrText; PTRACK_PLAY tr; int m, s; /* ** Add track to the current playlist. */ tr = AllocMemory( sizeof(TRACK_PLAY) ); tr->TocIndex = pCurr->Track - 1; FigureTrackTime(g_CurrCdrom, tr->TocIndex, &tr->min, &tr->sec); tr->nextplay = PLAYLIST(g_CurrCdrom); tr->prevplay = NULL; PLAYLIST(g_CurrCdrom)->prevplay = tr; PLAYLIST(g_CurrCdrom) = tr; /* ** Update the display. */ InitializeNewTrackTime( g_CurrCdrom, tr, TRUE ); ResetTrackComboBox( g_CurrCdrom ); m = CDTIME(g_CurrCdrom).TotalMin + tr->min; s = CDTIME(g_CurrCdrom).TotalSec + tr->sec; m += (s / 60); s = (s % 60); CDTIME(g_CurrCdrom).TotalMin = m; CDTIME(g_CurrCdrom).TotalSec = s; UpdateDisplay(DISPLAY_UPD_DISC_TIME); /* ** Now modify the current saved playlist. We do this so that transitions ** from/to random mode work correctly. */ tr = AllocMemory( sizeof(TRACK_PLAY) ); tr->TocIndex = pCurr->Track - 1; FigureTrackTime(g_CurrCdrom, tr->TocIndex, &tr->min, &tr->sec); tr->nextplay = SAVELIST(g_CurrCdrom); tr->prevplay = NULL; SAVELIST(g_CurrCdrom)->prevplay = tr; SAVELIST(g_CurrCdrom) = tr; #if 0 /* ** Now, tell the user what we have just done. Note that we disable the ** the Heart beat timer so that we don't renenter ourselves. */ lpstrTitle = AllocMemory( STR_MAX_STRING_LEN * sizeof(TCHAR) ); lpstrText = AllocMemory( STR_MAX_STRING_LEN * sizeof(TCHAR) ); _tcscpy( lpstrText, IdStr(STR_NOT_IN_PLAYLIST) ); _tcscpy( lpstrTitle, IdStr(STR_CDPLAYER) ); KillTimer( g_hwndApp, HEARTBEAT_TIMER_ID ); MessageBox( NULL, lpstrText, lpstrTitle, MB_APPLMODAL | MB_ICONINFORMATION | MB_OK ); SetTimer( g_hwndApp, HEARTBEAT_TIMER_ID, HEARTBEAT_TIMER_RATE, HeartBeatTimerProc ); LocalFree( (HLOCAL)lpstrText ); LocalFree( (HLOCAL)lpstrTitle ); #endif }