/* play.c: */

#include "includes.h"

#define VERSION "1.1 (04.01.1994)"

#define HRDBLBF

#define CDDA_BUFSIZE 2368
#define SUBCHANNEL_SIZE 16
#define STD_BUFSIZE 2048
#define SENSE_LENGTH 32
#define AUDIO_BUFSIZE ((CDDA_BUFSIZE-SUBCHANNEL_SIZE)/g_compression_factor/4)
#define TOTAL_CDDA_BUFSIZE (g_buffers*2*CDDA_BUFSIZE)
#define TOTAL_AUDIO_BUFSIZE (g_buffers*2*AUDIO_BUFSIZE)
#define CDDA_STD_BUFSIZE (STD_BUFSIZE+SENSE_LENGTH)

typedef short t_bool;

typedef struct toc {
  char reserved1;
  unsigned char flags;
  unsigned char track_number;
  char reserved2;
  long address;
} t_toc;

typedef enum dtype {UNKNOWNDRIVE = 0, TOSHIBA, APPLECD300} t_drivetype;

static char *TheVersion = "$VER: PlayCDDA " VERSION;

t_bool g_called_from_cli;
char g_scsi_device[80];
t_drivetype g_whatdrive = UNKNOWNDRIVE;
int g_scsi_id;
LONG g_memory_type = MEMF_CHIP;
UBYTE *g_cdda_base = NULL;
UBYTE *g_audio_base = NULL;
UBYTE *g_std_buf_base = NULL;
UBYTE *g_cdda_buf[2];
UBYTE *g_cdda_std_buf;
UBYTE *g_audio_buf[2];
struct MsgPort *g_cdda_port = NULL;
struct MsgPort *g_audio_port[2] = { NULL, NULL };
ULONG g_cdda_sigmask;
ULONG g_audio_sigmask[2];
#ifndef HRDBLBF
struct IOStdReq *g_scsireq = NULL;
t_bool g_outstanding_cdda_request = FALSE;
struct SCSICmd *g_scsicmd = NULL;
UBYTE *g_sense_data;
#else
struct IOStdReq *g_scsireq[2] = {NULL, NULL};
t_bool g_outstanding_cdda_request[2] = {FALSE, FALSE};
struct SCSICmd *g_scsicmd[2] = {NULL, NULL};
UBYTE *g_sense_data[2];
#endif
struct IOAudio *g_audioreq[2] = { NULL, NULL };
t_bool g_audio_device_open = FALSE;
long g_period;
int g_toc_length;
t_toc g_toc[100];
short g_volume = 1;
/* possible values for g_compression_factor: 2, 3, 4, 6, 7, 12, 14, 28, 49 */
unsigned short g_compression_factor = 2;
unsigned short g_buffers = 4;

/* user interface variables: */

#ifdef __SASC
extern struct Library *DOSBase;
#endif
struct Library *IconBase = NULL;
struct Library *IntuitionBase = NULL;
struct Library *GadToolsBase = NULL;
struct GfxBase *GfxBase = NULL;
struct Screen *g_screen = NULL;
void *g_visual_info = NULL;
struct Window *g_window = NULL;
struct Gadget *g_glist = NULL;
t_bool g_bye = FALSE;
char g_track_str[3] = { 0, 0, 0 };
char g_index_str[3] = { 0, 0, 0 };
char g_time_str[6] = { 0, 0, ':', 0, 0, 0 };
unsigned char g_track, g_index;
unsigned char g_minute, g_seconds;

enum gadget_ids {
  GID_SAMPLING_RATE = 21,
  GID_BUFFERS,
  GID_VOLUME,
  GID_PREV,
  GID_NEXT,
  GID_START,
  GID_STOP,
  GID_TRACK,
  GID_INDEX,
  GID_TIME,

  /* always last: */
  GID_MAX
};

struct Gadget *g_gadgets[GID_MAX];

#ifdef __SASC
void __regargs __chkabort(void)
{
}
#endif

void Cleanup_User_Interface (void)
{
  if (g_window)
    CloseWindow (g_window);
  if (g_glist)
    FreeGadgets (g_glist);
  if (g_visual_info)
    FreeVisualInfo (g_visual_info);
  if (g_screen)
    UnlockPubScreen (NULL, g_screen);
  if (GfxBase)
    CloseLibrary ((struct Library *) GfxBase);
  if (GadToolsBase)
    CloseLibrary (GadToolsBase);
  if (IntuitionBase)
    CloseLibrary (IntuitionBase);
}

void Cleanup_Audio (void)
{
  if (g_cdda_base) {
    FreeMem (g_cdda_base, TOTAL_CDDA_BUFSIZE + 15);
    g_cdda_base = NULL;
  }
  if (g_audio_base) {
    FreeMem (g_audio_base, TOTAL_AUDIO_BUFSIZE + 15);
    g_audio_base = NULL;
  }
  if (g_audio_device_open) {
    CloseDevice ((struct IORequest *) g_audioreq[0]);
    g_audio_device_open = FALSE;
  }
  if (g_audio_port[0]) {
    DeleteMsgPort (g_audio_port[0]);
    g_audio_port[0] = NULL;
  }
  if (g_audio_port[1]) {
    DeleteMsgPort (g_audio_port[1]);
    g_audio_port[1] = NULL;
  }
  if (g_audioreq[0]) {
    FreeMem (g_audioreq[0], sizeof (struct IOAudio));
    g_audioreq[0] = NULL;
  }
  if (g_audioreq[1]) {
    FreeMem (g_audioreq[1], sizeof (struct IOAudio));
    g_audioreq[1] = NULL;
  }
}

