This is mCD_Controller.m in view mode; [Download] [Up]
#import "mCD_Controller.h"
#import "PrefsController.h"
#import "StandardInfo.h"
#import "CD_DBase.subproj/CD_DBase.h"
void fixCatNum(struct rsc_media_catnum_reply *);
@implementation mCD_Controller
/* implement a periodic update */
void Trigger(DPSTimedEntry teNum, double new, id target)
{
[target EventLoop];
}
- EventLoopInit
{
teNum = DPSAddTimedEntry(updatePeriod, (DPSTimedEntryProc)Trigger,
self, NX_BASETHRESHOLD);
return self;
}
- EventLoop
{
int devReady;
struct timeval timed_cd_Tval;
if ( (cd_fd != 0) && !do_timed_updates ) {
devReady = do_testunitready(cd_fd, &timed_cd_Tval, &tur_Ereply);
if ( !devReady ) {
/* Hmm, will this do what I want? */
[self loadCD:self];
}
}
if ( do_timed_updates ) [self updateCdStatus:self];
return self;
}
- appDidInit:sender
{
if ( 0 != geteuid() ) {
id nonRootErrorPanel;
nonRootErrorPanel = [NXApp loadNibSection:"NonRoot.nib" owner:NXApp];
[NXApp runModalFor:nonRootErrorPanel];
exit(0);
}
[standardInfo initForAppUsingNib:"mCD_Info.nib"];
[globPrefs initGlobalPreferences:self mainPanel:mainPanel ];
/* NOTE: in NS-3.2, it seems that setAltIncrementValue will work
* backwards, at least for vertical sliders. I still want
* to have it though, it's better backwards than not at all
*/
[leftVolumeSliderID setAltIncrementValue: 1.0];
[rightVolumeSliderID setAltIncrementValue: 1.0];
[pauseButtonID setShowsStateBy:NX_CONTENTS | NX_CHANGEBACKGROUND];
cd_fd = 0;
[loadButtonID setEnabled:YES];
[playButtonID setEnabled:NO];
[pauseButtonID setEnabled:NO];
[stopButtonID setEnabled:NO];
[unloadButtonID setEnabled:NO];
[curTrackID setStringValue: "--"];
[endTrackID setStringValue: "--"];
[trackPlayTimeID setStringValue: "-:--:--"];
[trackRemTimeID setStringValue: "-:--:--"];
[discPlayTimeID setStringValue: "-:--:--"];
[discRemTimeID setStringValue: "-:--:--"];
[trackInfoID selectAll:self];
[trackInfoID replaceSel: ""];
[mainPanel makeKeyAndOrderFront:self];
/* start up the periodic update of the cd status */
do_timed_updates = NO; /* initial testing */
updatePeriod = 1.0; /* every 1 second */
updatePeriod = 0.5; /* crazy times for testing purposes */
updatePeriod = 0.99; /* a little less crazy... */
[self EventLoopInit];
/* if the preferences are indeed set for a CD-ROM drive, then
* try to load a music CD, and if it's already in there go
* to display it. Note that this will not (yet) automatically
* prompt to load in a CD if there isn't one there.
*/
if ( [self openCdFd:FROM_CD_APP_DID_INIT] ) {
[self displayCdInfo];
}
return self;
}
- appWillTerminate:sender
{
/* the purpose of this method is to handle "Quit" processing */
BOOL wantUnload, abortQuit;
int devReady, alertResult;
if ( cd_fd == 0 ) return self; /* no device open, nothing to do */
/* assume an unload of the CD drive will be wanted. If the device
* is ready (ie, there's a disc in it), and if the disc is playing,
* then prompt the user to find out of it's really wanted. If the
* drive has no CD, or the CD isn't playing, then always do an
* unload to prevent later confusion.
*/
wantUnload = YES; /* assume an unload will be wanted */
abortQuit = NO; /* and the Quit won't be aborted */
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( !devReady ) {
/* there is a disc in the drive */
if (cd_curpos.rsc_audio_status == RSC_ASTAT_PLAYING ) {
alertResult = NXRunAlertPanel("mCD Quitting",
"There is a music CD still playing. Should it be unloaded during quit?",
"Yes", "No", "Cancel Quit", rawDevName);
switch ( alertResult ) {
case NX_ALERTALTERNATE:
wantUnload = NO;
break;
case NX_ALERTOTHER:
wantUnload = NO;
abortQuit = YES;
break;
}
}
}
if (wantUnload) [self unloadCD:self];
else {
/* user wants the music CD left in the drive, playing, but
* we should at least enable the media-removal button (on
* the drive) to work again
*/
do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */
}
if (abortQuit) return NULL;
return self; /* anything non-null, so app will terminate */
}
- (int)appPowerOffIn:(int)ms andSave:(int)aFlag
{
/* the purpose of this method is to handle logout processing,
* which is not handled by the appWillTerminate method. I
* don't really care about power-off processing, but this
* seems to be the only way to handle logout processing
*/
/* this method is described *very* briefly in the pre-3.0
* concepts documentation. While it works in NS-3.x (on
* motorola hardware, at least), I suspect NeXT is phasing
* it out. If this routine was doing any significant amount
* of work, it might need to send the Workspace manager a:
- (int)extendPowerOffBy:(int)requestedMs actual:(int *)actualMs;
* message to get more time to do the processing.
*/
/* clean up after ourselves as quickly as possible. this is
* basically a trimmed-down version of "unload" processing
*/
int err;
if ( cd_fd == 0 ) return 0; /* no device currently, ignore */
do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */
do_eject_1b(cd_fd, &cd_Tval, &cd_Ereply);
err = ioctl(cd_fd, DKIOCEJECT, NULL);
close( cd_fd );
cd_fd = 0;
return 0 ; /* not sure what this method needs to return... */
}
- updateCdStatus:sender
{
BOOL needUpdate, indexChanged;
int tempHour, tempMin, tempSec;
int lastCdIndex, lastAbsSecond;
char buff[80];
if ( cd_fd == 0 ) return self; /* no device yet, ignore */
needUpdate = indexChanged = NO;
do_readcurrentposition_42(cd_fd, &cd_curpos, &rcp_Ereply);
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PAUSED:
case RSC_ASTAT_PLAYING:
break;
case RSC_ASTAT_PLAYCOMPLETE:
if ( [globPrefs consoleDebugMsgs] )
printf("mCD debug: play complete\n");
[self stopCD:self];
break;
case RSC_ASTAT_PLAYABORTED:
if ( [globPrefs consoleDebugMsgs] )
printf("mCD debug: play aborted\n");
/* presumably not wanted: [self stopCD:self]; */
break;
case RSC_ASTAT_NONE:
break;
}
lastCdIndex = curCdIndex;
curCdIndex = (cd_curpos.track * 100) + cd_curpos.index;
if ( lastCdIndex != curCdIndex ) needUpdate = indexChanged = YES;
lastAbsSecond = curAbsSecond;
curAbsSecond = (cd_curpos.abs_hour * 3600) + (cd_curpos.abs_min * 60) +
cd_curpos.abs_sec;
if ( lastAbsSecond != curAbsSecond ) needUpdate = YES;
if ( needUpdate ) {
[mainPanel disableFlushWindow];
if ( cd_curpos.index < 2 ) {
sprintf (buff, "%d", cd_curpos.track);
}
else {
sprintf (buff, "%d.%d", cd_curpos.track, cd_curpos.index);
}
[curTrackID setStringValue: buff];
if ( (cd_curpos.index == 0) && (cd_curpos.rel_sec > 0) ) {
sprintf (buff, "- %02d:%02d",
cd_curpos.rel_min, cd_curpos.rel_sec);
}
else {
sprintf (buff, "%d:%02d:%02d", cd_curpos.rel_hour,
cd_curpos.rel_min, cd_curpos.rel_sec);
}
[trackPlayTimeID setStringValue: buff];
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PLAYING:
/* the CD is playing right now... */
if ( (indexChanged) &&
(cd_curpos.index + cd_curpos.rel_sec > 0) &&
[globPrefs consoleDebugMsgs] ) {
printf("mCD debug: index %d.%d started %d:%02d:%02d-%02d into CD",
cd_curpos.track, cd_curpos.index,
cd_curpos.abs_hour, cd_curpos.abs_min,
cd_curpos.abs_sec, cd_curpos.abs_frame);
if ((cd_curpos.rel_hour > 0) ||
(cd_curpos.rel_min > 0) || (cd_curpos.rel_sec > 0)) {
if ( cd_curpos.index != 0 ) {
printf(", %d:%02d:%02d into the song",
cd_curpos.rel_hour, cd_curpos.rel_min,
cd_curpos.rel_sec);
}
else {
printf(", -%02d:%02d before the song",
cd_curpos.rel_min, cd_curpos.rel_sec);
}
}
printf("\n");
}
tempSec = toc.info[cd_curpos.track].elapsedSec - (
(cd_curpos.rel_hour * 3600) +
(cd_curpos.rel_min * 60) +
cd_curpos.rel_sec );
tempMin = tempSec / 60;
tempSec = tempSec % 60;
tempHour = tempMin / 60;
tempMin = tempMin % 60;
sprintf (buff, "%d:%02d:%02d", tempHour, tempMin, tempSec);
[trackRemTimeID setStringValue: buff];
tempSec = toc.info[100].elapsedSec - (
(cd_curpos.abs_hour * 3600) +
(cd_curpos.abs_min * 60) +
cd_curpos.abs_sec );
tempMin = tempSec / 60;
tempSec = tempSec % 60;
tempHour = tempMin / 60;
tempMin = tempMin % 60;
sprintf (buff, "%d:%02d:%02d", tempHour, tempMin, tempSec);
[discRemTimeID setStringValue: buff];
break;
}
sprintf (buff, "%d:%02d:%02d", cd_curpos.abs_hour,
cd_curpos.abs_min, cd_curpos.abs_sec);
[discPlayTimeID setStringValue: buff];
[mainPanel reenableFlushWindow];
[mainPanel flushWindowIfNeeded];
}
return self;
}
- ejectCD:sender
{
int devReady;
char buff[80];
if ( cd_fd == 0 ) return self; /* no device yet, ignore */
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( devReady ) return self; /* device is not ready, ignore */
do_timed_updates = NO; /* initial testing */
/* zap a few display fields to reduce confusion */
[trackInfoID selectAll:self];
[trackInfoID replaceSel: ""];
[curTrackID setStringValue: "--"];
[endTrackID setIntValue: toc.lastCDtrack];
[trackPlayTimeID setStringValue: "-:--:--"];
[trackRemTimeID setStringValue: "-:--:--"];
[discPlayTimeID setStringValue: "-:--:--"];
if( toc.info[100].min >= 60 ) {
toc.info[100].hour++ ;
toc.info[100].min -= 60;
}
sprintf (buff, "%u:%02u:%02u", toc.info[100].hour,
toc.info[100].min, toc.info[100].sec);
[discRemTimeID setStringValue: buff];
do_preventremoval_1e(cd_fd, NO, &cd_Ereply); /* ie, allow removal */
do_eject_1b(cd_fd, &cd_Tval, &cd_Ereply);
return self;
}
- unloadCD:sender
{
int err;
if ( cd_fd == 0 ) return self; /* no device yet, ignore */
/* (note that we can do the unload processing even if there is
* no disc in the drive, so we don't testunitready here ) */
[self ejectCD:self];
/* the difference between ejecting and unloading is the
following commands: */
err = ioctl(cd_fd, DKIOCEJECT, NULL);
close( cd_fd );
cd_fd = 0;
[loadButtonID setEnabled:YES];
[playButtonID setEnabled:NO];
[pauseButtonID setEnabled:NO];
[stopButtonID setEnabled:NO];
[unloadButtonID setEnabled:NO];
return self;
}
- loadCD:sender
{
if ( [self openCdFd:FROM_LOAD_CD_REQUEST] ) {
[self displayCdInfo];
}
return self;
}
- displayCdInfo
{
int track;
BOOL isAudio; /* as opposed to computer-data track */
char buff[120];
NXStream *songInfoStream;
[self updateCdStatus:self];
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PAUSED:
case RSC_ASTAT_PLAYING:
break;
case RSC_ASTAT_PLAYCOMPLETE:
case RSC_ASTAT_PLAYABORTED:
case RSC_ASTAT_NONE:
do_spinup_1b(cd_fd, &cd_Tval, &cd_Ereply);
}
do_readtoc_43(cd_fd, &toc, &cd_Ereply);
[self fillTocTitles];
[curTrackID setIntValue: 0];
[endTrackID setIntValue: toc.lastCDtrack];
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PAUSED:
/* have to skip over this for now, due to the odd behavior
* of the CD-ROM drive that NeXT used to sell, which
* indicates it's paused if it's not actively playing and
* if a play operation didn't just complete. This part
* could be done on a drive-specific basis, once the code
* is better organized.
*/
/* do nothing but ensure pause button is right */
/* [pauseButtonID setIntValue:1]; */
break;
case RSC_ASTAT_PLAYING:
/* do nothing but ensure pause button is right */
[pauseButtonID setIntValue:0];
break;
case RSC_ASTAT_PLAYCOMPLETE:
case RSC_ASTAT_PLAYABORTED:
case RSC_ASTAT_NONE:
break;
}
[trackPlayTimeID setStringValue: "-:--:--"];
[trackRemTimeID setStringValue: "-:--:--"];
if( toc.info[100].min >= 60 ) {
toc.info[100].hour++ ;
toc.info[100].min -= 60;
}
sprintf (buff, "%u:%02u:%02u", toc.info[100].hour,
toc.info[100].min, toc.info[100].sec);
[discRemTimeID setStringValue: buff];
if ( ! toc.discTitle )
[cdTitleID setStringValue:"title of CD is not known"];
else {
tempTitle[0] = '\0';
if ( toc.discPerformer ) {
strcat(tempTitle, toc.discPerformer);
strcat(tempTitle, " - ");
}
strcat(tempTitle, toc.discTitle);
[cdTitleID setStringValue: tempTitle];
}
/* display the table of contents to the window, as rich-text */
songInfoStream = NXOpenMemory(NULL, 0, NX_READWRITE);
NXPrintf(songInfoStream,
"{\\rtf0\\ansi{\\fonttbl\\f0\\fmodern Ohlfs;");
NXPrintf(songInfoStream, "\\f1\\fnil Times-Roman;}\n");
NXPrintf(songInfoStream, "\\pard\\tx460\\tx1060\\tx1260");
NXPrintf(songInfoStream, "\\f0\\b0\\i0\\ulnone\\fs18");
NXPrintf(songInfoStream, "\\fi-1260\\li1260\\fc0\\cf0\n");
for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) {
isAudio= YES;
if ( toc.info[track].control & DATA_TRACK ) isAudio = NO;
NXPrintf(songInfoStream, "\\f0\\fs18%3d)\t", track);
if ( isAudio ) {
NXPrintf(songInfoStream, "%02d:%02d",
toc.info[track].elapsedSec / 60,
toc.info[track].elapsedSec % 60);
} else {
NXPrintf(songInfoStream, "==:==");
}
NXPrintf(songInfoStream, "\t-\t\\f1\\fs24 ");
if ( toc.info[track].trackTitle )
NXPrintf(songInfoStream, "%s", toc.info[track].trackTitle);
else if (isAudio)
NXPrintf(songInfoStream, "???");
else NXPrintf(songInfoStream, "[data track]");
if ( track != toc.lastCDtrack ) NXPrintf(songInfoStream, "\\\n");
}
NXPrintf(songInfoStream, "\n}\n");
NXSeek(songInfoStream, 0, NX_FROMSTART);
[trackInfoID selectAll:self];
[trackInfoID replaceSelWithRichText: songInfoStream];
NXCloseMemory(songInfoStream, NX_FREEBUFFER);
return self;
}
- pauseCD:sender
{
/* note that this method is called from both the pause
* button and a menu item. Due to the menu item, this
* has to directly set the state of the button (instead
* of just sending a message to "sender").
*/
do_readcurrentposition_42(cd_fd, &cd_curpos, &rcp_Ereply);
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PLAYING:
/* it is playing, switch it to pause */
do_pauseaudio_4b(cd_fd, 1, &cd_Ereply);
[pauseButtonID setIntValue:1];
break;
case RSC_ASTAT_PAUSED:
/* it is paused, switch it to playing */
do_pauseaudio_4b(cd_fd, 0, &cd_Ereply);
[pauseButtonID setIntValue:0];
break;
case RSC_ASTAT_PLAYCOMPLETE:
case RSC_ASTAT_PLAYABORTED:
case RSC_ASTAT_NONE:
/* do nothing but ensure pause button is right */
[pauseButtonID setIntValue:0];
break;
}
return self;
}
- playCD:sender
{
int sTrack = toc.firstAtrack, eTrack = toc.lastAtrack;
[self playTracks:sTrack to: eTrack];
return self;
}
- playTrackRange: sender
{
/* target for the play-range button in the
select track range panel */
int sTrack, eTrack;
sTrack = [newStartTrackID intValue];
eTrack = [newEndTrackID intValue];
[self playTracks:sTrack to: eTrack];
[trackRangePanel performClose:self];
return self;
}
- setLeftVolume:sender
{
[self setPlayVolumes:[sender intValue] :-1];
return self;
}
- setRightVolume:sender
{
[self setPlayVolumes:-1 :[sender intValue]];
return self;
}
- setPlayVolumes:(int) leftVol :(int) rightVol
{
int devReady;
if ( cd_fd == 0 ) return self; /* no device yet, ignore */
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( devReady ) return self; /* device is not ready, ignore */
if ( [globPrefs separateVolumes] || ((leftVol < 0) && (rightVol < 0))) {
/* need to know current volume settings */
do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply);
if (leftVol < 0) leftVol = cd_volumes.ch0_vol;
if (rightVol < 0) rightVol = cd_volumes.ch1_vol;
}
else {
if (leftVol < 0) {
cd_volumes.ch0_vol = leftVol = rightVol;
[leftVolumeSliderID setIntValue:leftVol];
}
if (rightVol < 0) {
cd_volumes.ch1_vol = rightVol = leftVol;
[rightVolumeSliderID setIntValue:rightVol];
}
}
do_modeselect_pc_E(cd_fd, leftVol, rightVol, &cd_Ereply);
return self;
}
/* the following used by PrefController */
- getVolumes:(int *)leftVolPtr :(int *)rightVolPtr
{
do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply);
*leftVolPtr = cd_volumes.ch0_vol;
*rightVolPtr = cd_volumes.ch1_vol;
return self;
}
- showSelectTrackRange:sender
{
[newStartTrackID setIntValue: toc.firstAtrack];
[newEndTrackID setIntValue: toc.lastAtrack];
[trackRangePanel makeKeyAndOrderFront:self];
[newStartTrackID selectText:self];
return self;
}
- goNextTrack:sender
{
int sTrack, eTrack;
if ( cd_curpos.track >= toc.lastAtrack ) {
return self;
}
sTrack = cd_curpos.track + 1;
eTrack = toc.lastAtrack; /* last audio track */
[self playTracks:sTrack to: eTrack];
return self;
}
- goPreviousTrack:sender
{
int sTrack, eTrack;
/* goes to the beginning of the current track if we're more than
a few seconds into it, otherwise the previous track */
sTrack = cd_curpos.track;
if ( (0 == cd_curpos.rel_hour) && (0 == cd_curpos.rel_min)
&& (5 > cd_curpos.rel_sec) ) {
sTrack = cd_curpos.track - 1;
}
if ( sTrack < toc.firstAtrack ) sTrack = toc.firstAtrack;
eTrack = toc.lastAtrack; /* last audio track */
[self playTracks:sTrack to: eTrack];
return self;
}
/* you might ask, why does this go thru all the trouble of
* finding starting and ending times and doing a playaudio_msf
* instead of just playing a track range?
*
* The answer is that I want flexibility such that the CD database
* can *change* the start and end times for a track, for those
* CD's which were manufactured with the wrong starting times.
*/
- playTracks:(int)startTrack to: (int) endTrack
{
struct pa_msf tst_start = {0, 0, 0};
struct pa_msf tst_end = {0, 0, 0};
int aeTrack;
int devReady;
if ( cd_fd == 0 ) return self; /* no device currently, ignore */
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( devReady ) return self; /* device is not ready, ignore */
/* calc the track after the end track*/
if ( endTrack >= toc.lastCDtrack ) aeTrack = 100;
else aeTrack = endTrack + 1;
/* note that do_playaudio_msf_47 has no hours variable... */
tst_start.min = (toc.info[startTrack].hour * 60)
+ toc.info[startTrack].min;
tst_start.sec = toc.info[startTrack].sec;
tst_start.frame = toc.info[startTrack].frame;
tst_end.min = (toc.info[aeTrack].hour * 60) + toc.info[aeTrack].min;
tst_end.sec = toc.info[aeTrack].sec;
tst_end.frame = toc.info[aeTrack].frame;
if ( tst_end.frame > 0 ) tst_end.frame--;
else {
tst_end.frame = 74;
if ( tst_end.sec > 0 ) tst_end.sec--;
else {
tst_end.sec = 59;
if ( tst_end.min > 0 ) tst_end.min--;
}
}
if ( [globPrefs consoleDebugMsgs] ) {
printf("mCD debug: start (%d) %02u:%02u-%02u",
startTrack,
tst_start.min, tst_start.sec, tst_start.frame);
printf(" end (%d) %02u:%02u-%02u\n",
endTrack,
tst_end.min, tst_end.sec, tst_end.frame);
}
do_playaudio_msf_47(cd_fd, tst_start, tst_end, &cd_Ereply);
/* make sure the volumes are set right (doing a playaudio command
* causes the CD to play at full volume, even if modesense shows
* that some other volume level has been set)
*/
do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply);
[leftVolumeSliderID setIntValue: cd_volumes.ch0_vol];
[rightVolumeSliderID setIntValue: cd_volumes.ch1_vol];
[self setPlayVolumes:cd_volumes.ch0_vol :cd_volumes.ch1_vol];
/* ensure pause button is set right */
[pauseButtonID setIntValue:0];
return self;
}
- stopCD:sender
{
int devReady;
char buff[40];
if ( cd_fd == 0 ) return self; /* no device yet, ignore */
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( devReady ) return self; /* device is not ready, ignore */
do_rezerounit_01(cd_fd, &cd_Ereply);
do_stopunit_1b(cd_fd, &cd_Tval, &cd_Ereply);
curCdIndex = -1;
curAbsSecond = -1;
/* probably should reset time-displaying fields differently */
[trackRemTimeID setStringValue: "-:--:--"];
if( toc.info[100].min >= 60 ) {
toc.info[100].hour++ ;
toc.info[100].min -= 60;
}
sprintf (buff, "%u:%02u:%02u", toc.info[100].hour,
toc.info[100].min, toc.info[100].sec);
[discRemTimeID setStringValue: buff];
return self;
}
- copyMcdEntry:sender
{
int track;
BOOL isAudio;
NXStream *mstream;
id pboard;
NXAtom ptypes[1];
/* note: probably should check that there really is an mCD entry
* to copy at this point in time...
*/
mstream = NXOpenMemory(NULL, 0, NX_READWRITE);
if( toc.info[100].min >= 60 ) {
toc.info[100].hour++ ;
toc.info[100].min -= 60;
}
NXPrintf(mstream, "%u:%02u:%02u\t", toc.info[100].hour,
toc.info[100].min, toc.info[100].sec);
if ( ! toc.discTitle )
NXPrintf(mstream, "**artist unknown**\t- ** title unknown **");
else {
if ( ! toc.discPerformer )
NXPrintf(mstream, "**artist unknown**");
else
NXPrintf(mstream, "%s", toc.discPerformer);
NXPrintf(mstream, "\t- %s", toc.discTitle);
}
if ( toc.discCatNum )
NXPrintf(mstream, "\tUPC=%s", toc.discCatNum);
NXPrintf(mstream, "\n");
/* include the table of contents */
for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) {
isAudio= YES;
if ( toc.info[track].control & DATA_TRACK ) isAudio = NO;
NXPrintf(mstream, "%4d)\t", track);
if ( isAudio ) {
NXPrintf(mstream, "%02d:%02d\t- ",
toc.info[track].elapsedSec / 60,
toc.info[track].elapsedSec % 60);
} else {
NXPrintf(mstream, "==:==\t- ");
}
if ( toc.info[track].trackTitle )
NXPrintf(mstream, "%s", toc.info[track].trackTitle);
else if (isAudio)
NXPrintf(mstream, "???");
else NXPrintf(mstream, "[data track]");
NXPrintf(mstream, "\n");
}
NXPrintf(mstream, "\n");
pboard = [Pasteboard new];
ptypes[0] = NXAsciiPboardType;
[pboard declareTypes:ptypes num:1 owner:self];
[pboard writeType:NXAsciiPboardType fromStream:mstream];
NXCloseMemory(mstream, NX_FREEBUFFER);
return self;
}
- copyMcdEntryAsObjC:sender
{
u_int key;
int track;
BOOL isAudio;
NXStream *mstream;
id pboard;
NXAtom ptypes[1];
/* note: probably should check that there really is an mCD entry
* to copy at this point in time...
*/
mstream = NXOpenMemory(NULL, 0, NX_READWRITE);
key = [cd_dbase indexKey:&toc];
NXPrintf(mstream, "#define %s %10u /* key parts = %d.%02u.%02d.%d %d */\n",
"0000_none_0000", key, toc.info[100].elapsedSec,
toc.info[100].frame, toc.lastCDtrack, toc.info[1].elapsedSec,
toc.info[toc.lastCDtrack].elapsedSec);
if( toc.info[100].min >= 60 ) {
toc.info[100].hour++ ;
toc.info[100].min -= 60;
}
NXPrintf(mstream, " if ( cdKey == %s ) { /* %u:%02u:%02u-%02u */\n",
"0000_none_0000", toc.info[100].hour,
toc.info[100].min, toc.info[100].sec, toc.info[100].frame);
if ( ! toc.discPerformer )
NXPrintf(mstream, "\ttocPtr->discPerformer = \"performer??\";\n");
else
NXPrintf(mstream, "\ttocPtr->discPerformer = \"%s\";\n", toc.discPerformer);
if ( ! toc.discTitle )
NXPrintf(mstream, "\ttocPtr->discTitle = \"title??\";\n");
else
NXPrintf(mstream, "\ttocPtr->discTitle = \"%s\";\n", toc.discTitle);
if ( toc.discCatNum )
NXPrintf(mstream, "\ttocPtr->discCatNum = \"%s\";\n", toc.discCatNum);
#ifdef ADD_SKIPSONGS
NXPrintf(mstream, "# if !defined(SKIPSONGS_\n");
#endif
/* include the table of contents */
for ( track = toc.firstCDtrack; track <= toc.lastCDtrack; track++ ) {
isAudio= YES;
if ( toc.info[track].control & DATA_TRACK ) isAudio = NO;
NXPrintf(mstream, "\tSET_CDtt(%2d, %02d.%02d, \"", track,
toc.info[track].elapsedSec / 60,
toc.info[track].elapsedSec % 60);
if ( toc.info[track].trackTitle )
NXPrintf(mstream, "%s", toc.info[track].trackTitle);
else if (!isAudio)
NXPrintf(mstream, "[data track]");
NXPrintf(mstream, "\");\n");
}
/* finish off the entry */
#ifdef ADD_SKIPSONGS
NXPrintf(mstream, "# endif\n");
#endif
NXPrintf(mstream, "\t}\n");
pboard = [Pasteboard new];
ptypes[0] = NXAsciiPboardType;
[pboard declareTypes:ptypes num:1 owner:self];
[pboard writeType:NXAsciiPboardType fromStream:mstream];
NXCloseMemory(mstream, NX_FREEBUFFER);
return self;
}
- pasteMcdEntry:sender
{
return self;
}
- (BOOL)openCdFd:(BOOL)tryToLoad
{
int devReady;
strcpy(rawDevName, [globPrefs rawDeviceName]);
if ( cd_fd > 0 ) close( cd_fd );
if (tryToLoad == FROM_LOAD_CD_REQUEST) {
cd_fd = open( rawDevName, O_RDONLY );
if ( cd_fd < 0 ) {
NXRunAlertPanel(0,
"Error return from open() for device %s",
0, 0, 0, rawDevName);
cd_fd = 0;
return NO;
}
}
else {
cd_fd = open( rawDevName, O_RDONLY | O_NDELAY );
if ( cd_fd < 0 ) {
cd_fd = 0;
return NO;
}
}
/* should check the result of do_inquiry to see which model
* of CD-ROM drive is there (so this would know which SCSI
* commands can be sent to the drive) */
do_inquiry(cd_fd, &cd_Inq, &cd_Ereply);
if ( cd_Inq.ir_devicetype != DEVTYPE_CDROM ) {
if (tryToLoad == FROM_LOAD_CD_REQUEST) {
NXRunAlertPanel(0,
"Device %s does not seem to be a CD-ROM drive",
0, 0, 0, rawDevName);
}
else {
printf("Device %s does not seem to be a CD-ROM drive\n",
rawDevName);
}
close( cd_fd );
return NO;
}
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
if ( cd_Ereply.er_sensekey == SENSE_UNITATTENTION ) {
/* drive is signalling attention because there is a new disc
* in the drive since the last time it was checked. In this
* context (loading in a CD), we don't care that it's new
*/
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
}
if ( (tryToLoad == FROM_LOAD_CD_REQUEST)
&& (devReady == -1)
&& (cd_Ereply.er_sensekey == SENSE_NOSENSE) ) {
/* I don't understand what the culprit is that forces this
* special case. It is needed on my NS/Intel box, but only
* on the *first* time the "Load" button is selected after
* the system is rebooted. For some reason, "loads" at that
* point will not succeed unless an unload is first done on
* the device...
*/
int err;
printf("mCD; device not ready, unloading and reloading the CD\n");
err = ioctl(cd_fd, DKIOCEJECT, NULL);
close( cd_fd );
cd_fd = 0;
cd_fd = open( rawDevName, O_RDONLY );
if ( cd_fd < 0 ) {
NXRunAlertPanel(0,
"Error return from second open() on device %s",
0, 0, 0, rawDevName);
cd_fd = 0;
return NO;
}
devReady = do_testunitready(cd_fd, &cd_Tval, &tur_Ereply);
}
if ( devReady ) {
if (tryToLoad == FROM_LOAD_CD_REQUEST) {
printf("mCD: Device %s is not ready (%d, %d)\n", rawDevName,
devReady, cd_Ereply.er_sensekey);
/* the cd_fd is not freed here, on purpose */
/* -------
* Hmm. I think I'll change my mind on that. I forget
* why I didn't want to free it, but now I think I do
* at least for one case.
*/
if ((devReady == -1) && (cd_Ereply.er_sensekey == SENSE_NOSENSE)) {
int err;
printf("mCD; device not ready, unloading the CD\n");
err = ioctl(cd_fd, DKIOCEJECT, NULL);
close( cd_fd );
cd_fd = 0;
}
}
else {
/* freeing cd_fd in this case might be a preference option
* to add someday... */
close( cd_fd );
}
return NO;
}
if (tryToLoad == FROM_CD_APP_DID_INIT) {
/* need to think more about under what circumstances the current
* volumes should be set from global preference values
*/
int leftInt, rightInt;
[globPrefs getVolumes:&leftInt:&rightInt];
cd_volumes.ch0_vol = leftInt;
cd_volumes.ch1_vol = rightInt;
[self setPlayVolumes:cd_volumes.ch0_vol :cd_volumes.ch1_vol];
} else {
do_modesense_pc_E(cd_fd, &cd_volumes, &cd_Ereply);
}
[leftVolumeSliderID setIntValue: cd_volumes.ch0_vol];
[rightVolumeSliderID setIntValue: cd_volumes.ch1_vol];
curCdIndex = -1;
curAbsSecond = -1;
/* do_preventremoval_1e(cd_fd, YES, &cd_Ereply); */
do_timed_updates = YES; /* initial testing */
[loadButtonID setEnabled:NO];
[playButtonID setEnabled:YES];
[pauseButtonID setEnabled:YES];
[stopButtonID setEnabled:YES];
[unloadButtonID setEnabled:YES];
return YES; /* successfully setup */
}
- fillTocTitles
{
u_int key;
struct rsc_media_catnum_reply cd_catnum;
char origCatNumber[MEDIA_CATNUM_LENGTH];
BOOL switchUPC;
key = [cd_dbase indexKey:&toc];
setreuid(-1, geteuid()); /* switch to real uid */
[cd_dbase fillTocTitles:&toc givenKey:key];
setreuid(-1, 0); /* back to being root */
/* check out the media catalog number, if this CD has one */
switch (cd_curpos.rsc_audio_status) {
case RSC_ASTAT_PLAYING:
/* don't read the media catalog number. Trying to read
* it will stop the CD on the Apple CD300 and NeXT
* drives. [on the Toshiba drive, it doesn't stop the
* music, and in fact the command sometimes won't work
* return the catalog number unless the CD *is* playing!]
*/
if ( !toc.discCatNum && [globPrefs consoleDebugMsgs] ) {
printf("mCD info: didn't check for media catalog number\n");
}
break;
case RSC_ASTAT_PAUSED:
/* it'd be nice to take the same precaution when the cd-rom
* drive is paused, but it seems that the CD-ROM drive that
* NeXT used to sell (a Sony mechanism) returns "paused" at
* times when it's not really paused. The Apple CD300 (a
* newer Sony mechanism) doesn't seem to have this problem.
*/
case RSC_ASTAT_PLAYCOMPLETE:
case RSC_ASTAT_PLAYABORTED:
case RSC_ASTAT_NONE:
switchUPC = NO;
do_readmediacatnum_42(cd_fd, &cd_catnum, &cd_Ereply);
if ( cd_catnum.catnum_isSet ) {
memcpy(origCatNumber, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH);
fixCatNum(&cd_catnum);
if (!toc.discCatNum) switchUPC = YES;
else {
if ( strncmp(toc.discCatNum, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH)) {
/* the UPC in our database does not match the UPC this
* drive found on the CD itself. First have to check
* for lame NeXT-CD-ROM case (which misses 3 chars)
*/
if ( cd_catnum.media_catnum[MEDIA_CATNUM_LENGTH-1] != '?') switchUPC = YES;
else {
if ( strncmp(toc.discCatNum,cd_catnum.media_catnum,
MEDIA_CATNUM_LENGTH-3)) switchUPC = YES;
}
}
if ( switchUPC && [globPrefs consoleDebugMsgs]) {
/* tell the user of mismatch */
int i;
u_char *cptr;
printf("mCD info: original UPC = x'");
cptr = &(origCatNumber[0]);
printf("%.2X", *cptr++); /* ignore comp. warning */
for (i=1; i<MEDIA_CATNUM_LENGTH; i++) {
printf(" %.2X", *cptr++); /* ignore comp. warning */
}
printf("'\n");
printf("mCD info: fixed up UPC = \"%.15s\"\n", cd_catnum.media_catnum);
printf("mCD info: DBase return = \"%15s\"\n", toc.discCatNum);
printf("mCD info: currently using the fixed upCD's catalog number\n");
}
}
}
if ( switchUPC ) {
memcpy(holdCatNumber, cd_catnum.media_catnum, MEDIA_CATNUM_LENGTH);
holdCatNumber[MEDIA_CATNUM_LENGTH+1] = '\0';
toc.discCatNum = &(holdCatNumber[0]);
}
/* Redoing the "spinup" here seems to reduce the problem
* of getting a unit attn when do_readmediacatnum finds
* a media code on the music CD */
do_spinup_1b(cd_fd, &cd_Tval, &cd_Ereply);
break;
}
return self;
}
@end
/* I've been testing this program on three different models of CD-ROM
* drives. Each one returns the catalog number in a different format.
* In the case of the CD-ROM drive NeXT used to sell, it doesn't even
* return the whole number. Do the best we can to map the three
* formats (that I know of so far) into a common format.
*
* Apple CD300 UPC = '3030 3735 3939 3235 3739 3432 3130 30'
* DEC-Toshiba UPC = '0000 0705 0909 0205 0709 0402 0100 00'
* NeXT CD-ROM UPC = '0075 9925 7942 0000 0000 0000 0000 00'
*
* Not sure if this routine should really be in the scsi_cd subproj...
*/
void fixCatNum(catNumPtr)
struct rsc_media_catnum_reply *catNumPtr;
{
char *tempPtr, *endPtr;
BOOL all_char, all_dec;
all_char = YES; /* all bytes are characters '0' to '9' */
all_dec = YES; /* all bytes are 0x00 to 0x09 */
tempPtr = &(catNumPtr->media_catnum[0]);
endPtr = tempPtr + MEDIA_CATNUM_LENGTH;
for (; tempPtr < endPtr; tempPtr++) {
if (*tempPtr < '0' || *tempPtr > '9') all_char = NO;
if (*tempPtr > 0x09) all_dec = NO;
}
if (all_dec) {
/* all decimal, change them to all-characters */
tempPtr = catNumPtr->media_catnum;
for (; tempPtr < endPtr; tempPtr++) *tempPtr += 0x30;
all_dec = NO;
all_char = YES;
}
if (all_char) return;
/* the NeXT format, have to expand it, going right-to-left */
/* [done the brute-force way, and assumes MEDIA_CATNUM_LENGTH = 15] */
catNumPtr->media_catnum[14] = (catNumPtr->media_catnum[7] >> 4) + 0x30;
catNumPtr->media_catnum[13] = (catNumPtr->media_catnum[6] & 0x0F) + 0x30;
catNumPtr->media_catnum[12] = (catNumPtr->media_catnum[6] >> 4) + 0x30;
catNumPtr->media_catnum[11] = (catNumPtr->media_catnum[5] & 0x0F) + 0x30;
catNumPtr->media_catnum[10] = (catNumPtr->media_catnum[5] >> 4) + 0x30;
catNumPtr->media_catnum[9] = (catNumPtr->media_catnum[4] & 0x0F) + 0x30;
catNumPtr->media_catnum[8] = (catNumPtr->media_catnum[4] >> 4) + 0x30;
catNumPtr->media_catnum[7] = (catNumPtr->media_catnum[3] & 0x0F) + 0x30;
catNumPtr->media_catnum[6] = (catNumPtr->media_catnum[3] >> 4) + 0x30;
catNumPtr->media_catnum[5] = (catNumPtr->media_catnum[2] & 0x0F) + 0x30;
catNumPtr->media_catnum[4] = (catNumPtr->media_catnum[2] >> 4) + 0x30;
catNumPtr->media_catnum[3] = (catNumPtr->media_catnum[1] & 0x0F) + 0x30;
catNumPtr->media_catnum[2] = (catNumPtr->media_catnum[1] >> 4) + 0x30;
catNumPtr->media_catnum[1] = (catNumPtr->media_catnum[0] & 0x0F) + 0x30;
catNumPtr->media_catnum[0] = (catNumPtr->media_catnum[0] >> 4) + 0x30;
/* and after all that, we probably have three zeros at the end,
* which most-likely means we've lost the last three digits.
* Put in question-marks, I guess.
*/
if ( (catNumPtr->media_catnum[14] == '0')
&& (catNumPtr->media_catnum[13] == '0')
&& (catNumPtr->media_catnum[12] == '0') ) {
catNumPtr->media_catnum[12] = '?';
catNumPtr->media_catnum[13] = '?';
catNumPtr->media_catnum[14] = '?';
}
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.