/*                          CDROM AUDIO ROUTINES
			      By Barry Egerter

			    Written July 18, 1994

			    Using Borland C++ 3.1

		   Code : FREEWARE - alter and use at will.


		Internet Email:      barry.egerter@softnet.com
*/
#include <dos.h>
#include <io.h>
#include <mem.h>
#include <fcntl.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 long  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 long	 endofdisk;
  unsigned char  upc[7];
  unsigned char  diskid[6];
  unsigned long  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

INT 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",INT 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 union REGS inregs, outregs;
static struct SREGS sregs;


void device_request (void *block)
{
  inregs.x.ax = 0x1510;
  inregs.x.cx = cdrom_data.first_drive;
  inregs.x.bx = FP_OFF (block);
  sregs.es = FP_SEG (block);
  int86x (0x2f, &inregs, &outregs, &sregs);
}


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


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

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


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

  struct {
    unsigned char mode;
    unsigned char adr_mode;
    unsigned long 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 = (unsigned long)&head_data;
  tray_request.bytes = 6;
  head_data.mode = 0x01;
  head_data.adr_mode = 0x00;
  device_request (&tray_request);
  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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  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 = (unsigned long)vol;
  tray_request.bytes = 9;
  vol->mode = 0x04;
  device_request (&tray_request);
  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 long 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 = (unsigned long)vol;
  cd_request.bytes = 9;
  device_request (&cd_request);
  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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  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 = (unsigned long)&upc_data;
  tray_request.bytes = 11;
  upc_data.mode = 0x0e;
  upc_data.adr = 2;
  device_request (&tray_request);
  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;
    long address;
    short bytes;
    short sector;
    long volid;
  } ioctli;

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

  ioctli.length = sizeof (ioctli);
  ioctli.subunit = 0;
  ioctli.comcode = 3;
  ioctli.media = 0;
  ioctli.sector = 0;
  ioctli.volid = 0;
  ioctli.address = (long)&track_data;
  ioctli.bytes = sizeof (track_data);
  track_data.mode = 0x0a;
  device_request (&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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned char track;
    unsigned long 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 = (unsigned long)&track_data;
  tray_request.bytes = 7;
  track_data.mode = 0x0b;
  track_data.track = tracknum;
  device_request (&tray_request);
  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 long get_track_length (short tracknum)
{
  unsigned long 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 long 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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  volid;
  } tray_request;

  struct {
    unsigned char mode;
    unsigned long 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 = (unsigned long)&cd_data;
  tray_request.bytes = 5;

  cd_data.mode = 0x06;
  device_request (&tray_request);
  cdrom_data.status = cd_data.status;
  cdrom_data.error = tray_request.status;
}


void cd_seek (unsigned long 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 long transfer;
    unsigned short sectors;
    unsigned long 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;
  device_request (&play_request);
  cdrom_data.error = play_request.status;
}


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

  struct {
    unsigned char length;
    unsigned char subunit;
    unsigned char comcode;
    unsigned short status;
    char ununsed[8];
    unsigned char addressmode;
    unsigned long start;
    unsigned long 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;
  device_request (&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;
  device_request (&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;
  device_request (&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 long 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 = (unsigned long)&cd_mode;
  tray_request.bytes = 1;
  device_request (&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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  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 = (unsigned long)info;
  tray_request.bytes = 6;
  info->control = 12;
  device_request (&tray_request);
  cdrom_data.error = tray_request.status;
}


short cdrom_installed (void)
{
  inregs.h.ah = 0x15;
  inregs.h.al = 0x00;
  inregs.x.bx = 0;
  int86 (0x2f, &inregs, &outregs);
  if (outregs.x.bx == 0)
    return (0);
  cdrom_data.drives = outregs.x.bx;
  cdrom_data.first_drive = outregs.x.cx;
  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 long address;
    unsigned short bytes;
    unsigned short sector;
    unsigned long  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 = (unsigned long)&cd_data;
  tray_request.bytes = 2;

  cd_data.mode = 0x09;
  device_request (&tray_request);
  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 long 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 = (unsigned long)&cd_data;
  tray_request.bytes = 2;
  cd_data.mode = 1;
  cd_data.media = doormode;
  device_request (&tray_request);
  cdrom_data.error = tray_request.status;
}