void Cleanup (void)
{
#ifdef HRDBLBF
  int i;
#endif

  Cleanup_Audio ();

  if (g_std_buf_base)
    FreeMem (g_std_buf_base, CDDA_STD_BUFSIZE + 15);
#ifndef HRDBLBF
  if (g_scsicmd)
    FreeMem (g_scsicmd, sizeof (struct SCSICmd));
  if (g_scsireq) {
    if (g_scsireq->io_Device) {
      if (g_outstanding_cdda_request) {
        AbortIO ((struct IORequest *) g_scsireq);
        WaitIO ((struct IORequest *) g_scsireq);
      }
      CloseDevice ((struct IORequest *) g_scsireq);
    }
    DeleteIORequest ((struct IORequest *) g_scsireq);
  }
#else
  for (i = 0; i < 2; i++) {
    if (g_scsicmd[i])
      FreeMem (g_scsicmd[i], sizeof (struct SCSICmd));
    if (g_scsireq[i]) {
      if (g_scsireq[i]->io_Device) {
	if (g_outstanding_cdda_request[i]) {
	  AbortIO ((struct IORequest *) g_scsireq[i]);
	  WaitIO ((struct IORequest *) g_scsireq[i]);
	}
	CloseDevice ((struct IORequest *) g_scsireq[i]);
      }
      DeleteIORequest ((struct IORequest *) g_scsireq[i]);
    }
  }
#endif
  if (g_cdda_port)
    DeleteMsgPort (g_cdda_port);

  Cleanup_User_Interface ();
  
  if (IconBase)
    CloseLibrary (IconBase);
}

void Fatal_Error (char *p_message, ...)
{
  va_list arg;

  static struct EasyStruct req = {
    sizeof (struct EasyStruct),
    0,
    (UBYTE *) "PlayCDDA Error",
    NULL,
    (UBYTE *) "Abort"
  };

  va_start (arg, p_message);
  if (IntuitionBase) {
    req.es_TextFormat = (UBYTE *) p_message;
    EasyRequestArgs (NULL, &req, NULL, arg);
  } else if (g_called_from_cli) {
    VPrintf ((UBYTE *) p_message, (LONG *) arg);
    WriteChars ((UBYTE *) "\n", 1);
  } else
    Alert (0x0000CDDA);

  va_end (p_message);

  exit (1);
}

char *Open_User_Interface (void)
{
  static struct TextAttr Topaz8 = { (UBYTE *) "topaz.font", 8, 0, 0, };
  struct TextFont *font;
  int i, j;
  static char *labels[20] = {
    "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",
    "12", "13", "14", "15", "16", "17", "18", "19", "20"
  };
  static char *sampling_rate_labels[] = {
    "22050 bps",  /* 2 */
    "14700 bps",  /* 3 */
    "11025 bps",  /* 4 */
    "7350 bps",   /* 6 */
    "6300 bps",   /* 7 */
    NULL
  };
  static char *buffers_labels[] = {
    "2",
    "4",
    "8",
    "16",
    "32",
    "64",
    NULL
  };
  static char *volume_labels[] = {
    "Low",
    "Medium",
    "High",
    NULL
  };
  struct NewGadget ng;
  struct Gadget *gad;
  int topborder;

  if (!(IntuitionBase = OpenLibrary ((UBYTE *) "intuition.library", 37)))
    return "cannot open intuition.library";
  if (!(GadToolsBase = OpenLibrary ((UBYTE *) "gadtools.library", 37)))
    return "cannot open gadtools.library";
  if (!(GfxBase = (struct GfxBase *)
  	OpenLibrary ((UBYTE *) "graphics.library", 37)))
    return "cannot open graphics.library";

  /* does the font exist? */
  if (!(font = OpenFont (&Topaz8)))
    return "cannot open topaz 8 font";
  CloseFont (font);

  if (!(g_screen = LockPubScreen (NULL)))
    return "cannot lock default public screen";

  if (!(g_visual_info = GetVisualInfo (g_screen, TAG_END)))
    return "GetVisualInfo() failed";

  gad = CreateContext (&g_glist);

  topborder = g_screen->WBorTop + (g_screen->Font->ta_YSize + 1);

  ng.ng_Width = 20;
  ng.ng_Height = 12;
  ng.ng_TextAttr = &Topaz8;
  ng.ng_VisualInfo = g_visual_info;
  ng.ng_Flags = 0;

  for (i=0; i<5; i++)
    for (j=0; j<4; j++) {
      ng.ng_GadgetText = (UBYTE *) labels[i*4+j];
      ng.ng_GadgetID = i*4 + j + 1;
      ng.ng_LeftEdge = 10 + j * 24;
      ng.ng_TopEdge = topborder + 2 + i * 16;
      g_gadgets[ng.ng_GadgetID] = gad =
        CreateGadget (BUTTON_KIND, gad, &ng,
          GA_Disabled, TRUE,
          TAG_END);
    }

  ng.ng_GadgetID = GID_PREV;
  ng.ng_GadgetText = (UBYTE *) "Prev";
  ng.ng_Width = 44;
  ng.ng_LeftEdge = 10;
  ng.ng_TopEdge = topborder + 2 + 5 * 16;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (BUTTON_KIND, gad, &ng, TAG_END);
  
  ng.ng_GadgetID = GID_NEXT;
  ng.ng_GadgetText = (UBYTE *) "Next";
  ng.ng_LeftEdge = 58;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (BUTTON_KIND, gad, &ng, TAG_END);

  ng.ng_GadgetID = GID_START;
  ng.ng_GadgetText = (UBYTE *) "Start";
  ng.ng_LeftEdge = 120;
  ng.ng_TopEdge = topborder + 2 + 4 * 16;
  ng.ng_Width = 120;
  ng.ng_Height = 28;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (BUTTON_KIND, gad, &ng, TAG_END);

  ng.ng_GadgetID = GID_STOP;
  ng.ng_GadgetText = (UBYTE *) "Stop";
  ng.ng_LeftEdge = 250;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (BUTTON_KIND, gad, &ng, TAG_END);

  ng.ng_GadgetText = (UBYTE *) "Sampling rate:";
  ng.ng_GadgetID = GID_SAMPLING_RATE;
  ng.ng_Width = 120;
  ng.ng_Height = 12;
  ng.ng_LeftEdge = 250;
  ng.ng_TopEdge = topborder + 2;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (CYCLE_KIND, gad, &ng,
      GTCY_Labels, sampling_rate_labels,
      TAG_END);

  ng.ng_GadgetText = (UBYTE *) "Buffers:      ";
  ng.ng_TopEdge += 16;
  ng.ng_GadgetID = GID_BUFFERS;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (CYCLE_KIND, gad, &ng,
      GTCY_Labels, buffers_labels,
      GTCY_Active, 1,
      TAG_END);

  ng.ng_GadgetText = (UBYTE *) "Volume:       ";
  ng.ng_TopEdge += 16;
  ng.ng_GadgetID = GID_VOLUME;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (CYCLE_KIND, gad, &ng,
      GTCY_Labels, volume_labels,
      GTCY_Active, (int) g_volume-1,
      TAG_END);

  ng.ng_GadgetID = GID_TRACK;
  ng.ng_GadgetText = (UBYTE *) "Track";
  ng.ng_Width = 25;
  ng.ng_Height = 12;
  ng.ng_LeftEdge = 170;
  ng.ng_TopEdge = topborder + 2 + 3 * 16;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (TEXT_KIND, gad, &ng,
      GTTX_Border, TRUE,
      TAG_END);

  ng.ng_GadgetID = GID_INDEX;
  ng.ng_GadgetText = (UBYTE *) "Index";
  ng.ng_LeftEdge = 250;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (TEXT_KIND, gad, &ng,
      GTTX_Border, TRUE,
      TAG_END);
  
  ng.ng_GadgetID = GID_TIME;
  ng.ng_GadgetText = (UBYTE *) "Time";
  ng.ng_Width = 50;
  ng.ng_LeftEdge = 320;
  g_gadgets[ng.ng_GadgetID] =
    gad = CreateGadget (TEXT_KIND, gad, &ng,
      GTTX_Border, TRUE,
      TAG_END);
  

  if (!gad)
    return "cannot create gadgets";

  g_window = OpenWindowTags (NULL,
    WA_Title, TheVersion + 6,
    WA_Gadgets, g_glist,
    WA_Left, 20,
    WA_Top, 20,
    WA_Width, 385,
    WA_Height, topborder + 98,
    WA_IDCMP, IDCMP_CLOSEWINDOW | BUTTONIDCMP | CYCLEIDCMP,
    WA_PubScreen, g_screen,
    WA_DragBar, TRUE,
    WA_DepthGadget, TRUE,
    WA_CloseGadget, TRUE,
    WA_Activate, TRUE,
    WA_SmartRefresh, TRUE,
    TAG_END);
  if (!g_window)
    return "cannot open window";

  return NULL;
}

