Play.c

/* File:    Play.c
 * Purpose: demonstrate Apple CD SC control
 * Date:    17 June 1988
 * Comments:
 *          This code is written for LightSpeed C version 2.15
 *          Create a project with this source plus MacTraps.
 *          Requires some associated resources (in the file Play.r).
 *          With this program on your hard disk, you can click
 *          on a track on an Audio CD and the CD SC drive will
 *          start audio play, beginning at that track and continuing
 *          to the end of the disc.
 *
 * Revisions:
 *
 *          Converted to converted to mpw 3.0 8/29 mb
 *
 *  Build instructions: 
 *
 *      rez -c MBAU :RIncludes:Types.r Play.r  -o Play  
 *      c Play.c -w                         
 *      link Play.c.o -o Play -c aucd ¶
 *         "{CLibraries}"CRuntime.o ¶
 *         "{CLibraries}"CInterface.o ¶
 *         "{Libraries}"Interface.o
 *      
 *
 */
 
#include <types.h>      /* Nearly always required */
#include <quickdraw.h>  /* To access the qd globals */
#include <fonts.h>      /* Only for InitFonts() trap */
#include <events.h>     /* GetNextEvent(), ... */
#include <windows.h>    /* GetNewWindow(), ... */
#include <dialogs.h>    /* InitDialogs() and GetNewDialog() */
#include <menus.h>      /* EnableItem(), DisableItem() */
#include <desk.h>       /* SystemTask(), SystemClick() */
#include <TextEdit.h>
#include <Resources.h>  /* GetResource() */
#include <OSUtils.h>    /* SysBeep() */
#include <ToolUtils.h>
#include <SegLoad.h>
#include <Strings.h>
#include <Devices.h>
#include <Files.h>
#include <Packages.h>
 
#include <Memory.h>
#include <OSEvents.h>
 
/* Some constants, from the CD SC Developer's Guide chapter 7 */
#define TRACKADDR   2
#define STEREO      9
#define INPROGRESS  0
 
/* Some commands, from the CD SC Developer's Guide chapter 7 */
#define APLAY   104
#define ASTOP   106
#define ASTATUS 107
 
/* The resource ID I use for my error dialog */
#define errorID 128
 
/* prototype function declarations. */
void    Error(char *, short);
void    ExtractTrackNo(char *, short *);
OSErr   AStop(short, short);
OSErr   APlay(short, short);
OSErr   Play(char *, short);
short   GetDrvRefNum(short);
void    PlayOnStartup(short);
Boolean AskAndPlay(void);
void    main(void);
pascal void Debugger(/*void*/) extern 0xA9FF;
 
/************************************************************************
 *
 *  Function:       Error
 *
 *  Purpose:        Report an error to the user.
 *
 *  Returns:        nothing
 *
 *  Side Effects:   none.
 *
 *  Description:    Takes a string and a result code.  Convert the result
 *                  into a pascal string using NumToString() and then just
 *                  do a switch to determine which friendly error message
 *                  to put up on the screen.  ParamText() sets up the alert
 *                  so that it has the correct information.  Use Alert() to 
 *                  actually display the message to the user.
 *
 ************************************************************************/
void
Error(where, result)
char    *where;
short   result;
{
    DialogPtr   dPtr;
    short       itemhit;
    Str255      strResult;
    long        longResult;
    
    longResult = result;
    NumToString(longResult, strResult);
    
    switch (result)
    {
        case -56:
            ParamText(where, strResult, "\pSpecified drive number doesn't match any number in the drive queue.", "\p");
            break;
        case -51:
            ParamText(where, strResult, "\pReference number specifies nonexistent access path.", "\p");
            break;
        case -50:
            ParamText(where, strResult, "\pError in parameter list.", "\p");
            break;
        case -43:
            ParamText(where, strResult, "\pFile not found.", "\p");
            break;
        case -36:
            ParamText(where, strResult, "\pI/O error.", "\p");
            break;
        case -35:
            ParamText(where, strResult, "\pNo such volume.", "\p");
            break;
        case -21:
            ParamText(where, strResult, "\pBad unit number.", "\p");
            break;
        default:
            ParamText(where, strResult, "\p", "\p");
            break;
    }
    
    Alert(errorID, (ProcPtr) NULL);
}
 
              
/************************************************************************
 *
 *  Function:       ExtractTrackNo
 *
 *  Purpose:        Extract track number in BCD from PString
 *
 *  Returns:        nothing
 *
 *  Side Effects:   *track gets a new value
 *
 *  Description:    Extract track number in BCD from PString "name".
 *                  "name" is always of the form "Track XX", where XX
 *                  ranges from " 1" (1 with a leading space) to "99"
 *                  (You can't have more than 99 tracks on a compact disc.
 *                  Bet you didn't know that, did you?  Right in the "Yellow
 *                  Book" specification of compact disc encoding.)
 *
 ************************************************************************/
