/*                          CDROM AUDIO ROUTINES
			      By Barry Egerter

			 Written September 24, 1994

			  Using Watcom C/C++ 10.0

		  Code : FREEWARE - alter and use at will.


	       Internet Email:      barry.egerter@softnet.com
*/
#include <dos.h>
#include <io.h>
#include <mem.h>

#define CDROM 0x21
#define EJECT_TRAY 0
#define RESET 2
#define CLOSE_TRAY 5
#define MEDIA_CHANGE 9
#define BUSY  512
#define TRACK_MASK 208

typedef struct playinfo {
  unsigned char control;
  unsigned char adr;
  unsigned char track;
  unsigned char index;
  unsigned char min;
  unsigned char sec;
  unsigned char frame;
  unsigned char zero;
  unsigned char amin;
  unsigned char asec;
  unsigned char aframe;
};


typedef struct volumeinfo {
    unsigned char mode;
    unsigned char input0;
    unsigned char volume0;
    unsigned char input1;
    unsigned char volume1;
    unsigned char input2;
    unsigned char volume2;
    unsigned char input3;
    unsigned char volume3;
};


struct {
  unsigned short drives;
  unsigned char  first_drive;
  unsigned short current_track;
  unsigned int   track_position;
  unsigned char  track_type;
  unsigned char  low_audio;
  unsigned char  high_audio;
  unsigned char  disk_length_min;
  unsigned char  disk_length_sec;
  unsigned char  disk_length_frames;
  unsigned int   endofdisk;
  unsigned char  upc[7];
  unsigned char  diskid[6];
  unsigned int   status;
  unsigned short error;      /* See description below */
} cdrom_data;

/* CDROM_DATA.ERROR Description

  Bit 15         - Error bit
  Bit 14-10      - Reserved
  Bit  9         - Busy
  Bit  8         - Done
  Bit  7-0       - Error code (bit 15 on)

Error codes are the following:

  0  Write-protect violation
  1  Unknown unit
  2  Drive not ready
  3  Unknown command
  4  CRC error
  5  Bad drive request structure length
  6  Seek error
  7  Unknown media
  8  Sector not found
  9  Printer out of paper
  A  Write fault
  B  Read fault
  C  General failure
  D  Reserved
  E  Reserved
  F  Invalid disk change
*/