void Alloc_Audio (void)
{
  static UBYTE whichannel[] = { 1, 2, 4, 8 };
  int i;

  /* allocate buffers: */

  g_audio_base = AllocMem (TOTAL_AUDIO_BUFSIZE + 15, MEMF_PUBLIC | MEMF_CHIP);
  if (!g_audio_base)
    Fatal_Error ("cannot allocate memory");

  g_cdda_base = AllocMem (TOTAL_CDDA_BUFSIZE + 15, MEMF_PUBLIC | g_memory_type);
  if (!g_cdda_base)
    Fatal_Error ("cannot allocate memory");

  /* make the buffers quad-word aligned. This greatly helps 
   * performance on '040-powered systems with DMA SCSI
   * controllers.
   */
  g_cdda_buf[0] = (UBYTE *)(((long) g_cdda_base + 15) & ~15);
  g_audio_buf[0] = (UBYTE *)(((long) g_audio_base + 15) & ~15);

  g_cdda_buf[1] = g_cdda_buf[0] + g_buffers * CDDA_BUFSIZE;  
  g_audio_buf[1] = g_audio_buf[0] + g_buffers * AUDIO_BUFSIZE;

  /* allocate message ports and IO requests: */

  if (!(g_audio_port[0] = CreateMsgPort ()) || 
      !(g_audio_port[1] = CreateMsgPort ()))
    Fatal_Error ("cannot allocate message ports");

  g_audio_sigmask[0] = (1 << g_audio_port[0]->mp_SigBit);
  g_audio_sigmask[1] = (1 << g_audio_port[1]->mp_SigBit);

  if (!(g_audioreq[0] = AllocMem (sizeof (struct IOAudio),
  			          MEMF_PUBLIC | MEMF_CLEAR)) ||
      !(g_audioreq[1] = AllocMem (sizeof (struct IOAudio),
  			          MEMF_PUBLIC | MEMF_CLEAR)))
    Fatal_Error ("cannot allocate memory");

  /* open audio device: */

  g_audioreq[0]->ioa_Request.io_Message.mn_ReplyPort = g_audio_port[0];
  g_audioreq[0]->ioa_Request.io_Message.mn_Node.ln_Pri = 0;
  g_audioreq[0]->ioa_Request.io_Command = ADCMD_ALLOCATE;
  g_audioreq[0]->ioa_Request.io_Flags = ADIOF_NOWAIT;
  g_audioreq[0]->ioa_AllocKey = 0;
  g_audioreq[0]->ioa_Data = whichannel;
  g_audioreq[0]->ioa_Length = sizeof (whichannel);

  if (OpenDevice ((UBYTE *) AUDIONAME, 0,
  		  (struct IORequest *) g_audioreq[0], 0))
    Fatal_Error ("cannot open audio.device\n");

  g_audio_device_open = TRUE;
 
  *(g_audioreq[1]) = *(g_audioreq[0]);
  g_audioreq[0]->ioa_Request.io_Message.mn_ReplyPort = g_audio_port[0];
  g_audioreq[1]->ioa_Request.io_Message.mn_ReplyPort = g_audio_port[1];
  g_audioreq[0]->ioa_Data = (UBYTE *) g_audio_buf[0];
  g_audioreq[1]->ioa_Data = (UBYTE *) g_audio_buf[1];

  for (i=0; i<2; i++) {
    struct IOAudio *req = g_audioreq[i];

    req->ioa_Request.io_Command = CMD_WRITE;
    req->ioa_Request.io_Flags = ADIOF_PERVOL;
    req->ioa_Length = g_buffers * AUDIO_BUFSIZE;
    req->ioa_Period = g_period;
    req->ioa_Volume = 64;
    req->ioa_Cycles = 1;
  }
}