void
ExtractTrackNo(name, track)
char        *name;
short       *track;
{
    short   size;
    short   t;
        
    t = 0;
    size = *name;
    if (name[size-1] != ' ')
        t = (name[size-1] - '0') * 16;
    t += name[size] - '0';
    
    *track = t; 
}
 
/************************************************************************
 *
 *  Function:       AStop
 *
 *  Purpose:        stop playing an audio track
 *
 *  Returns:        OSErr.  Probably either
 *                      noErr       everything's hunky-dory!
 *                      paramErr    you messed up the call somehow.
 *
 *  Side Effects:   stops play.
 *
 *  Description:    The track that you pass in is the LAST track that you
 *                  want to have play.  This means that if you wanted to
 *                  play only one track, you'd pass the same value to
 *                  this routine and APlay() [q.v.]
 *
 *                  Note that this routine isn't called in this sample,
 *                  but it's included for your enjoyment.
 *
 ************************************************************************/
OSErr
AStop(track, refNum)
short   track;
short   refNum;
{
    struct {
        short   addrFormat;
        long    address;
    }   csParam;
    
    csParam.addrFormat = TRACKADDR;
    csParam.address = track;
    return (Control(refNum, ASTOP, &csParam));
}
 
/************************************************************************
 *
 *  Function:       APlay
 *
 *  Purpose:        start playing an audio track
 *
 *  Returns:        OSErr.  Probably either
 *                      noErr       everything's hunky-dory!
 *                      paramErr    you messed up the call somehow.
 *
 *  Side Effects:   starts play.  By default, this will play until the
 *                  end of the disc.
 *
 *  Description:    The track that you pass in is the first track that you
 *                  want to have play.
 *
 ************************************************************************/
OSErr
APlay(track, refNum)
short   track;
short   refNum;
{
    struct {
        short   addrFormat;
        long    address;
        short   stopAddress;
        short   playMode;
    }   csParam;
    
    csParam.addrFormat = TRACKADDR;
    csParam.address = track;    /* must be in BCD */
    csParam.stopAddress = 0;
    csParam.playMode = STEREO;
    return (Control(refNum, APLAY, &csParam));
}
 
 
/************************************************************************
 *
 *  Function:       Play
 *
 *  Purpose:        play a track, given it's "filename"
 *
 *  Returns:        OSErr
 *                      whatever is returned by APlay()
 *
 *  Side Effects:   starts play of the audio track.
 *
 *  Description:    The two parameters passed in are the file name (which
 *                  will always be of the form "Track XX", XX varying from
 *                  " 1" to "99") and the driver reference number. Extract
 *                  the track number and use it to call APlay() [q.v.]
 *
 ************************************************************************/
OSErr
Play(name, driveNumber)
char        *name;
short       driveNumber;
{
    short   trackno;
    OSErr   result;
    
    ExtractTrackNo(name, &trackno);
    return( APlay(trackno, driveNumber) );
}
 
 
/************************************************************************
 *
 *  Function:       GetDrvRefNum
 *
 *  Purpose:        Get the driver reference number
 *
 *  Returns:        short.  The driver reference number
 *
 *  Side Effects:   none.
 *
 *  Description:    PBHGetVInfo() will retrieve the driver reference
 *                  number, given the vRefNum associated with a file.
 *                  We acquired the vRefNum as something passed into
 *                  the program, either by SFGetFile() or by 
 *                  GetAppFile().
 *
 *                  We'll use the driver reference number in all our
 *                  control calls to the driver.
 *
 ************************************************************************/