/*   Multiplex Interrupt routines
     "borrowed" from Ralf Brown's MSDOS Interrupt List v4.1

short 21 - CD-ROM device driver - IOCTL INPUT
	AX = 4402h
	BX = file handle referencing character device for CD-ROM driver
	CX = number of bytes to read
	DS:DX -> control block (see #0563)
Return: CF clear if successful
	    AX = number of bytes actually read
	CF set on error
	    AX = error code (01h,05h,06h,0Dh) (see #0770 at AH=59h)
Note:   the data returned depends on the first byte of the control block; the
	  remainder of the control block is filled by the driver
SeeAlso: AX=4403h"CD-ROM",short 2F/AX=0802h

(Table 0562)
Values for CD-ROM data being requested:
 00h    device driver header address
 01h    drive head location
 02h    reserved
 03h    error statistics
 04h    audio channel info
 05h    raw drive bytes (uninterpreted and device-specific)
 06h    device status
 07h    sector size
 08h    volume size
 09h    media change status
 0Ah    audio disk info
 0Bh    audio track info
 0Ch    audio Q-Channel info
 0Dh    audio sub-channel info
 0Eh    UPC code

Format of CD-ROM control block:
Offset  Size    Description     (Table 0563)
 00h    BYTE    data being requested (see #0562)
---function 00h---
 01h    DWORD   device driver header address (see also AH=52h)
---function 01h---
 01h    BYTE    addressing mode
		00h HSG
		01h Red Book
 02h    DWORD   current location of drive's head
		logical sector number in HSG mode
		frame/second/minute/unused in Red Book mode
		(HSG sector = minute * 4500 + second * 75 + frame - 150)
---function 03h---
 01h  N BYTEs   undefined as of 5 Aug 88 specification
---function 04h---
 01h    BYTE    input channel (0-3) for output channel 0
 02h    BYTE    volume for output channel 0
 03h    BYTE    input channel (0-3) for output channel 1
 04h    BYTE    volume for output channel 1
 05h    BYTE    input channel (0-3) for output channel 2
 06h    BYTE    volume for output channel 2
 07h    BYTE    input channel (0-3) for output channel 3
 08h    BYTE    volume for output channel 3
Notes:  output channels 0 and 1 are left and right, 2 and 3 are left prime and
	  right prime; a volume of 00h is off
	the default setting is for each input channel to be assigned to the
	  same-numbered output channel at full (FFh) volume
---function 05h---
 01h    BYTE    number of bytes read
 02h 128 BYTEs  buffer for drive bytes
---function 06h---
 01h    DWORD   device parameters (see #0564)
---function 07h---
 01h    BYTE    read mode
		00h cooked
		01h raw
 02h    WORD    sector size in bytes
---function 08h---
 01h    DWORD   volume size in sectors
---function 09h---
 01h    BYTE    media change status
		00h don't know
		01h media unchanged
		FFh media has been changed
---function 0Ah---
 01h    BYTE    lowest audio track number
 02h    BYTE    highest audio track number
 03h    DWORD   start address of lead-out track (Red Book format)
--function 0Bh---
 01h    BYTE    track number (set by caller)
 02h    DWORD   starting point of track (Red Book format)
 06h    BYTE    track control info
		bits 15,14,12: track type (notice: bits not contiguous!)
			000 two audio channels, no pre-emphasis
			001 two audio channels with pre-emphasis
			010 data track
			100 four audio channels, no pre-emphasis
			101 four audio channels with pre-emphasis
			other reserved
		bit 13: digital copy permitted
---function 0Ch---
 01h    BYTE    CONTROL and ADR byte (as received from drive)
 02h    BYTE    track number
 03h    BYTE    point or index
 04h    BYTE    minute  \
 05h    BYTE    second   > running time within track
 06h    BYTE    frame   /
 07h    BYTE    zero
 08h    BYTE    "AMIN" or "PMIN"     \
 09h    BYTE    "ASEC" or "PSEC"      > running time on disk
 0Ah    BYTE    "AFRAME" or "PFRAME" /
---function 0Dh---
 01h    DWORD   starting frame address (Red Book format)
 05h    DWORD   transfer address
 09h    DWORD   number of sectors to read
Note:   copies 96 bytes of sub-channel info per sector into buffer
---function 0Eh---
 01h    BYTE    CONTROL and ADR byte
 02h  7 BYTEs   UPC/EAN code (13 BCD digits,low-order nybble of last byte is 0)
 09h    BYTE    zero
 0Ah    BYTE    "AFRAME"

Bitfields for CD-ROM device parameters:
Bit(s)  Description     (Table 0564)
 0      door open
 1      door unlocked
 2      supports raw reading in addition to cooked
 3      writable
 4      can play audio/video tracks
 5      supports interleaving
 6      reserved
 7      supports prefetch requests
 8      supports audio channel control
 9      supports Red Book addressing in addition to HSG
 10     audio is playing
*/
static struct rminfo {
	long EDI;
	long ESI;
	long EBP;
	long reserved_by_system;
	long EBX;
	long EDX;
	long ECX;
	long EAX;
	short flags;
	short ES,DS,FS,GS,IP,CS,SP,SS;
} RMI;

static union REGS inregs, outregs;
static struct SREGS sregs;
static short lowptr;
static int lowptr2;
static void *watptr, *watptr2;

void allocbuffers (void)
{
  memset (&sregs, 0, sizeof(sregs));
  inregs.w.ax = 0x0100;
  inregs.w.bx = 0x20;
  int386x (0x31, &inregs, &outregs, &sregs);
  lowptr = outregs.w.ax;
  watptr = (void *)((outregs.x.eax & 0xffff) << 4);
  int386x (0x31, &inregs, &outregs, &sregs);
  lowptr2 = outregs.w.ax << 16;
  watptr2 = (void *)((outregs.x.eax & 0xffff) << 4);
}


void device_request ()
{
  memset (&sregs, 0, sizeof (sregs));
  memset (&RMI, 0, sizeof(RMI));
  RMI.EAX = 0x00001510;
  RMI.ECX = cdrom_data.first_drive;
  RMI.EBX = 0;
  RMI.ES = lowptr;
  
  inregs.w.ax = 0x0300;
  inregs.h.bl = 0x2f;
  inregs.h.bh = 0;
  inregs.w.cx = 0;
  sregs.es = FP_SEG (&RMI);
  inregs.x.edi = FP_OFF (&RMI);
  int386x (0x31, &inregs, &outregs, &sregs);
  if (outregs.x.cflag)
    printf ("DEVICE REQUEST FAILED!!!\n");
}


void red_book (unsigned int value, unsigned char *min, unsigned char *sec, unsigned char *frame)
{
  *frame = value & 0x000000ff;
  *sec = (value & 0x0000ff00) >> 8;
  *min = (value & 0x00ff0000) >> 16;
}


unsigned int hsg (unsigned int value)
{
  unsigned char min, sec, frame;

  red_book (value, &min, &sec, &frame);
  value = (unsigned int)min * 4500;
  value += (short)sec * 75;
  value += frame;
  value -= 150;
  return value;
}