void Alloc_CDROM (void)
{
#ifdef HRDBLBF
  int i;
#endif

  g_std_buf_base = AllocMem (CDDA_STD_BUFSIZE + 15, MEMF_PUBLIC | MEMF_CHIP);
  if (!g_std_buf_base)
    Fatal_Error ("cannot allocate memory");

#ifndef HRDBLBF
  g_scsicmd = AllocMem (sizeof (struct SCSICmd), MEMF_PUBLIC | MEMF_CHIP);
  if (!g_scsicmd)
    Fatal_Error ("cannot allocate memory");
#else
  for (i = 0; i < 2; i++) {
    g_scsicmd[i] = AllocMem (sizeof (struct SCSICmd), MEMF_PUBLIC | MEMF_CHIP);
    if (!g_scsicmd[i])
      Fatal_Error ("cannot allocate memory");
  }
#endif

  /* make the buffer quad-word aligned. This greatly helps 
   * performance on '040-powered systems with DMA SCSI
   * controllers.
   */

  g_cdda_std_buf = (UBYTE *)(((long) g_std_buf_base + 15) & ~15);
#ifndef HRDBLBF
  g_sense_data = g_cdda_std_buf + STD_BUFSIZE;
#else
  for (i = 0; i < 2; i++)
    g_sense_data[i] = g_cdda_std_buf + STD_BUFSIZE;
#endif
  
  /* allocate message ports and IO requests: */

  if (!(g_cdda_port = CreateMsgPort ()))
    Fatal_Error ("cannot allocate message port");

  g_cdda_sigmask = (1 << g_cdda_port->mp_SigBit);

#ifndef HRDBLBF
  if (!(g_scsireq = CreateIORequest (g_cdda_port,
  		 		     sizeof (struct IOStdReq))))
    Fatal_Error ("cannot create IO request\n");

  /* open SCSI device: */

  g_scsireq->io_Device = NULL;

  if (OpenDevice ((UBYTE *) g_scsi_device, g_scsi_id,
  		  (struct IORequest *) g_scsireq, 0)) {
    if (g_called_from_cli)
      Fatal_Error ("Cannot open \"%s\", unit %ld",
      		   g_scsi_device, g_scsi_id);
    else
      Fatal_Error ("Cannot open \"%s\", unit %ld\n"
      		   "Please edit the tooltype entries\n"
		   "in the PlayCDDA icon!",
      		   g_scsi_device, g_scsi_id);
  }
#else
  for (i = 0; i < 2; i++) {
    if (!(g_scsireq[i] = CreateIORequest (g_cdda_port,
				       sizeof (struct IOStdReq))))
      Fatal_Error ("cannot create IO request\n");

    /* open SCSI device: */

    g_scsireq[i]->io_Device = NULL;

    if (OpenDevice ((UBYTE *) g_scsi_device, g_scsi_id,
		    (struct IORequest *) g_scsireq[i], 0)) {
      if (g_called_from_cli)
	Fatal_Error ("Cannot open \"%s\", unit %ld",
		     g_scsi_device, g_scsi_id);
      else
	Fatal_Error ("Cannot open \"%s\", unit %ld\n"
		     "Please edit the tooltype entries\n"
		     "in the PlayCDDA icon!",
		     g_scsi_device, g_scsi_id);
    }
  }
#endif

}

