/* Mednafen - Multi-system Emulator
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "mednafen.h"
#include "scsicd.h"
#include "cdromif.h"

static uint32 CD_DATA_TRANSFER_RATE;
static uint32 SYSTEM_CLOCK;
static int32 CDDADivAcc;
static void (*CDIRQCallback)(int);
static void (*CDStuffSubchannels)(uint8);
static Blip_Buffer *sbuf[2];
static int WhichSystem;

typedef struct
{
 uint8 DB;			// Data bus
 bool BSY, kingBSY, kingACK, kingRST, MSG, kingMSG, kingSEL, CD, kingCD, REQ, IO, kingIO, kingATN;
 bool last_RST_signal;

 uint8 message_pending;
 bool status_sent, message_sent;

 uint8 key_pending, asc_pending, ascq_pending, fru_pending;

 bool need_message_after_status;

 bool device_selected;

 uint8 command_buffer[256];
 uint8 command_buffer_pos;
 uint8 command_size_left;

 // To initiator(the king chip)
 uint8 data_in[8192];
 uint32 data_in_pos;
 uint32 data_in_size;
 bool data_transfer_done;	// FALSE if not all pending data is in data_in, TRUE if it is(used for multiple sector CD reads)


 // To target(the cd unit), for "MODE SELECT" mainly
 uint8 data_out[8192];
 uint32 data_out_pos;
 uint32 data_out_size;

 uint32 lastts;

 bool8 IsInserted;


 uint8 SubQBuf[0xC];
 uint8 SubRWBuf[96];

} scsicd_t;

void MakeSense(uint8 * target, uint8 key, uint8 asc, uint8 ascq, uint8 fru)
{
 memset(target, 0, 18);

 target[0] = 0x70;		// Current errors and sense data is not SCSI compliant
 target[2] = key;
 target[12] = asc;		// Additional Sense Code
 target[13] = ascq;		// Additional Sense Code Qualifier
 target[14] = fru;		// Field Replaceable Unit code
}

scsicd_t cd;

typedef Blip_Synth < blip_good_quality, 65536 > CDSynth;

static uint8 PlayMode;
static CDSynth CDDASynth[2];
static int16 last_sample[2];
static int16 CDDASectorBuffer[1176];
static uint32 CDDAReadPos;
static int8 CDDAStatus;		// 1 = playing, 0 = stopped, -1 = paused
static int32 CDDADiv;
static int CDDATimeDiv;

static uint32 read_sec_start;
static uint32 read_sec;
static uint32 read_sec_end;

static int32 CDReadTimer;
static uint32 SectorAddr;
static uint32 SectorCount;

static bool PhaseChange = 0;
static uint32 next_time;

#define BSY_signal (cd.BSY | cd.kingBSY)
#define ACK_signal (cd.kingACK)
#define RST_signal (cd.kingRST)
#define MSG_signal (cd.MSG | cd.kingMSG)
#define SEL_signal (cd.kingSEL)
#define REQ_signal (cd.REQ)
#define IO_signal (cd.IO | cd.kingIO)
#define CD_signal (cd.CD | cd.kingCD)

#define ATN_signal (cd.kingATN)

void SCSICD_Power(void)
{
 memset(&cd, 0, sizeof(scsicd_t));

 cd.IsInserted = TRUE;

 CDDADivAcc = (int64)SYSTEM_CLOCK * 65536 / 44100;
 CDReadTimer = 0;

 SectorAddr = SectorCount = 0;
 read_sec_start = read_sec = 0;
 read_sec_end = ~0;

 PlayMode = 0;
 CDDAReadPos = 0;
 CDDAStatus = 0;
 CDDADiv = 0;
}


void SCSICD_SetDB(uint8 data)
{
 cd.DB = data;
}

uint8 SCSICD_GetDB(void)
{
 return (cd.DB);
}

void SCSICD_SetACK(bool set)
{
 cd.kingACK = set;
}

void SCSICD_SetMSG(bool set)
{
 //cd.kingMSG = set;
}

void SCSICD_SetREQ(bool set)
{
 cd.REQ = set;
}

void SCSICD_SetSEL(bool set)
{
 cd.kingSEL = set;
}

void SCSICD_SetRST(bool set)
{
 cd.kingRST = set;
}

void SCSICD_SetBSY(bool set)
{
 cd.kingBSY = set;
}

void SCSICD_SetCD(bool set)
{
 //cd.kingCD = set;
}

void SCSICD_SetIO(bool set)
{
 //cd.kingIO = set;
}

void SCSICD_SetATN(bool set)
{
 cd.kingATN = set;
}

bool SCSICD_GetBSY(void)
{
 return (BSY_signal);
}

bool SCSICD_GetACK(void)
{
 return (ACK_signal);
}

bool SCSICD_GetRST(void)
{
 return (RST_signal);
}

bool SCSICD_GetMSG(void)
{
 return (MSG_signal);
}

bool SCSICD_GetSEL(void)
{
 return (SEL_signal);
}

bool SCSICD_GetCD(void)
{
 return (CD_signal);
}

bool SCSICD_GetREQ(void)
{
 return (REQ_signal);
}

bool SCSICD_GetIO(void)
{
 return (IO_signal);
}

bool SCSICD_GetATN(void)
{
 return (ATN_signal);
}


#define STATUS_GOOD		0
#define STATUS_CHECK_CONDITION	1
#define STATUS_CONDITION_MET	2
#define STATUS_BUSY		4
#define STATUS_INTERMEDIATE	8

#define SENSEKEY_NO_SENSE		0
#define SENSEKEY_RECOVERED_ERROR	1
#define SENSEKEY_NOT_READY		2
#define SENSEKEY_MEDIUM_ERROR		3
#define SENSEKEY_HARDWARE_ERROR		4
#define SENSEKEY_ILLEGAL_REQUEST	5
#define SENSEKEY_UNIT_ATTENTION		6
#define SENSEKEY_DATA_PROTECT		7

#define ASC_MEDIUM_NOT_PRESENT		0x3A
#define ASC_INVALID_command_OPERATION_CODE   0x20
#define ASC_INVALID_FIELD_IN_CDB	0x24

static uint8 CDIFFormat_To_DMF(CDIF_Track_Format format)
{
 switch (format)
 {
  default:
   return (0xFF);
  case CDIF_FORMAT_AUDIO:
   return (0x00);
  case CDIF_FORMAT_MODE1:
   return (0x01);
  case CDIF_FORMAT_MODE2:
   return (0x02);
 }
}

static uint8 CDIFFORMAT_To_QSCF(CDIF_Track_Format format)	// Q sub-channel control field
{
 switch (format)
 {
  case CDIF_FORMAT_AUDIO:
   return (0x00);
  default:
   return (0x04);
 }
}

static void SendStatusAndMessage(uint8 status, uint8 message)
{
 cd.CD = TRUE;
 cd.MSG = FALSE;
 cd.IO = TRUE;
 cd.REQ = TRUE;

 cd.message_pending = message;
 cd.need_message_after_status = TRUE;

 cd.status_sent = FALSE;
 cd.message_sent = FALSE;

 if(WhichSystem == SCSICD_PCE)
 {
  if(status == STATUS_GOOD || status == STATUS_CONDITION_MET)
   cd.DB = 0x00;
  else
   cd.DB = 0x01;
 }
 else
  cd.DB = status << 1;

 PhaseChange = TRUE;
}

bool SCSICD_IsInserted(void)
{
 return (cd.IsInserted);
}

bool SCSICD_EjectVirtual(void)
{
 if(cd.IsInserted)
 {
  cd.IsInserted = 0;
  return (1);
 }
 return (0);
}

bool SCSICD_InsertVirtual(void)
{
 if(!cd.IsInserted)
 {
  cd.IsInserted = 1;
  return (1);
 }
 return (0);
}


static void lba2msf(uint32 lba, uint8 * m, uint8 * s, uint8 * f)
{
 *m = lba / 75 / 60;
 *s = (lba - *m * 75 * 60) / 75;
 *f = lba - (*m * 75 * 60) - (*s * 75);
}

static void DoMODESELECT6(void)
{
 if(cd.command_buffer[4])
 {
  cd.data_out_pos = 0;
  cd.data_out_size = cd.command_buffer[4];
  //printf("Switch to DATA OUT phase, len: %d\n", cd.data_out_size);
  cd.CD = FALSE;
  cd.IO = FALSE;
  cd.MSG = FALSE;
  cd.REQ = TRUE;
  PhaseChange = TRUE;
 }
 else
  SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoMODESENSE6(void)
{
 unsigned int PC = (cd.command_buffer[2] >> 6) & 0x3;
 unsigned int PageCode = cd.command_buffer[2] & 0x3F;
 bool DBD = cd.command_buffer[1] & 0x08;
 int AllocSize = cd.command_buffer[4];

 printf("Mode sense 6: %02x %d %d %d\n", PageCode, PC, DBD, AllocSize);

 switch (PageCode)
 {
  default:
   exit(1);
  case 0x0E:			// Audio control page
   if(AllocSize > 16)
    AllocSize = 16;
   memset(cd.data_in, 0, 16);

   cd.data_in[0] = 0x0E;	// Page code
   cd.data_in[1] = 0x0E;	// Page length(kinky coincidence!)
   cd.data_in[2] = 0x00;

   //cd.data_in[5] = 0x80
   cd.data_in[8] = 0x01;	// Output port 0 channel selection
   cd.data_in[9] = 0xFF;	// Outport port 0 volume
   cd.data_in[10] = 0x02;	// Output port 1 channel selection
   cd.data_in[11] = 0xFF;	// Outport port 1 volume
   break;
 }
 cd.data_in_size = AllocSize;
 cd.data_in_pos = 0;
 cd.data_transfer_done = TRUE;
 cd.IO = TRUE;
 cd.CD = FALSE;
 PhaseChange = TRUE;

 //exit(1);
}

static void DoSTARTSTOPUNIT6(void)
{
 bool Immed = cd.command_buffer[1] & 0x01;
 bool LoEj = cd.command_buffer[4] & 0x02;
 bool Start = cd.command_buffer[4] & 0x01;

 //printf("Do start stop unit 6: %d %d %d\n", Immed, LoEj, Start);
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoREZEROUNIT(void)
{
 printf("Rezero Unit: %02x\n", cd.command_buffer[5]);
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static const uint8 InqData[] = {
 0x05, 0x80, 0x02, 0x00,	// Standard data
 0x1F, 0x00, 0x00, 0x00,	// Additional length
 0x4E, 0x45, 0x43, 0x20, 0x20, 0x20, 0x20, 0x20,
 0x43, 0x44, 0x2D, 0x52, 0x4F, 0x4D, 0x20, 0x44,
 0x52, 0x49, 0x56, 0x45, 0x3A, 0x46, 0x58, 0x20,
 0x31, 0x2E, 0x30, 0x45, 0x0E, 0x0E, 0x04
};

static void DoINQUIRY(void)
{
 unsigned int AllocSize = cd.command_buffer[4];

 if(!AllocSize)
 {
  cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
  cd.asc_pending = 0x20;
  cd.ascq_pending = 0x00;
  cd.fru_pending = 0x00;
  SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
  return;
 }

 printf("Inquiry: %02x %02x %02x %02x %02x\n", cd.command_buffer[1], cd.command_buffer[2], cd.command_buffer[3], cd.command_buffer[4], cd.command_buffer[5]);
 memcpy(cd.data_in, InqData, sizeof(InqData));
 cd.data_in_pos = 0;
 cd.data_in_size = (AllocSize > sizeof(InqData)) ? sizeof(InqData) : AllocSize;
 cd.IO = TRUE;
 cd.CD = FALSE;
 cd.data_transfer_done = TRUE;
 PhaseChange = TRUE;
}

static void DoEJECT(void)
{
 puts("Eject!");
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoREQUESTSENSE(void)
{
 MakeSense(cd.data_in, cd.key_pending, cd.asc_pending, cd.ascq_pending, cd.fru_pending);
 cd.data_in_pos = 0;
 cd.data_in_size = 18;
 cd.key_pending = 0;
 cd.asc_pending = 0;
 cd.ascq_pending = 0;
 cd.fru_pending = 0;

 cd.IO = TRUE;
 cd.CD = FALSE;
 cd.data_transfer_done = TRUE;
 PhaseChange = TRUE;
}

static void DoGETDIRINFO(void)
{
 //printf("GETDIR: %02x\n", cd.command_buffer[1]);
 switch (cd.command_buffer[1])
 {
  default:
   printf("DOHDOH: %02x\n", cd.command_buffer[1]);
  case 0x0:
   cd.data_in[0] = INT_TO_BCD(CDIF_GetFirstTrack());
   cd.data_in[1] = INT_TO_BCD(CDIF_GetLastTrack());
   cd.data_in_size = 2;
   break;
  case 0x1:
   {
    uint8 m, s, f;
    lba2msf(CDIF_GetSectorCountLBA(), &m, &s, &f);
    cd.data_in[0] = INT_TO_BCD(m);
    cd.data_in[1] = INT_TO_BCD(s);
    cd.data_in[2] = INT_TO_BCD(f);
    cd.data_in_size = 3;
   }
   break;
  case 0x2:
   {
    int m, s, f;
    int track = BCD_TO_INT(cd.command_buffer[2]);

    if(!track)
     track = 1;
    else if(cd.command_buffer[2] == 0xAA)
    {
     track = CDIF_GetLastTrack() + 1;
    }
    else if(track > 99)
    {
     cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
     cd.asc_pending = ASC_INVALID_FIELD_IN_CDB;
     cd.ascq_pending = 0x00;
     cd.fru_pending = 0x00;
     SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
     return;
    }

    CDIF_Track_Format format;
    CDIF_GetTrackStartPositionMSF(track, m, s, f);
    CDIF_GetTrackFormat(track, format);

    //printf(" Track: %d %02x %02x %02x\n", track, m, s, f);

    cd.data_in[0] = INT_TO_BCD(m);
    cd.data_in[1] = INT_TO_BCD(s);
    cd.data_in[2] = INT_TO_BCD(f);
    cd.data_in[3] = (format == CDIF_FORMAT_AUDIO) ? 0x00 : 0x04;
    cd.data_in_size = 4;
   }
   break;

 }
 cd.data_transfer_done = TRUE;
 cd.data_in_pos = 0;
 cd.IO = TRUE;
 cd.CD = FALSE;
 PhaseChange = TRUE;
}

static void DoREADTOC(void)
{
 int FirstTrack = CDIF_GetFirstTrack();
 int LastTrack = CDIF_GetLastTrack();
 int StartingTrack = cd.command_buffer[6];
 unsigned int AllocSize = (cd.command_buffer[7] << 8) | cd.command_buffer[8];
 unsigned int RealSize = 0;
 bool WantInMSF = cd.command_buffer[1] & 0x2;

 //printf("READ TOC: %d %02x %d %d\n", StartingTrack, cd.command_buffer[6], AllocSize, WantInMSF);

 if(!StartingTrack)
  StartingTrack = 1;
 else if(cd.command_buffer[6] == 0xAA || StartingTrack > 99)
 {
  StartingTrack = LastTrack + 1;
 }
 //else if(StartingTrack > 100) // 100 is the hidden leadout track, so test for >100, not >99!
 //{
 // puts("ERROR");
 // cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
 // cd.asc_pending = ASC_INVALID_FIELD_IN_CDB;
 // cd.ascq_pending = 0x00;
 // cd.fru_pending = 0x00;
 // SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
 // return;
 // }

 cd.data_in[2] = INT_TO_BCD(FirstTrack);
 cd.data_in[3] = INT_TO_BCD(LastTrack);

 RealSize += 4;

 // Read leadout track too LastTrack + 1 ???
 for(int track = StartingTrack; track <= (LastTrack + 1) && (RealSize + 8) <= AllocSize; track++)
 {
  uint8 *subptr = &cd.data_in[RealSize];

  int minutes;
  int seconds;
  int frames;
  CDIF_Track_Format format;
  CDIF_GetTrackStartPositionMSF(track, minutes, seconds, frames);
  CDIF_GetTrackFormat(track, format);

  uint32 lba = frames + 75 * seconds + 75 * 60 * minutes;

  //printf("%d, %d, %02x\n", track, WantInMSF, CDIFFORMAT_To_QSCF(format) | 0x10);
  subptr[0] = 0;
  subptr[1] = CDIFFORMAT_To_QSCF(format) | 0x10;	// | 0x10 = Q sub channel encodes current position info
  if(track == (LastTrack + 1))
   subptr[2] = 0xAA;
  else
   subptr[2] = INT_TO_BCD(track);
  subptr[3] = 0;

  //printf("LA: %d\n", lba);
  if(WantInMSF)
  {
   subptr[4] = 0;
   subptr[5] = minutes;		// Min
   subptr[6] = seconds;		// Sec
   subptr[7] = frames;		// Frames
  }
  else
  {
   lba -= 150;
   subptr[4] = lba >> 24;
   subptr[5] = lba >> 16;
   subptr[6] = lba >> 8;
   subptr[7] = lba >> 0;
  }
  RealSize += 8;
 }

 if(RealSize > AllocSize)
  RealSize = AllocSize;

 // Record the length of the response, minus the size of the TOC data length field(2 bytes)
 cd.data_in[0] = (RealSize - 2) >> 8;
 cd.data_in[1] = (RealSize - 2) >> 0;

 //printf("%d %d %d %d\n", cd.data_in_size, StartingTrack, LastTrack, CDIFFORMAT_To_QSCF(format) | 0x10);
 cd.data_in_size = RealSize;
 cd.data_in_pos = 0;
 cd.IO = TRUE;
 cd.CD = FALSE;
 cd.data_transfer_done = TRUE;
 PhaseChange = TRUE;
}

static void DoREADHEADER(void)
{
 bool WantInMSF = cd.command_buffer[1] & 0x2;
 uint32 HeaderLBA = (cd.command_buffer[2] << 24) | (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
 int AllocSize = (cd.command_buffer[7] << 8) | cd.command_buffer[8];
 int Track = CDIF_FindTrackByLBA(HeaderLBA);

 int minutes;
 int seconds;
 int frames;
 CDIF_Track_Format format;

 CDIF_GetTrackStartPositionMSF(Track, minutes, seconds, frames);
 CDIF_GetTrackFormat(Track, format);

 uint32 lba = frames + 75 * seconds + 75 * 60 * minutes;

 //printf("LBA: %d, Track: %d\n", HeaderLBA, Track);

 cd.data_in[0] = CDIFFormat_To_DMF(format);
 cd.data_in[1] = 0;
 cd.data_in[2] = 0;
 cd.data_in[3] = 0;

 if(WantInMSF)
 {
  cd.data_in[4] = 0;
  cd.data_in[5] = minutes;	// Min
  cd.data_in[6] = seconds;	// Sec
  cd.data_in[7] = frames;	// Frames
 }
 else
 {
  lba -= 150;
  cd.data_in[4] = lba >> 24;
  cd.data_in[5] = lba >> 16;
  cd.data_in[6] = lba >> 8;
  cd.data_in[7] = lba >> 0;
 }
 cd.data_in_size = 8;
 cd.data_in_pos = 0;
 cd.IO = TRUE;
 cd.CD = FALSE;
 cd.data_transfer_done = TRUE;

 CDDAStatus = 0;
 PhaseChange = TRUE;
}

static void DoSAPSP(void)
{
 uint32 new_read_sec_start;

 //            printf("Set audio start: %02x %02x %02x %02x %02x %02x %02x\n", cd.command_buffer[9], cd.command_buffer[1], cd.command_buffer[2], cd.command_buffer[3], cd.command_buffer[4], cd.command_buffer[5], cd.command_buffer[6]);
 switch (cd.command_buffer[9] & 0xc0)
 {
  default:  printf("Unknown SAPSP 9: %02x\n", cd.command_buffer[9]);
  case 0x00:
   new_read_sec_start = (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
   break;
  case 0x40:
   new_read_sec_start = BCD_TO_INT(cd.command_buffer[4]) + 75 * (BCD_TO_INT(cd.command_buffer[3]) + 60 * BCD_TO_INT(cd.command_buffer[2]));
   new_read_sec_start -= 150;
   break;
  case 0x80:
   new_read_sec_start = CDIF_GetTrackStartPositionLBA(BCD_TO_INT(cd.command_buffer[2]));
   break;
 }

 // Is this a hack, or does it really work like this on the real thing?  Ack, it breaks Nexzr!
 //if(new_read_sec_start != read_sec_start || CDDAStatus != 1 || WhichSystem == SCSICD_PCFX)
 if(1)
 {
  read_sec = read_sec_start = new_read_sec_start;
  CDDAReadPos = 588;
  CDDAStatus = -1;
  PlayMode = cd.command_buffer[1];

  if(PlayMode)
  {
   CDDAStatus = 1;
  }
 }
 SendStatusAndMessage(STATUS_GOOD, 0x00);
 CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE);
}

static void DoSAPEP(void)
{
 //                printf("Set audio end: %02x %02x %02x %02x %02x\n", cd.command_buffer[9], cd.command_buffer[1], cd.command_buffer[2], cd.command_buffer[3], cd.command_buffer[4]);
 uint32 new_read_sec_end;

 switch (cd.command_buffer[9] & 0xc0)
 {
  default: printf("Unknown SAPEP 9: %02x\n", cd.command_buffer[9]);

  case 0x00:
   new_read_sec_end = (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
   break;
  case 0x40:
   new_read_sec_end = BCD_TO_INT(cd.command_buffer[4]) + 75 * (BCD_TO_INT(cd.command_buffer[3]) + 60 * BCD_TO_INT(cd.command_buffer[2]));
   new_read_sec_end -= 150;
   break;
  case 0x80:
   new_read_sec_end = CDIF_GetTrackStartPositionLBA(BCD_TO_INT(cd.command_buffer[2]));
   break;
 }

 PlayMode = cd.command_buffer[1];
 if(PlayMode)
  CDDAStatus = 1;
 else
  CDDAStatus = -1;	//	paused or stopped?

 read_sec_end = new_read_sec_end;

 SendStatusAndMessage(STATUS_GOOD, 0x00);
 //CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE);
}

static void DoSST(void)		// Command 0xDB, Set Stop Time
{
 printf("Set stop time: ");
 for(int i = 0; i < 10; i++)
  printf("%02x ", cd.command_buffer[i]);
 printf("\n");
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoPATI(void)
{
 int StartTrack = BCD_TO_INT(cd.command_buffer[4]);
 int EndTrack = BCD_TO_INT(cd.command_buffer[7]);
 int StartIndex = BCD_TO_INT(cd.command_buffer[5]);
 int EndIndex = BCD_TO_INT(cd.command_buffer[8]);

 CDIF_Track_Format start_format;

 if(!CDIF_GetTrackFormat(StartTrack, start_format) || start_format != CDIF_FORMAT_AUDIO)
 {
  cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
  cd.asc_pending = 0x00;
  cd.ascq_pending = 0x00;
  cd.fru_pending = 0x00;

  SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
  return;
 }

 read_sec = read_sec_start = CDIF_GetTrackStartPositionLBA(StartTrack);
 read_sec_end = CDIF_GetTrackStartPositionLBA(EndTrack);
 CDDAStatus = 1;
 PlayMode = 3;			// No repeat

 printf("PATI: %d %d %d  SI: %d, EI: %d\n", StartTrack, EndTrack, CDIF_GetTrackStartPositionLBA(StartTrack), StartIndex, EndIndex);
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoPAUSERESUME(void)
{
 // Pause/resume
 // "It shall not be considered an error to request a pause when a pause is already in effect, 
 // or to request a resume when a play operation is in progress."
 if(!CDDAStatus)		// If CDDA isn't playing/paused, make an error!
 {
  cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
  cd.asc_pending = 0x2c;
  cd.ascq_pending = 0x00;
  cd.fru_pending = 0x00;
  SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
  return;
 }
 if(cd.command_buffer[8] & 1)	// Resume
 {
  CDDAStatus = 1;
 }
 else
  CDDAStatus = -1;
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoREADBase(uint32 sa, uint32 sc)
{
 SectorAddr = sa;
 SectorCount = sc;
 if(SectorCount)
 {
  CDReadTimer = (uint64)1 * 2048 * SYSTEM_CLOCK / CD_DATA_TRANSFER_RATE;
  PhaseChange = TRUE;
 }
 else
 {
  CDReadTimer = 0;
  SendStatusAndMessage(STATUS_GOOD, 0x00);
 }
 CDDAStatus = 0;
}

static void DoREAD6(void)
{
 uint32 sa = ((cd.command_buffer[1] & 0x1F) << 16) | (cd.command_buffer[2] << 8) | (cd.command_buffer[3] << 0);
 uint32 sc = cd.command_buffer[4];

 DoREADBase(sa, sc);
}

static void DoREAD10(void)
{
 uint32 sa = (cd.command_buffer[2] << 24) | (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
 uint32 sc = (cd.command_buffer[7] << 8) | (cd.command_buffer[8]);

 DoREADBase(sa, sc);
}

static void DoREAD12(void)
{
 uint32 sa = (cd.command_buffer[2] << 24) | (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
 uint32 sc = (cd.command_buffer[6] << 24) | (cd.command_buffer[7] << 16) | (cd.command_buffer[8] << 8) | cd.command_buffer[9];

 DoREADBase(sa, sc);
}

static void DoPREFETCH(void)
{
 uint32 lba = (cd.command_buffer[2] << 24) | (cd.command_buffer[3] << 16) | (cd.command_buffer[4] << 8) | cd.command_buffer[5];
 uint32 len = (cd.command_buffer[7] << 8) | cd.command_buffer[8];
 bool link = cd.command_buffer[9] & 0x1;
 bool flag = cd.command_buffer[9] & 0x2;
 bool reladdr = cd.command_buffer[1] & 0x1;
 bool immed = cd.command_buffer[1] & 0x2;

 //printf("Prefetch: %08x %08x %d %d %d %d\n", lba, len, link, flag, reladdr, immed);
 //SendStatusAndMessage(STATUS_GOOD, 0x00);
 SendStatusAndMessage(STATUS_CONDITION_MET, 0x00);
 //exit(1);
}

static void DoREADSUBCHANNEL(void)
{
 int DataFormat = cd.command_buffer[3];
 int TrackNum = cd.command_buffer[6];
 int AllocSize = (cd.command_buffer[7] << 8) | cd.command_buffer[8];
 bool WantQ = cd.command_buffer[2] & 0x40;
 bool WantMSF = cd.command_buffer[1] & 0x02;

 //printf("42Read SubChannel: %02x %02x %d %d %d\n", DataFormat, TrackNum, AllocSize, WantQ, WantMSF);
 if(!WantQ)
  SendStatusAndMessage(STATUS_GOOD, 0x00);
 else
 {
  switch (DataFormat)
  {
   default:
    printf("42Read SubChannel: %02x %02x %d %d %d\n", DataFormat, TrackNum, AllocSize, WantQ, WantMSF);

    exit(1);

   case 0x01:			// CD-ROM current position
    {
     cd.data_in[0] = 0;

     if(CDDAStatus == 1)
      cd.data_in[1] = 0x11;
     else if(CDDAStatus == -1)
      cd.data_in[1] = 0x12;
     else
      cd.data_in[1] = 0x13;	// 0x13 or 0x15? :(

     // Sub-channel data length
     cd.data_in[2] = 0x00;
     cd.data_in[3] = 0x0C;

     // Sub-channel format code
     cd.data_in[4] = 0x01;

     cd.data_in[5] = cd.SubQBuf[0]; // Control/adr
     cd.data_in[6] = cd.SubQBuf[1]; // Track
     cd.data_in[7] = cd.SubQBuf[2]; // Index

     // Absolute CD-ROM address
     if(WantMSF)
     {
      cd.data_in[8] = 0;
      cd.data_in[9] = cd.SubQBuf[7]; // M
      cd.data_in[10] = cd.SubQBuf[8]; // S
      cd.data_in[11] = cd.SubQBuf[9]; // F
     }
     else
     {
      uint32 tmp_lba = cd.SubQBuf[7] * 60 * 75 + cd.SubQBuf[8] * 75 + cd.SubQBuf[9] - 150;

      cd.data_in[8] = tmp_lba >> 24;
      cd.data_in[9] = tmp_lba >> 16;
      cd.data_in[10] = tmp_lba >> 8;
      cd.data_in[11] = tmp_lba >> 0;
     }

     // Relative CD-ROM address
     if(WantMSF)
     {
      cd.data_in[12] = 0;
      cd.data_in[13] = cd.SubQBuf[3]; // M
      cd.data_in[14] = cd.SubQBuf[4]; // S
      cd.data_in[15] = cd.SubQBuf[5]; // F
     }
     else
     {
      uint32 tmp_lba = cd.SubQBuf[3] * 60 * 75 + cd.SubQBuf[4] * 75 + cd.SubQBuf[5] - 150;

      cd.data_in[12] = tmp_lba >> 24;
      cd.data_in[13] = tmp_lba >> 16;
      cd.data_in[14] = tmp_lba >> 8;
      cd.data_in[15] = tmp_lba >> 0;
     }
     cd.data_in_size = (AllocSize > 16) ? 16 : AllocSize;
     cd.data_transfer_done = TRUE;
     cd.data_in_pos = 0;
     cd.IO = TRUE;
     cd.CD = FALSE;
     PhaseChange = TRUE;
    }
    break;
  }
 }
 //printf("%02x %02x %04x %d\n", DataFormat, TrackNum, AllocSize, WantQ);
 //exit(1);
}

static void DoREADSUBQ(void)
{
 memset(cd.data_in, 0x00, 10);

 cd.data_in[2] = cd.SubQBuf[1];     // Track
 cd.data_in[3] = cd.SubQBuf[2];     // Index
 cd.data_in[4] = cd.SubQBuf[3];     // M(rel)
 cd.data_in[5] = cd.SubQBuf[4];     // S(rel)
 cd.data_in[6] = cd.SubQBuf[5];     // F(rel)
 cd.data_in[7] = cd.SubQBuf[7];     // M(abs)
 cd.data_in[8] = cd.SubQBuf[8];     // S(abs)
 cd.data_in[9] = cd.SubQBuf[9];     // F(abs)

 if(CDDAStatus == -1)
  cd.data_in[0] = 2;		// Pause
 else if(CDDAStatus == 1)
  cd.data_in[0] = 0;		// Playing
 else
  cd.data_in[0] = 3;		// Stopped

 cd.data_in_size = 10;
 cd.data_transfer_done = TRUE;
 cd.data_in_pos = 0;
 cd.IO = TRUE;
 cd.CD = FALSE;
 PhaseChange = TRUE;
}

static void DoTESTUNITREADY(void)
{
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

static void DoNECPAUSE(void)
{
 if(CDDAStatus == 1 || CDDAStatus == -1) // Hmm, should we give an error if it tries to pause and it's already paused?
 {
  CDDAStatus = -1;
  SendStatusAndMessage(STATUS_GOOD, 0x00);
 }
 else // Definitely give an error if it tries to pause when no track is playing!
 {
  cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
  cd.asc_pending = 0x2c;
  cd.ascq_pending = 0x00;
  cd.fru_pending = 0x00;
  SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
 }
}

static void DoPREVENTALLOWREMOVAL(void)
{
 SendStatusAndMessage(STATUS_GOOD, 0x00);
}

typedef struct
{
 uint8 cmd;
 unsigned int cdb_length;
 void (*func)(void);
 const char *pretty_name;
} SCSICH;

static SCSICH PCFXCommandDefs[] =
{
 { 0x00, 6, DoTESTUNITREADY, "Test Unit Ready" },
 { 0x01, 6, DoREZEROUNIT, "Rezero Unit" },
 { 0x03, 6, DoREQUESTSENSE, "Request Sense" },
 { 0x08, 6, DoREAD6, "Read(6)" },
 { 0x12, 6, DoINQUIRY, "Inquiry" },
 { 0x15, 6, DoMODESELECT6, "Mode Select(6)" },
 { 0x1A, 6, DoMODESENSE6, "Mode Sense(6)" },
 { 0x1B, 6, DoSTARTSTOPUNIT6, "Start/Stop Unit" },
 { 0x1E, 16, DoPREVENTALLOWREMOVAL, "Prevent/Allow Media Removal" },
 { 0x28, 10, DoREAD10, "Read(10)" },
 { 0x34, 10, DoPREFETCH, "Prefetch" },
 { 0x42, 10, DoREADSUBCHANNEL, "Read Subchannel" },
 { 0x43, 10, DoREADTOC, "Read TOC" },
 { 0x44, 10, DoREADHEADER, "Read Header" },
 { 0x48, 10, DoPATI, "Play Audio Track Index" },
 { 0x4B, 10, DoPAUSERESUME, "Pause/Resume" },

 { 0xA8, 12, DoREAD12, "Read(12)" },

 { 0xD8, 10, DoSAPSP, "Set Audio Playback Start Position" },
 { 0xD9, 10, DoSAPEP, "Set Audio Playback End Position" },
 { 0xDA, 10, DoNECPAUSE, "Pause" },
 { 0xDB, 10, DoSST, "Set Stop Time" },
 { 0xDC, 10, DoEJECT, "Eject" },
 { 0xDD, 10, DoREADSUBQ, "Read Subchannel Q" },
 { 0xDE, 10, DoGETDIRINFO, "Get Dir Info" },

 { 0xFF, 0, NULL, NULL },
};

static SCSICH PCECommandDefs[] = 
{
 { 0x00, 1, DoTESTUNITREADY, "Test Unit Ready" }, // 1 or 6?
 { 0x03, 6, DoREQUESTSENSE, "Request Sense" },
 { 0x08, 6, DoREAD6, "Read(6)" },
 //{ 0x15, 6, DoMODESELECT6, "Mode Select(6)" },
 { 0xD8, 10, DoSAPSP, "Set Audio Playback Start Position" },
 { 0xD9, 10, DoSAPEP, "Set Audio Playback End Position" },
 { 0xDA, 10, DoNECPAUSE, "Pause" },
 { 0xDD, 10, DoREADSUBQ, "Read Subchannel Q" },
 { 0xDE, 10, DoGETDIRINFO, "Get Dir Info" },

 { 0xFF, 0, NULL, NULL },
};


static int32 lastts;
void SCSICD_ResetTS(void)
{
 lastts = 0;
}

void SCSICD_GetCDDAValues(int16 &left, int16 &right)
{
 if(CDDAStatus)
 {
  left = CDDASectorBuffer[CDDAReadPos * 2];
  right = CDDASectorBuffer[CDDAReadPos * 2 + 1];
 }
 else
  left = right = 0;
}

uint32 SCSICD_Run(uint32 system_timestamp)
{
 int32 run_time = system_timestamp - lastts;

 lastts = system_timestamp;

 if(CDReadTimer > 0)
 {
  CDReadTimer -= run_time;
  if(CDReadTimer <= 0)
  {
   if(cd.data_in_pos != cd.data_in_size)
   {
    //printf("Carp: %d %d %d %d\n", cd.data_in_pos, cd.data_in_size, SectorCount, CDReadTimer);

    CDReadTimer += (uint64) 1 * 2048 * SYSTEM_CLOCK / CD_DATA_TRANSFER_RATE;
   }
   else
   {
    if(!CDIF_ReadSector(cd.data_in, cd.SubQBuf, cd.SubRWBuf, SectorAddr, 1))
    {
     cd.data_transfer_done = FALSE;
     cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
     cd.asc_pending = 0x00;
     cd.ascq_pending = 0x00;
     cd.fru_pending = 0x00;
     SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
     puts("Error");
    }
    else
    {
     CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_READY);

     SectorAddr++;
     SectorCount--;

     cd.IO = TRUE;
     cd.CD = FALSE;

     if(SectorCount)
     {
      cd.data_transfer_done = FALSE;
      CDReadTimer += (uint64) 1 * 2048 * SYSTEM_CLOCK / CD_DATA_TRANSFER_RATE;
     }
     else
     {
      cd.data_transfer_done = TRUE;
     }
     cd.data_in_pos = 0;
     cd.data_in_size = 2048;
    }
   }				// end else to if(!CDIF_ReadSector

  }
 }


 if(CDDAStatus == 1)
 {
  int16 sample[2];

  CDDADiv -= run_time << 16;

  while(CDDADiv <= 0)
  {
   CDDADiv += CDDADivAcc;

   if(CDDAReadPos == 588)
   {
    if(read_sec == read_sec_end)
    {
     switch (PlayMode)
     {
      default:
      case 0x03:		// No repeat
       CDDAStatus = 0;
       break;
      case 0x02:		// Interrupt when finished?
       CDDAStatus = 0;
       CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE);
       break;
      case 0x04:
      case 0x01:		// Repeat play!
       read_sec = read_sec_start;
       break;
     }
     if(CDDAStatus != 1)
      break;
    }
    else
     read_sec++;
    CDDAReadPos = 0;
    CDIF_ReadAudioSector(CDDASectorBuffer, cd.SubQBuf, cd.SubRWBuf, read_sec);
   }

   sample[0] = CDDASectorBuffer[CDDAReadPos * 2];
   sample[1] = CDDASectorBuffer[CDDAReadPos * 2 + 1];

   uint32 synthtime = ((system_timestamp + (CDDADiv >> 16))) / CDDATimeDiv;
   if(FSettings.SndRate)
   {
    if(!(CDDAReadPos % 6))
    {
     int subindex = CDDAReadPos / 6 - 2;

     if(subindex >= 0)
     {
      uint8 the_stuff;

      the_stuff = cd.SubRWBuf[subindex]; // | ((cd.SubQBuf[subindex >> 3] >> (subindex & 0x7)) & 0x1) ? 0x40 : 0x00;
      CDStuffSubchannels(the_stuff);
     }
     else
     {
      uint8 zebyte = 0;

      if(subindex == -2)
	zebyte = 0x00; //54;
      else if(subindex == -1)
	zebyte = 0x80; //0xAA;
      CDStuffSubchannels(zebyte);
     }
    }

    CDDASynth[0].offset(synthtime, sample[0] - last_sample[0], sbuf[0]);
    CDDASynth[1].offset(synthtime, sample[1] - last_sample[1], sbuf[1]);

    last_sample[0] = sample[0];
    last_sample[1] = sample[1];
   }
   CDDAReadPos++;
  }
 }

 do
 {
  PhaseChange = FALSE;


 if(!SEL_signal && !BSY_signal && cd.device_selected)	// BUS-free mode, release our signals
 {
  //puts("Bus free mode!");
  CDIRQCallback(0x8000 | SCSICD_IRQ_DATA_TRANSFER_DONE);
  cd.device_selected = FALSE;
  cd.CD = FALSE;
  cd.MSG = FALSE;
  cd.IO = FALSE;
  cd.REQ = FALSE;
 }

 if(RST_signal)
 {
  if(!cd.last_RST_signal)
  {
   cd.BSY = FALSE;
   cd.CD = FALSE;
   cd.MSG = FALSE;
   cd.IO = FALSE;
   cd.REQ = FALSE;
   PhaseChange = TRUE;
  }
  cd.last_RST_signal = RST_signal;
  //return;
  goto SpoonyBard;
 }
 cd.last_RST_signal = RST_signal;

 if(SEL_signal)
 {
  if(WhichSystem == SCSICD_PCFX)
  {
   if(BSY_signal)		// Selection phase?
   {
    cd.device_selected = TRUE;

    cd.CD = TRUE;		// Ask for message!
    cd.BSY = TRUE;
    cd.MSG = FALSE;
    cd.IO = FALSE;
    cd.REQ = TRUE;
    //puts("Yay");
   }
  }
  else if(!cd.device_selected)// PCE
  {
   cd.device_selected = TRUE;
   cd.CD = TRUE;               // Ask for message!
   cd.BSY = TRUE;
   cd.MSG = FALSE;
   cd.IO = FALSE;
   cd.REQ = TRUE;
  }
 }
 else if(!cd.device_selected)
 {

 }
 else if(ATN_signal)
 {
  // If any bits are changing, set PhaseChange
  if(!cd.BSY || !cd.MSG || !cd.REQ || !cd.CD || cd.IO)
   PhaseChange = TRUE;

  cd.BSY = TRUE;
  cd.MSG = TRUE;
  cd.REQ = TRUE;
  cd.CD = TRUE;
  cd.IO = FALSE;
 }
 else if(!MSG_signal && BSY_signal)	// Data phaseseses
 {
  if(!IO_signal)
  {
   if(CD_signal)		// COMMAND phase
   {
    if(REQ_signal && ACK_signal)	// Data bus is valid nowww
    {
     //printf("Command Phase Byte I->T: %02x, %d\n", cd.DB, cd.command_buffer_pos);
     cd.command_buffer[cd.command_buffer_pos++] = cd.DB;
     cd.REQ = FALSE;
    }

    if(!REQ_signal && !ACK_signal && cd.command_buffer_pos)	// Received at least one byte, what should we do?
    {
     const SCSICH *cmd_info_ptr;

     if(WhichSystem == SCSICD_PCFX)
      cmd_info_ptr = PCFXCommandDefs;
     else
      cmd_info_ptr = PCECommandDefs;

     while(cmd_info_ptr->pretty_name && cmd_info_ptr->cmd != cd.command_buffer[0])
      cmd_info_ptr++;
  
     if(cmd_info_ptr->pretty_name == NULL)	// Command not found!
     {
      cd.key_pending = SENSEKEY_ILLEGAL_REQUEST;
      cd.asc_pending = 0x20;
      cd.ascq_pending = 0x00;
      cd.fru_pending = 0x00;
      SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);

      printf("Bad Command: %02x\n", cd.command_buffer[0]);
      cd.command_buffer_pos = 0;
     }
     else if(cd.command_buffer_pos == cmd_info_ptr->cdb_length)	// We've received the command, and it's done, sooooo...
     {
      uint8 cmd = cd.command_buffer[0];

      if(0)
      {
       printf("Command: %02x, %s\t", cd.command_buffer[0], cmd_info_ptr->pretty_name);
       for(int i = 0; i < cmd_info_ptr->cdb_length; i++)
        printf("%02x ", cd.command_buffer[i]);
       printf("\n");
      }
      if(!cd.IsInserted && cmd != 0x03 && cmd != 0x1E)	// Hmm, user shouldn't be able to eject a physical CD while it's open with libcdio, so
       // hopefully there won't be a race condition! >_>
      {
       cd.key_pending = SENSEKEY_NOT_READY;
       cd.asc_pending = ASC_MEDIUM_NOT_PRESENT;
       cd.ascq_pending = 0x00;
       cd.fru_pending = 0x00;
       SendStatusAndMessage(STATUS_CHECK_CONDITION, 0x00);
      }
      else
      {
       cmd_info_ptr->func();
      }

      cd.command_buffer_pos = 0;
     }
     else			// Otherwise, get more data for the command!
      cd.REQ = TRUE;
    }
   }
   else				// DATA OUT Phase, neato!?
   {
    if(REQ_signal && ACK_signal)	// Data bus is valid nowww
    {
     //printf("DATAOUT-SCSIIN: %d %02x\n", cd.data_out_pos, cd.DB);
     cd.data_out[cd.data_out_pos++] = cd.DB;
     cd.REQ = FALSE;
    }
    else if(!REQ_signal && !ACK_signal && cd.data_out_pos)
    {
     if(cd.data_out_pos == cd.data_out_size)
     {
      //printf("Datumama: %02x %02x %02x %02x %02x %02x %02x   %d\n", cd.data_out[0], cd.data_out[1], cd.data_out[2], cd.data_out[3], cd.data_out[4], cd.data_out[5], cd.data_out[6], cd.data_out_size);
      //for(int i = 0; i < 0x10; i++) printf("%02x\n", cd.data_out[i]);
      //printf("%d\n", cd.data_out_size);
      if(cd.command_buffer[0] == 0x15)
      {
       switch (cd.data_out[4])
       {
	case 0x29:
	 break;			// ??? Miraculum tries to set this mode page
	case 0x2B:
	 {
	  int8 speed = cd.data_out[6];
	  double rate = 44100 + (double)44100 * speed / 100;
	  //printf("Speed: %d %f\n", speed, rate);
	  CDDADivAcc = (int32)((int64)SYSTEM_CLOCK * 65536 / rate);
	 }
	 break;
       }
      }
      cd.data_out_pos = 0;
      SendStatusAndMessage(STATUS_GOOD, 0x00);
     }
     else
      cd.REQ = TRUE;
    }
   }
  }
  else				// to if(!IO_signal)
  {
   if(CD_signal)		// STATUS
   {
    if(REQ_signal && ACK_signal)
    {
     cd.REQ = FALSE;
     cd.status_sent = TRUE;
    }

    if(!REQ_signal && !ACK_signal && cd.status_sent)
    {
     cd.status_sent = FALSE;
     if(cd.need_message_after_status)
     {
      cd.need_message_after_status = FALSE;
      cd.MSG = TRUE;
      cd.REQ = TRUE;
      cd.DB = cd.message_pending;
     }
     //puts("Status");
    }
   }				// END STATUS
   else
   {				// BEGIN "DATA IN"(us to them!)
    //puts("Choo");

    if(!REQ_signal && !ACK_signal)
    {
     if(cd.data_in_pos == cd.data_in_size)	// aaand we're done!
     {
      CDIRQCallback(0x8000 | SCSICD_IRQ_DATA_TRANSFER_READY);

      if(cd.data_transfer_done)
      {
       SendStatusAndMessage(STATUS_GOOD, 0x00);
       cd.data_transfer_done = FALSE;
       CDIRQCallback(SCSICD_IRQ_DATA_TRANSFER_DONE);
      }
      else
      {
       //SendStatus(STATUS_GOOD);
      }
     }
     else
     {
      //printf("DB Set: %d, %02x\n", cd.data_in_pos, cd.DB);
      cd.DB = cd.data_in[cd.data_in_pos++];
      cd.REQ = TRUE;
     }
    }
    if(REQ_signal && ACK_signal)
    {
     cd.REQ = FALSE;
    }
   }

  }
 }
 else if(MSG_signal && BSY_signal)	// Message phases, todo
 {
  if(!IO_signal)		// MESSAGE OUT
  {
   if(REQ_signal && ACK_signal)
   {
    cd.REQ = FALSE;
    if(cd.DB == 0x6)		// ABORT message!
    {
     cd.data_in_pos = cd.data_in_size = 0;
     cd.BSY = FALSE;
     cd.CD = FALSE;
     cd.MSG = FALSE;
     cd.IO = FALSE;
     CDReadTimer = 0;
     CDDAStatus = 0;
     PhaseChange = TRUE;
    }
    else
     printf("Message to target: %02x\n", cd.DB);

   }
  }
  else				// MESSAGE IN
  {
   if(REQ_signal && ACK_signal)
   {
    cd.REQ = FALSE;
    cd.message_sent = TRUE;
   }

   if(!REQ_signal && !ACK_signal && cd.message_sent)
   {
    cd.message_sent = FALSE;
    cd.BSY = FALSE;
    PhaseChange = TRUE;
   }
  }
 }
 } while(PhaseChange);

 SpoonyBard:

 next_time = 0x7fffffff;

 if(CDReadTimer > 0 && CDReadTimer < next_time)
  next_time = CDReadTimer;

 if(CDDAStatus == 1)
 {
  int32 cdda_div_sexytime = (CDDADiv + 0xFFFF) >> 16;
  if(cdda_div_sexytime > 0 && cdda_div_sexytime < next_time)
   next_time = cdda_div_sexytime;
 }

 return(next_time);
}

bool SCSICD_StealByte(uint8 & stolen)
{
 bool ret = 0;

 if(REQ_signal)
 {
  stolen = cd.DB;
  cd.REQ = FALSE;
  ret = 1;
 }
 else if(cd.data_in_pos < cd.data_in_size)
 {
  stolen = cd.data_in[cd.data_in_pos];
  //printf("Stolen: %02x %d %d\n", stolen, cd.data_in_pos, cd.data_in_size);
  ret = 1;
  cd.data_in_pos++;
  if(cd.data_in_pos == cd.data_in_size)
  {
   if(cd.data_transfer_done)
   {
    cd.data_transfer_done = FALSE;
    SendStatusAndMessage(STATUS_GOOD, 0x00);
   }
  }
 }

 return (ret);
}


void SCSICD_Init(int type, int cdda_time_div, Blip_Buffer *leftbuf, Blip_Buffer *rightbuf, uint32 TransferRate, uint32 SystemClock, void (*IRQFunc)(int), void (*SSCFunc)(uint8))
{
 WhichSystem = type;
 CDDATimeDiv = cdda_time_div;

 CDDASynth[0].volume(1.0f);
 CDDASynth[1].volume(1.0f);
 sbuf[0] = leftbuf;
 sbuf[1] = rightbuf;

 CD_DATA_TRANSFER_RATE = TransferRate;
 SYSTEM_CLOCK = SystemClock;
 CDIRQCallback = IRQFunc;
 CDStuffSubchannels = SSCFunc;
}

void SCSICD_SetCDDAVolume(double left, double right)
{
 // CDDASynth[*].volume will generate an exception if a too small number is passed(less than 1/8192), but we'll check for < 1024
 // to be on the safe side.
 if(left < (1.0f / 1024))
  left = 0;
 if(right < (1.0f / 1024))
  right = 0;

 CDDASynth[0].volume(left);
 CDDASynth[1].volume(right);
}

int SCSICD_StateAction(StateMem * sm, int load, int data_only, const char *sname)
{
 SFORMAT StateRegs[] = 
 {
  SFVARN(cd.DB, "DB"),
  SFVARN(cd.BSY, "BSY"),
  SFVARN(cd.MSG, "MSG"),
  SFVARN(cd.CD, "CD"),
  SFVARN(cd.REQ, "REQ"),
  SFVARN(cd.IO, "IO"),

  SFVARN(cd.kingBSY, "kingBSY"),
  SFVARN(cd.kingACK, "kingACK"),
  SFVARN(cd.kingRST, "kingRST"),
  SFVARN(cd.kingMSG, "kingMSG"),
  SFVARN(cd.kingSEL, "kingSEL"),
  SFVARN(cd.kingCD, "kingCD"),
  SFVARN(cd.kingIO, "kingIO"),
  SFVARN(cd.kingATN, "kingATN"),

  SFVARN(cd.last_RST_signal, "last_RST"),
  SFVARN(cd.message_pending, "message_pending"),
  SFVARN(cd.status_sent, "status_sent"),
  SFVARN(cd.message_sent, "message_sent"),
  SFVARN(cd.key_pending, "key_pending"),
  SFVARN(cd.asc_pending, "asc_pending"),
  SFVARN(cd.ascq_pending, "ascq_pending"),
  SFVARN(cd.fru_pending, "fru_pending"),
  SFVARN(cd.need_message_after_status, "need_message_after_status"),
  SFVARN(cd.device_selected, "device_selected"),

  SFARRAYN(cd.command_buffer, 256, "command_buffer"),
  SFVARN(cd.command_buffer_pos, "command_buffer_pos"),
  SFVARN(cd.command_size_left, "command_size_left"),

  SFARRAYN(cd.data_in, 8192, "data_in"),
  SFVARN(cd.data_in_pos, "data_in_pos"),
  SFVARN(cd.data_in_size, "data_in_size"),
  SFVARN(cd.data_transfer_done, "data_transfer_done"),

  SFARRAYN(cd.data_out, 8192, "data_out"),
  SFVARN(cd.data_out_pos, "data_out_pos"),
  SFVARN(cd.data_out_size, "data_out_size"),

  SFVARN(cd.IsInserted, "IsInserted"),

  SFVAR(PlayMode),
  SFARRAY16(CDDASectorBuffer, 1176),
  SFVAR(CDDAReadPos),
  SFVAR(CDDAStatus),
  SFVAR(CDDADiv),
  SFVAR(read_sec_start),
  SFVAR(read_sec),
  SFVAR(read_sec_end),

  SFVAR(CDReadTimer),
  SFVAR(SectorAddr),
  SFVAR(SectorCount),

  SFVAR(next_time),
  SFEND
 };

 int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, sname);

 if(load)
 {

 }

 return (ret);
}