unsigned int cd_head_position (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
    unsigned char unused[4];
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char adr_mode;
    unsigned int address;
  } head_data;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 6;
  head_data.mode = 0x01;
  head_data.adr_mode = 0x00;
  memcpy (watptr, &tray_request, tray_request.length);
  memcpy (watptr2, &head_data, 6);
  device_request ();
  memcpy (&tray_request, watptr, tray_request.length);
  memcpy (&head_data, watptr2, 6);
  cdrom_data.error = tray_request.status;
  return head_data.address;
}


void cd_get_volume (struct volumeinfo *vol)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 9;
  vol->mode = 0x04;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, vol, sizeof (struct volumeinfo));
  device_request ();
  memcpy (&tray_request, watptr, tray_request.length);
  memcpy (vol, watptr2, sizeof (struct volumeinfo));
  cdrom_data.error = tray_request.status;
}


void cd_set_volume (struct volumeinfo *vol)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned char unused[4];
  } cd_request;

  vol->mode = 3;
  cd_request.length = sizeof (cd_request);
  cd_request.subunit = 0;
  cd_request.comcode = 12;
  cd_request.media = 0;
  cd_request.address = lowptr2;
  cd_request.bytes = 9;
  memcpy (watptr, &cd_request, sizeof (cd_request));
  memcpy (watptr2, vol, sizeof (struct volumeinfo));
  device_request ();
  memcpy (&cd_request, watptr, cd_request.length);
  cdrom_data.error = cd_request.status;
}


short cd_getupc (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char adr;
    unsigned char upc[7];
    unsigned char zero;
    unsigned char aframe;
  } upc_data;
  
  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 11;
  upc_data.mode = 0x0e;
  upc_data.adr = 2;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &upc_data, 11);
  device_request ();
  memcpy (&upc_data, watptr2, 11);
  memcpy (&tray_request, watptr, tray_request.length);
  cdrom_data.error = tray_request.status;
  if (upc_data.adr == 0)
    memset (&upc_data.upc, 0, 7);
  memcpy (&cdrom_data.upc[0], &upc_data.upc[0], 7);
  return 1;
}


void cd_get_audio_info (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    int address;
    short bytes;
    short sector;
    int volid;
  } ioctli;

  struct {
    unsigned char mode;
    unsigned char lowest;
    unsigned char highest;
    unsigned int address;
  } track_data;

  ioctli.length = sizeof (ioctli);
  ioctli.subunit = 0;
  ioctli.comcode = 3;
  ioctli.media = 0;
  ioctli.sector = 0;
  ioctli.volid = 0;
  ioctli.address = (unsigned int)lowptr2;
  ioctli.bytes = 7;
  memset (&track_data, 0, 7);
  track_data.mode = 0x0a;
  memcpy (watptr, &ioctli, sizeof (ioctli));
  memcpy (watptr2, &track_data.mode, 7);
  memcpy (&track_data, watptr2, sizeof (track_data));
  device_request ();
  memcpy (&track_data, watptr2, sizeof (track_data));
  memcpy (&ioctli, watptr, sizeof (ioctli));
  memcpy (&cdrom_data.diskid, &track_data.lowest, 6);
  cdrom_data.low_audio = track_data.lowest;
  cdrom_data.high_audio = track_data.highest;
  red_book (track_data.address, &cdrom_data.disk_length_min, &cdrom_data.disk_length_sec, &cdrom_data.disk_length_frames);
  cdrom_data.endofdisk = hsg (track_data.address);
  cdrom_data.error = ioctli.status;
}


void cd_set_track (short tracknum)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char track;
    unsigned int address;
    unsigned char control;
  } track_data;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 7;
  track_data.mode = 0x0b;
  track_data.track = tracknum;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &track_data, sizeof (track_data));
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  memcpy (&track_data, watptr2, sizeof (track_data));
  cdrom_data.error = tray_request.status;
  cdrom_data.track_position = hsg (track_data.address);
  cdrom_data.current_track = tracknum;
  cdrom_data.track_type = track_data.control & TRACK_MASK;
}


unsigned int get_track_length (short tracknum)
{
  unsigned int start, finish;
  unsigned short ct;

  ct = cdrom_data.current_track;
  cd_set_track (tracknum);
  start = cdrom_data.track_position;
  if (tracknum < cdrom_data.high_audio)
  {
    cd_set_track (tracknum+1);
    finish = cdrom_data.track_position;
  }
  else finish = cdrom_data.endofdisk;

  cd_set_track (ct);

  finish -= start;
  return finish;
}


void cd_track_length (short tracknum, unsigned char *min, unsigned char *sec, unsigned char *frame)
{
  unsigned int value;

  value = get_track_length (tracknum);
  value -= 150;
  *frame = value % 75;
  value -= *frame;
  value /= 75;
  *sec = value % 60;
  value -= *sec;
  value /= 60;
  *min = value;
}