void Do_SCSI_Command (UBYTE *p_command, int p_length,
		     short p_phase, int p_sync, int p_direction)
{
#ifndef HRDBLBF
  g_scsireq->io_Length   = sizeof (struct SCSICmd);
  g_scsireq->io_Data     = (APTR) g_scsicmd;
  g_scsireq->io_Command  = HD_SCSICMD;

  if (p_phase == -1) {
    g_scsicmd->scsi_Data        = (UWORD *) g_cdda_std_buf;
    g_scsicmd->scsi_Length      = STD_BUFSIZE;
  } else {
    g_scsicmd->scsi_Data        = (UWORD *) g_cdda_buf[p_phase];
    g_scsicmd->scsi_Length      = g_buffers * CDDA_BUFSIZE;
  }

  g_scsicmd->scsi_Flags       = SCSIF_AUTOSENSE | p_direction;
  g_scsicmd->scsi_SenseData   = (UBYTE *) g_sense_data;
  g_scsicmd->scsi_SenseLength = SENSE_LENGTH;
  g_scsicmd->scsi_SenseActual = 0;
  g_scsicmd->scsi_Command     = (UBYTE *) p_command;
  g_scsicmd->scsi_CmdLength   = p_length;

  if (p_sync) {
    int i = 0;
    
    do {
      DoIO ((struct IORequest *) g_scsireq);
      if (g_scsicmd->scsi_Status == 0)
        return;
      i++;
    } while (i < 2);
    Fatal_Error ("sync SCSI command failed");
  } else {
    SendIO ((struct IORequest *) g_scsireq);
    g_outstanding_cdda_request = TRUE;
  }
#else
  if (p_phase == -1) {
    p_phase = 0;
    g_scsicmd[p_phase]->scsi_Data        = (UWORD *) g_cdda_std_buf;
    g_scsicmd[p_phase]->scsi_Length      = STD_BUFSIZE;
  } else {
    g_scsicmd[p_phase]->scsi_Data        = (UWORD *) g_cdda_buf[p_phase];
    g_scsicmd[p_phase]->scsi_Length      = g_buffers * CDDA_BUFSIZE;
  }
  g_scsireq[p_phase]->io_Length   = sizeof (struct SCSICmd);
  g_scsireq[p_phase]->io_Data     = (APTR) g_scsicmd[p_phase];
  g_scsireq[p_phase]->io_Command  = HD_SCSICMD;

  g_scsicmd[p_phase]->scsi_Flags       = SCSIF_AUTOSENSE | p_direction;
  g_scsicmd[p_phase]->scsi_SenseData   = (UBYTE *) g_sense_data[p_phase];
  g_scsicmd[p_phase]->scsi_SenseLength = SENSE_LENGTH;
  g_scsicmd[p_phase]->scsi_SenseActual = 0;
  g_scsicmd[p_phase]->scsi_Command     = (UBYTE *) p_command;
  g_scsicmd[p_phase]->scsi_CmdLength   = p_length;

  if (p_sync) {
    int i = 0;
    
    do {
      DoIO ((struct IORequest *) g_scsireq[p_phase]);
      if (g_scsicmd[p_phase]->scsi_Status == 0)
        return;
      i++;
    } while (i < 2);
    Fatal_Error ("sync SCSI command failed");
  } else {
    SendIO ((struct IORequest *) g_scsireq[p_phase]);
    g_outstanding_cdda_request[p_phase] = TRUE;
  }
#endif
}

#ifndef HRDBLBF
void Wait_CDROM_Command (void)
#else
void Wait_CDROM_Command (short p_phase)
#endif     
{
  ULONG sig;

#ifndef HRDBLBF  
  sig = Wait (SIGBREAKF_CTRL_C | g_cdda_sigmask);
  if (sig & g_cdda_sigmask) {
    if (CheckIO ((struct IORequest *) g_scsireq)) {
      WaitIO ((struct IORequest *) g_scsireq);
      g_outstanding_cdda_request = FALSE;
      if (g_scsicmd->scsi_Status)
        Fatal_Error ("async SCSI command failed");
    }
  }
  if (sig & SIGBREAKF_CTRL_C)
    exit (1);
#else
  /* HR */

  if (p_phase < 0 || p_phase > 1)
    Fatal_Error ("wrong p_phase argument for Wait_CDROM_Command");

  if (g_outstanding_cdda_request[p_phase] != FALSE) {
    for (;;) {
      sig = Wait (SIGBREAKF_CTRL_C | g_cdda_sigmask);
      if (sig & g_cdda_sigmask) {
	if (CheckIO ((struct IORequest *) g_scsireq[p_phase])) {
	  WaitIO ((struct IORequest *) g_scsireq[p_phase]);
	  g_outstanding_cdda_request[p_phase] = FALSE;
	  if (g_scsicmd[p_phase]->scsi_Status)
	    Do_SCSI_Command (g_scsicmd[p_phase]->scsi_Command,
			     g_scsicmd[p_phase]->scsi_CmdLength,
			     p_phase, FALSE, SCSIF_READ);
	  else
	    break;
	}
      }
      if (sig & SIGBREAKF_CTRL_C)
	exit (1);
    }
  }
#endif
}

void Start_CDROM_Read (short p_phase, long p_sector)
{
  static UBYTE cmd[2][12] =
    {
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}
    };

  if (p_phase < 0 || p_phase > 1)
    Fatal_Error ("Invalid p_phase argument for Start_CDROM_Read()");

  cmd[p_phase][2] = (p_sector >> 24);
  cmd[p_phase][3] = ((p_sector >> 16) & 0xFF);
  cmd[p_phase][4] = ((p_sector >> 8) & 0xFF);
  cmd[p_phase][5] = (p_sector & 0xFF);

  if (g_whatdrive == APPLECD300) {
    cmd[p_phase][0] = 0xD8;
    cmd[p_phase][9] = g_buffers;
    Do_SCSI_Command (cmd[p_phase], 12, p_phase, FALSE, SCSIF_READ);
  }
  else if (g_whatdrive == TOSHIBA) {
    cmd[p_phase][0] = 0x28;
    cmd[p_phase][8] = g_buffers;
    Do_SCSI_Command (cmd[p_phase], 10, p_phase, FALSE, SCSIF_READ);
  }
}

#ifndef HRDBLBF
void Wait_CDROM_Read (void)
{
  Wait_CDROM_Command ();
}
#else
void Wait_CDROM_Read (short p_phase)
{
  Wait_CDROM_Command (p_phase);
}
#endif

void Update_Track_Data (short p_num)
{
  g_track_str[0] = '0' + (p_num >> 4);
  g_track_str[1] = '0' + (p_num & 15);
  GT_SetGadgetAttrs (g_gadgets[GID_TRACK], g_window, NULL,
		     GTTX_Text, g_track_str, TAG_END);
  g_track = p_num;
}

void Update_Index_Data (short p_num)
{
  g_index_str[0] = '0' + (p_num >> 4);
  g_index_str[1] = '0' + (p_num & 15);
  GT_SetGadgetAttrs (g_gadgets[GID_INDEX], g_window, NULL,
		     GTTX_Text, g_index_str, TAG_END);
  g_index = p_num;
}