short
GetDrvRefNum(vRefNum)
short   vRefNum;
{
    HParamBlockRec  io;
    
    io.volumeParam.ioCompletion = NULL;
    io.volumeParam.ioNamePtr = NULL;
    io.volumeParam.ioVRefNum = vRefNum;
    io.volumeParam.ioVolIndex = 0;
    PBHGetVInfo(&io, false);
    return io.volumeParam.ioVDRefNum;
}
 
 
 
/************************************************************************
 *
 *  Function:       PlayOnStartup
 *
 *  Purpose:        Play the files passed in to our application
 *
 *  Returns:        nothing
 *
 *  Side Effects:   plays some files.
 *
 *  Description:    Loop, using GetAppFiles() to get the next "file"
 *                  (actually audio CD track) to play.  Continue until
 *                  we run out of files to play or until we get some
 *                  error while trying to play a track.
 *
 *                  Exercise to the programmer:
 *                  As this routine is currently written, it doesn't
 *                  handle opening multiple tracks at once.  (e.g. the
 *                  user selects five tracks and opens them all.)  Right
 *                  now, you'll only hear the last track (each Play()
 *                  command cancels the previous one.)
 *
 *                  Add a routine to check the status of the CD SC 
 *                  drive (using AStatus) so that you wait for a track
 *                  to finish before starting the next track.
 *
 ************************************************************************/
void
PlayOnStartup(count)
short   count;
{
    short   i;
    AppFile app;
    OSErr   result;
 
    for (i = 0; i++<count; )
    {
        GetAppFiles(i, &app);
        if (app.fType == 'trak')
        {
            result = Play((char *)app.fName, GetDrvRefNum(app.vRefNum));
            if (result != noErr)
                Error("\pPlay returned", result);
        }
    }
}
 
 
/************************************************************************
 *
 *  Function:       AskAndPlay
 *
 *  Purpose:        Prompt the user for a track and play it
 *
 *  Returns:        Boolean.  Either:
 *                      true    the user wants us to continue
 *                      false   the user wants us to exit, or an error
 *                              occurred.
 *
 *  Side Effects:   may play a track.
 *
 *  Description:    Use SFGetFile() with a specific type ('trak') to get
 *                  a list of just audio CD tracks.  If the user selects
 *                  an audio CD track, play it.
 *
 ************************************************************************/
Boolean
AskAndPlay()
{
    SFReply sf;
    short   refnum;
    static Point where = {100,100};
    static OSType sftl = 'trak';
    Boolean result;
    OSErr   errorCode;
    
    result = true;
    FlushEvents(everyEvent-diskMask,0);
    SFGetFile(where, "\pSelect Track to Play", (ProcPtr)NULL,
        1, &sftl, (ProcPtr)NULL, &sf);
    if (!sf.good)
        result = false;
    else
    {
        errorCode = Play((char *)sf.fName, GetDrvRefNum(sf.vRefNum));
        if (errorCode != noErr)
        {
            Error("\pPlay returned", result);
            result = false;
        }
    }
    return result;
}
 
 
/************************************************************************
 *
 *  Function:       main
 *
 *  Purpose:        The Master Cylinder.   This is where we start.
 *
 *  Returns:        nothing.
 *
 *  Side Effects:   does whatever you tell it.
 *
 *  Description:    Do the standard Macintosh initialization.  Following
 *                  all the boilerplate, use CountAppFiles() to see how
 *                  we were started.  We could be started one of three
 *                  ways:
 *                  1)  The user opened our application.  We will have 
 *                      a count of zero.  Go ask the user for a track.
 *                  2)  The user opened an audio CD track "file".  We
 *                      will have a non-zero count.  Go play the track(s)
 *                      that the user asked.
 *
 ************************************************************************/
void
main()
{
    short   message, count;
    short   refNum;
    
    MaxApplZone();
    MoreMasters();
 
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(0);
 
    InitCursor();
    FlushEvents(everyEvent-diskMask,0);
 
    CountAppFiles(&message, &count);
    if (count)
        PlayOnStartup(count);
    else
        while (AskAndPlay())
            ;               /* null loop */
}