void cd_status (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned int status;
  } cd_data;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 5;

  cd_data.mode = 0x06;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &cd_data, sizeof (cd_data));
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  memcpy (&cd_data, watptr2, sizeof (cd_data));
  cdrom_data.status = cd_data.status;
  cdrom_data.error = tray_request.status;
}


void cd_seek (unsigned int location)
{
  unsigned char min, sec, frame;
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char addressmode;
    unsigned int transfer;
    unsigned short sectors;
    unsigned int seekpos;
  } play_request;

  play_request.length = sizeof (play_request);
  play_request.subunit = 0;
  play_request.comcode = 131;
  play_request.addressmode = 0;
  play_request.transfer = 0;
  play_request.sectors = 0;
  play_request.seekpos = location;
  memcpy (watptr, &play_request, sizeof (play_request));
  device_request ();
  memcpy (&play_request, watptr, sizeof (play_request));
  cdrom_data.error = play_request.status;
}


void cd_play_audio (unsigned int begin, unsigned int end)
{
  unsigned int leng;

  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char addressmode;
    unsigned int start;
    unsigned int playlength;
  } play_request;

  play_request.length = sizeof (play_request);
  play_request.subunit = 0;
  play_request.comcode = 132;
  play_request.addressmode = 0;
  play_request.start = begin;
  play_request.playlength = end-begin;
  memcpy (watptr, &play_request, sizeof (play_request));
  device_request ();
  memcpy (&play_request, watptr, sizeof (play_request));
  cdrom_data.error = play_request.status;
}


void cd_stop_audio (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
  } stop_request;

  stop_request.length = sizeof (stop_request);
  stop_request.subunit = 0;
  stop_request.comcode = 133;
  memcpy (watptr, &stop_request, sizeof (stop_request));
  device_request ();
  memcpy (&stop_request, watptr, sizeof (stop_request));
  cdrom_data.error = stop_request.status;
}


void cd_resume_audio (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
  } stop_request;

  stop_request.length = sizeof (stop_request);
  stop_request.subunit = 0;
  stop_request.comcode = 136;
  memcpy (watptr, &stop_request, sizeof (stop_request));
  device_request ();
  memcpy (&stop_request, watptr, sizeof (stop_request));
  cdrom_data.error = stop_request.status;
}


void cd_cmd (unsigned char mode)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned char unused[4];
  } tray_request;

  unsigned char cd_mode;

  cd_mode = mode;
  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 12;
  tray_request.media = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 1;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &cd_mode, 1);
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  cdrom_data.error = tray_request.status;
}


void cd_getpos (struct playinfo *info)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 6;
  info->control = 12;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, info, sizeof (struct playinfo));
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  memcpy (info, watptr2, sizeof (struct playinfo));
  cdrom_data.error = tray_request.status;
}


short cdrom_installed (void)
{
  inregs.h.ah = 0x15;
  inregs.h.al = 0x00;
  inregs.w.bx = 0;
  int386 (0x2f, &inregs, &outregs);
  if (outregs.w.bx == 0)
    return (0);
  cdrom_data.drives = outregs.w.bx;
  cdrom_data.first_drive = outregs.w.cx;
  if (lowptr == 0)
    allocbuffers ();
  cd_get_audio_info ();
  return (1);
}


short cd_done_play (void)
{
  cd_cmd (CLOSE_TRAY);
  return ((cdrom_data.error & BUSY) == 0);
}


short cd_mediach (void)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned short sector;
    unsigned int  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char media;
  } cd_data;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 3;
  tray_request.media = 0;
  tray_request.media = tray_request.sector = tray_request.volid = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 2;

  cd_data.mode = 0x09;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &cd_data, sizeof (cd_data));
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  memcpy (&cd_data, watptr2, sizeof (cd_data));
  cdrom_data.error = tray_request.status;
  return cd_data.media;
}


void cd_lock (unsigned char doormode)
{
  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char media;
    unsigned int address;
    unsigned short bytes;
    unsigned char unused[4];
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char media;
  } cd_data;

  tray_request.length = sizeof (tray_request);
  tray_request.subunit = 0;
  tray_request.comcode = 12;
  tray_request.media = 0;
  tray_request.address = lowptr2;
  tray_request.bytes = 2;
  cd_data.mode = 1;
  cd_data.media = doormode;
  memcpy (watptr, &tray_request, sizeof (tray_request));
  memcpy (watptr2, &cd_data, sizeof (cd_data));
  device_request ();
  memcpy (&tray_request, watptr, sizeof (tray_request));
  cdrom_data.error = tray_request.status;
}