void Update_Time_Data (short p_min, short p_sec)
{
  g_time_str[0] = '0' + (p_min >> 4);
  g_time_str[1] = '0' + (p_min & 15);
  g_time_str[3] = '0' + (p_sec >> 4);
  g_time_str[4] = '0' + (p_sec & 15);
  GT_SetGadgetAttrs (g_gadgets[GID_TIME], g_window, NULL,
		     GTTX_Text, g_time_str, TAG_END);
  g_minute = p_min;
  g_seconds = p_sec;
}

void Convert_Buffer (short p_phase)
{
  short i;
  short sum;
  BYTE *src = (BYTE *) g_cdda_buf[p_phase];
  BYTE *dst = (BYTE *) g_audio_buf[p_phase];
  BYTE *stop = src + (g_buffers * CDDA_BUFSIZE);
  short skip = ((CDDA_BUFSIZE-SUBCHANNEL_SIZE)/4);
  
  while (src < stop) {
    sum = 0;
    for (i=0; i<g_compression_factor; i++) {
      sum += src[1] + src[3];
      src += 4;
    }
    for (i=g_volume-1; i--;)  /* this is faster than multiplication */
      sum += sum;
    sum /= (g_compression_factor << 1);
    if (sum < -127)
      sum = -127;
    else if (sum > 127)
      sum = 127;
    *dst++ = sum;
    skip -= g_compression_factor;
    if (!skip) {
      /* analyze Q-sub channel data: */
      if (g_whatdrive == TOSHIBA) {
	if (src[1] == 1) {
	  if (((UBYTE *) src)[2] != g_track)
	    Update_Track_Data (((UBYTE *) src)[2]);
	  if (((UBYTE *) src)[3] != g_index)
	    Update_Index_Data (((UBYTE *) src)[3]);
	  if (((UBYTE *) src)[5] != g_seconds ||
	      ((UBYTE *) src)[4] != g_minute)
	    Update_Time_Data (((UBYTE *) src)[4], ((UBYTE *) src)[5]);
	}
      }
      else if (g_whatdrive == APPLECD300) {
	if (src[0] == 1) {
	  if (((UBYTE *) src)[1] != g_track)
	    Update_Track_Data (((UBYTE *) src)[1]);
	  if (((UBYTE *) src)[2] != g_index)
	    Update_Index_Data (((UBYTE *) src)[2]);
	  if (((UBYTE *) src)[8] != g_seconds ||
	      ((UBYTE *) src)[7] != g_minute)
	    Update_Time_Data (((UBYTE *) src)[7], ((UBYTE *) src)[8]);
	}
      }
      /* skip Q-sub channel data: */
      src += SUBCHANNEL_SIZE;
      skip = ((CDDA_BUFSIZE-SUBCHANNEL_SIZE)/4);
    }
  }
}

void Start_Play_Audio (short p_phase)
{
  BeginIO ((struct IORequest *) (g_audioreq[p_phase]));
}

void Wait_Play_Audio (short p_phase)
{
  ULONG sig;

  sig = Wait (SIGBREAKF_CTRL_C | g_audio_sigmask[p_phase]);
  if (sig & g_audio_sigmask[p_phase]) {
    while (GetMsg (g_audio_port[p_phase]) == 0) ;
  }
  if (sig & SIGBREAKF_CTRL_C)
    exit (1);
}

#define FORMAT_CDDA 0x82
#define FORMAT_STD 0x00

void Select_Block_Format (int p_format)
{
  static UBYTE cmd[6] = { 0x15, 0x10, 0, 0, 12, 0 };
  static UBYTE mode[12] = { 0, 0, 0, 8,
			    0, 0, 0, 0, 0, 0, 0, 0 };

  mode[4] = p_format;
  switch (p_format) {
  case FORMAT_CDDA:
    mode[10] = (CDDA_BUFSIZE >> 8);
    mode[11] = (CDDA_BUFSIZE & 0xFF);
    break;
  case FORMAT_STD:
    mode[10] = (STD_BUFSIZE >> 8);
    mode[11] = (STD_BUFSIZE & 0xFF);
    break;
  }

  memcpy (g_cdda_std_buf, mode, sizeof (mode));
  Do_SCSI_Command (cmd, 6, -1, TRUE, SCSIF_WRITE);
}

void Read_Drive_Type (void)
{
  static UBYTE cmd[6] = { 0x12, 0, 0, 0, 0, 0};
  static char *applecd300string = "SONY    CD-ROM CDU-8003";
  static char *toshibastring = "TOSHIBA";
  UBYTE *buf = g_cdda_std_buf;

#if STD_BUFSIZE > 255
  cmd[4] = 0xff;
#else
  cmd[4] = STD_BUFSIZE & 0xff;
#endif

  Do_SCSI_Command (cmd, 6, -1, TRUE, SCSIF_READ);

  if ((buf[0] & 0x1f) != 5)
    Fatal_Error ("not a CD-ROM device\n");

  if (!memcmp(applecd300string,
	      &buf[8],
	      strlen(applecd300string))) {
      g_whatdrive = APPLECD300;
    }
  else if (!memcmp(toshibastring,
		   &buf[8],
		   strlen(toshibastring))) {
      g_whatdrive = TOSHIBA;
    }
  else
    Fatal_Error ("unsupported CD-ROM drive\n");
}

void Read_TOC (void)
{
  static UBYTE cmd[10] = { 0x43, 0, 0, 0, 0, 0, 1,
  			   STD_BUFSIZE >> 8, STD_BUFSIZE & 0xFF,
			   0, };
  UBYTE *buf = g_cdda_std_buf;

  Do_SCSI_Command (cmd, 10, -1, TRUE, SCSIF_READ);
  g_toc_length = ((buf[0] << 8) + buf[1] - 2) / 8;
  memcpy (g_toc, buf + 4, 8 * g_toc_length);
}

void Enable_Track_Buttons (void)
{
  int i, foo;

  for (i=0; i<g_toc_length; i++) {
    /* ENFORCER HIT, because if () {statement} is executed, even
     * though expression evalutes to 0 with SAS/C 6.50.
     * (added foo to avoid problem <HR>)
     */
    foo = g_toc[i].track_number;
    if (foo <= 20 &&
        !(g_toc[i].flags & 4)) {
      GT_SetGadgetAttrs (g_gadgets[g_toc[i].track_number],
      			 g_window, NULL,
			 GA_Disabled, FALSE,
			 TAG_END);
    }
  }
}

long Track_Address (int p_track)
{
  int i;

  for (i=0; i<g_toc_length; i++) {
    if (g_toc[i].track_number == p_track) {
      if (g_toc[i].flags & 4)
	return -1;
      return g_toc[i].address;
    }
  }
  return -1;
}

int First_Track (void)
{
  int i;
  
  for (i=0; i<g_toc_length; i++) {
    if (g_toc[i].track_number != 0xAA &&
        !(g_toc[i].flags & 4))
      return g_toc[i].track_number;
  }
  return 0;
}

long Next_Track (int p_offset)
{
  int track = (g_track >> 4) * 10 + (g_track & 15);
  long res;

  track += p_offset;
  res = Track_Address (track);
  return res == -1 ? Track_Address (track - p_offset) : res;
}

void NTSC_or_PAL (void)
{
  if (GfxBase->DisplayFlags & PAL)
    g_period = 3546895 / (44100 / g_compression_factor);
  else
    g_period = 3579545 / (44100 / g_compression_factor);
}

void Init_Idle_Mode (void)
{
  GT_SetGadgetAttrs (g_gadgets[GID_STOP], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_START], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_SAMPLING_RATE], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_BUFFERS], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_PREV], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_NEXT], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_TRACK], g_window, NULL,
  		     GTTX_Text, "", TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_INDEX], g_window, NULL,
  		     GTTX_Text, "", TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_TIME], g_window, NULL,
  		     GTTX_Text, "", TAG_END);
}

void Init_Play_Mode (void)
{
  GT_SetGadgetAttrs (g_gadgets[GID_STOP], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_START], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_SAMPLING_RATE], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_BUFFERS], g_window, NULL,
		     GA_Disabled, TRUE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_PREV], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  GT_SetGadgetAttrs (g_gadgets[GID_NEXT], g_window, NULL,
		     GA_Disabled, FALSE, TAG_END);
  g_track = g_index = g_minute = g_seconds = 0xFF;
}

int Get_Intui_Message (long *p_sec_no)
{
  struct IntuiMessage *imsg;
  struct Gadget *gad;
  ULONG class;
  UWORD code;

  if (imsg = GT_GetIMsg (g_window->UserPort)) {
    class = imsg->Class;
    code = imsg->Code;
    gad = (struct Gadget *) imsg->IAddress;
    GT_ReplyIMsg (imsg);
    switch (class) {
    case IDCMP_CLOSEWINDOW: {
      g_bye = TRUE;
      return TRUE;
    }
    case IDCMP_GADGETUP:
      if (gad->GadgetID <= 20)
        *p_sec_no = Track_Address (gad->GadgetID);
      else if (gad->GadgetID == GID_STOP)
        return TRUE;
      else if (gad->GadgetID == GID_VOLUME)
        g_volume = code + 1;
      else if (gad->GadgetID == GID_PREV)
        *p_sec_no = Next_Track (-1);
      else if (gad->GadgetID == GID_NEXT)
        *p_sec_no = Next_Track (+1);
      break;
    default:
      break;
    }
  }
  return FALSE;
}

#define INC_SEC(ptr,inc) *(ptr) += (inc)

void main (int argc, char *argv[])
{
  short i=0;
  long sec_no;
  char *err;
  struct IntuiMessage *imsg;
  t_bool done;
  struct Gadget *gad;
  ULONG class;
  UWORD code;
  char *error_msg = NULL;

  atexit (Cleanup);

  g_called_from_cli = (argc > 0);

  if (g_called_from_cli) {
    static UBYTE* template = (UBYTE *)
                             "DEVICE/A,UNIT/A/N,CHIP/S,FAST/S,DMA/S,ANY/S,"
    			     "LOW/S,MEDIUM/S,HIGH/S";
    enum Arg {
      ARG_DEVICE,
      ARG_UNIT,
      ARG_CHIP,
      ARG_FAST,
      ARG_DMA,
      ARG_ANY,
      ARG_LOW,
      ARG_MEDIUM,
      ARG_HIGH,
      ARGCOUNT
    };
    static LONG args[ARGCOUNT];
    struct RDArgs* rd;
    if (rd = ReadArgs (template, args, NULL)) {
      int cnt_mem = 0;
      int cnt_vol = 0;
      strcpy (g_scsi_device, (char*) (args[ARG_DEVICE]));
      g_scsi_id = args[ARG_UNIT];
      if (args[ARG_CHIP])
        g_memory_type = MEMF_CHIP, cnt_mem++;
      if (args[ARG_FAST])
        g_memory_type = MEMF_FAST, cnt_mem++;
      if (args[ARG_DMA])
        g_memory_type = MEMF_24BITDMA, cnt_mem++;
      if (args[ARG_ANY])
        g_memory_type = MEMF_ANY, cnt_mem++;
      if (args[ARG_LOW])
        g_volume = 1, cnt_vol++;
      if (args[ARG_MEDIUM])
        g_volume = 2, cnt_vol++;
      if (args[ARG_HIGH])
        g_volume = 3, cnt_vol++;

      FreeArgs (rd);

      if (cnt_mem > 1)
        Fatal_Error ("Only ONE memory option may be used!");
      if (cnt_vol > 1)
        Fatal_Error ("Only ONE volume option may be used!");
    } else
      Fatal_Error ("Args do not match template %s\n", template);

    strcpy (g_scsi_device, argv[1]);
    g_scsi_id = atoi (argv[2]);
  } else {
    char *str;
    UBYTE **toolarray;
    struct WBStartup *wbench_msg;
    struct DiskObject *dobj;

    if (!(IconBase = OpenLibrary ((UBYTE *) "icon.library", 37)))
      exit (1);
    wbench_msg = (struct WBStartup *) argv;
    dobj = GetDiskObject ((UBYTE *) wbench_msg->sm_ArgList->wa_Name);
    if (!dobj)
      exit (1);
    toolarray = (UBYTE **) dobj->do_ToolTypes;
    str = (char *) FindToolType (toolarray, (UBYTE *) "DEVICE");
    if (!str)
      error_msg = "Tool type DEVICE is missing";
    strcpy (g_scsi_device, str);
    str = (char *) FindToolType (toolarray, (UBYTE *) "UNIT");
    if (!str)
      error_msg = "Tool type UNIT is missing";
    g_scsi_id = atoi (str);
    str = (char *) FindToolType (toolarray, (UBYTE *) "MEMORY");
    if (str) {
      if (strcmp (str, "CHIP") == 0)
        g_memory_type = MEMF_CHIP;
      else if (strcmp (str, "FAST") == 0)
        g_memory_type = MEMF_FAST;
      else if (strcmp (str, "DMA") == 0)
        g_memory_type = MEMF_24BITDMA;
      else if (strcmp (str, "ANY") == 0)
        g_memory_type = MEMF_ANY;
      else
        error_msg = "Invalid MEMORY tool type";
    }
    str = (char *) FindToolType (toolarray, (UBYTE *) "VOLUME");
    if (str) {
      if (strcmp (str, "LOW") == 0)
        g_volume = 1;
      else if (strcmp (str, "MEDIUM") == 0)
        g_volume = 2;
      else if (strcmp (str, "HIGH") == 0)
        g_volume = 3;
      else
        error_msg = "Invalid VOLUME tool type";
    };
    FreeDiskObject (dobj);
  }

  err = Open_User_Interface ();
  if (err)
    Fatal_Error ("ERROR: %s", err);

  if (error_msg)
    Fatal_Error ("%s", error_msg);

  Alloc_CDROM ();

  Read_Drive_Type ();

  if (g_whatdrive == TOSHIBA)
    Select_Block_Format (FORMAT_CDDA);

  Read_TOC ();

  Enable_Track_Buttons ();

  if (!First_Track ())
    Fatal_Error ("This is no audio disk");

  while (!g_bye) {

    Init_Idle_Mode ();

    done = FALSE;
    while (!done) {
      Wait (1 << g_window->UserPort->mp_SigBit);
      while (imsg = GT_GetIMsg (g_window->UserPort)) {
        class = imsg->Class;
	code = imsg->Code;
        gad = (struct Gadget *) imsg->IAddress;
	GT_ReplyIMsg (imsg);
        switch (class) {
	case IDCMP_CLOSEWINDOW:
          Select_Block_Format (FORMAT_STD);  
	  exit (0);
        case IDCMP_GADGETUP:
	  if (gad->GadgetID <= 20) {
            sec_no = Track_Address (gad->GadgetID);
	    done = TRUE;
	  } else if (gad->GadgetID == GID_START) {
	    sec_no = Track_Address (First_Track ());
	    done = TRUE;
	  } else if (gad->GadgetID == GID_VOLUME)
            g_volume = code + 1;
	  else if (gad->GadgetID == GID_SAMPLING_RATE) {
	    static short factors[] = { 2, 3, 4, 6, 7 };
	    g_compression_factor = factors[code];
	  } else if (gad->GadgetID == GID_BUFFERS) {
	    static short buffers[] = { 2, 4, 8, 16, 32, 64 };
	    g_buffers = buffers[code];
	  }
          break;
        default:
          break;
        }
      }
    }

    Init_Play_Mode ();

    NTSC_or_PAL ();
    Alloc_Audio ();

    Start_CDROM_Read (0, sec_no);
#ifndef HRDBLBF
    Wait_CDROM_Read ();
#else
    Wait_CDROM_Read (0);
#endif

    INC_SEC (&sec_no, g_buffers);
    Start_CDROM_Read (1, sec_no);
    Convert_Buffer (0);
#ifndef HRDBLBF
    Wait_CDROM_Read ();
#else
    Wait_CDROM_Read (1);
#endif
    Convert_Buffer (1);
    Start_Play_Audio (0);
    Start_Play_Audio (1);
    INC_SEC (&sec_no, g_buffers);
    Start_CDROM_Read (0, sec_no);

    for (;;) {
      /* HERE: scsi-request i is active,
               play-audio i is active and
	       play-audio 1-i is queued */

      if (Get_Intui_Message (&sec_no))
        break;
#ifndef HRDBLBF
      Wait_CDROM_Read ();
#else
      Wait_CDROM_Read (i);
#endif
      INC_SEC (&sec_no, g_buffers);
      Start_CDROM_Read (1-i, sec_no);
      Wait_Play_Audio (i);
      Convert_Buffer (i);
      Start_Play_Audio (i);
      i = 1-i;
    }
    
#ifndef HRDBLBF
    Wait_CDROM_Read ();
#else
    Wait_CDROM_Read (0);
    Wait_CDROM_Read (1);
#endif
    Wait_Play_Audio (i);
    Wait_Play_Audio (1-i);
    
    Cleanup_Audio ();
  }

  if (g_whatdrive == TOSHIBA)
    Select_Block_Format (FORMAT_STD);

  exit (0);
}

