/*  cdrdao - write audio CD-Rs in disc-at-once mode
 *
 *  Copyright (C) 1998-2001  Andreas Mueller <mueller@daneb.ping.de>
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#include "PlextorReader.h"

#include "Toc.h"
#include "util.h"

#include "PWSubChannel96.h"

PlextorReader::PlextorReader(ScsiIf *scsiIf, unsigned long options)
  : CdrDriver(scsiIf, options)
{
  driverName_ = "Plextor CD-ROM Reader - Version 1.1";
  
  speed_ = 0;
  simulate_ = 0;
  audioDataByteOrder_ = 0;

  memset(&diskInfo_, 0, sizeof(DiskInfo));
  diskInfo_.valid.empty = 1;

  {
    struct
    {   
      int number;
      char *productid; // as obtained through INQUIRY
    } models[] =
      {
      { 1,"CD-ROM PX-4XCH" },
      { 2,"CD-ROM PX-4XCS" },
      { 3,"CD-ROM PX-4XCE" },
      { 4,"CD-ROM PX-6X" },
      { 5,"CD-ROM PX-8X" },
      { 6,"CD-ROM PX-12" },
      { 7,"CD-ROM PX-20" },
      { 8,"CD-ROM PX-32" },
      { 9,"CD-ROM PX-40" },
      { 0,NULL }
    };
    int m = 0;
    while (models[m].number)
    {
      if (strncmp(scsiIf_->product(), models[m].productid,
		  strlen(models[m].productid)) == 0)
        break;
      else
        m++;
    }
    model_ = models[m].number; // zero if not found

    message(4, "model number %d\n",model_);
    message(4, "PRODUCT ID: '%s'\n", scsiIf_->product());
  }
}

// static constructor
CdrDriver *PlextorReader::instance(ScsiIf *scsiIf, unsigned long options)
{
  return new PlextorReader(scsiIf, options);
}

// sets speed
// return: 0: OK
//         1: illegal speed (n/a; the strategy is to map to a valid speed)
int PlextorReader::speed(int speed)
{
  unsigned short dataLen = 56;
  unsigned char data[56];
  char speedvalue=0;

  if (model_ == 0) // not a Plextor device
    return 0;

  switch (model_) {
  case 4:
    speedvalue = (speed-1)/2;
    break;
  default:
    speedvalue = speed/2;
    break;
  }
  
  getModePage6((long)0x31,data,dataLen,NULL,NULL,1);
  //message(0,"page code %0x\n",data[0]);
  //message(0,"speed value was %d\n",data[2]);
  data[2]=speedvalue;
  //message(0,"new speedvalue %d\n",speedvalue);    
  setModePage6(data,NULL,NULL,1);

  return 0;  
}

DiskInfo *PlextorReader::diskInfo()
{
  return &diskInfo_;
}

int PlextorReader::initDao(const Toc *toc)
{
  message(-2, "Writing is not supported by this driver.");
  return 1;
}

int PlextorReader::startDao()
{
  return 1;
}

int PlextorReader::finishDao()
{
  return 1;
}

void PlextorReader::abortDao()
{
}

// tries to read catalog number from disk and adds it to 'toc'
// return: 1 if valid catalog number was found, else 0

int PlextorReader::readCatalog(Toc *toc, long startLba, long endLba)
{
  unsigned char cmd[10];
  unsigned short dataLen = 0x30;
  unsigned char data[0x30];
  char catalog[14];
  int i;

  // read sub channel information
  memset(cmd, 0, 10);
  cmd[0] = 0x42; // READ SUB CHANNEL
  cmd[2] = 0x40; // get sub channel data
  cmd[3] = 0x02; // get media catalog number
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-2, "Cannot read sub channel data.");
    return 0;
  }

  if (data[0x08] & 0x80) {
    for (i = 0; i < 13; i++) {
      catalog[i] = data[0x09 + i];
    }
    catalog[13] = 0;

    if (toc->catalog(catalog) == 0) {
      return 1;
    }
  }

  return 0;
}

// plays one audio block starting from given position
void PlextorReader::playAudioBlock(long start, long len)
{
  unsigned char cmd[10];

  // play one audio block
  memset(cmd, 0, 10);
  cmd[0] = 0x45; // PLAY AUDIO
  cmd[2] = start >> 24;
  cmd[3] = start >> 16;
  cmd[4] = start >> 8;
  cmd[5] = start;
  cmd[7] = len >> 8;
  cmd[8] = len;

  if (sendCmd(cmd, 10, NULL, 0, NULL, 0) != 0) {
    message(-2, "Cannot play audio block.");
    return;
  }
}

int PlextorReader::analyzeTrack(TrackData::Mode mode, int trackNr,
				long startLba, long endLba,
				Msf *index, int *indexCnt, long *pregap,
				char *isrcCode, unsigned char *ctl)
{
  int ret = analyzeTrackSearch(mode, trackNr, startLba, endLba,
			       index, indexCnt, pregap, isrcCode, ctl);

  if (mode == TrackData::AUDIO)
    readIsrc(trackNr, isrcCode);
  else
    *isrcCode = 0;

  return ret;
}

// tries to read ISRC code of given track number and stores it in
// given buffer.
// return: 1 if valid ISRC code was found, else 0
int PlextorReader::readIsrc(int trackNr, char *buf)
{
  unsigned char cmd[10];
  unsigned short dataLen = 0x30;
  unsigned char data[0x30];
  int i;

  // time to finish the last audio play operation
  sleep(1);

  buf[0] = 0;

  // read sub channel information
  memset(cmd, 0, 10);
  cmd[0] = 0x42; // READ SUB CHANNEL
  cmd[2] = 0x40; // get sub channel data
  cmd[3] = 0x03; // get ISRC code
  cmd[6] = trackNr;
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-1, "Cannot read ISRC code.");
    return 0;
  }

  if (data[0x08] & 0x80) {
    for (i = 0; i < 12; i++) {
      buf[i] = data[0x09 + i];
    }
    buf[12] = 0;

  }

  return 0;
}

// Reads actual track number, index and relative position from sub channel.
// return: 0 OK, 1: SCSI command failed
int PlextorReader::readSubChannelData(int *trackNr, int *indexNr,
				      long *relPos, unsigned char *ctl)
{
  unsigned char cmd[10];
  unsigned short dataLen = 0x30;
  unsigned char data[0x30];

  // read sub channel information
  memset(cmd, 0, 10);
  cmd[0] = 0x42; // READ SUB CHANNEL
  cmd[2] = 0x40; // get sub channel data
  cmd[3] = 0x01; // get sub Q channel data
  cmd[6] = 0;
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-2, "Cannot read sub Q channel data.");
  }

  *trackNr = data[6];
  *indexNr = data[7];
  *relPos = 0;
  *relPos |= data[0x0c] << 24;
  *relPos |= data[0x0d] << 16;
  *relPos |= data[0x0e] << 8;
  *relPos |= data[0x0f];
  
  if (ctl != NULL) {
    *ctl = data[5] & 0x0f;
  }

  return 0;
}

// Retrieves track and index at given block address 'lba'.
// Return: 0: OK, 1: SCSI command failed
int PlextorReader::getTrackIndex(long lba, int *trackNr, int *indexNr,
				 unsigned char *ctl)
{
  long relPos;

  playAudioBlock(lba, 1);

  return readSubChannelData(trackNr, indexNr, &relPos, ctl);
}

CdRawToc *PlextorReader::getRawToc(int sessionNr, int *len)
{
  unsigned char cmd[10];
  unsigned short dataLen;
  unsigned char *data = NULL;;
  unsigned char reqData[4]; // buffer for requestion the actual length
  unsigned char *p = NULL;
  int i, entries;
  CdRawToc *rawToc;
  
  assert(sessionNr >= 1);

  // read disk toc length
  memset(cmd, 0, 10);
  cmd[0] = 0x43; // READ TOC
  cmd[6] = sessionNr;
  cmd[8] = 4;
  cmd[9] |= 2 << 6; // get Q subcodes

  if (sendCmd(cmd, 10, NULL, 0, reqData, 4) != 0) {
    message(-2, "Cannot read raw disk toc.");
    return NULL;
  }

  dataLen = ((reqData[0] << 8) | reqData[1]) + 2;

  message(4, "Raw toc data len: %d", dataLen);

  data = new (unsigned char)[dataLen];
  
  // read disk toc
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    message(-2, "Cannot read raw disk toc.");
    delete[] data;
    return NULL;
  }

  entries = (((data[0] << 8) | data[1]) - 2) / 11;


  rawToc = new CdRawToc[entries];

  for (i = 0, p = data + 4; i < entries; i++, p += 11 ) {
#if 0
    message(0, "%d %02x %02d %2x %02x:%02x:%02x %02x %02x:%02x:%02x",
	    p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10]);
#endif
    rawToc[i].sessionNr = p[0];
    rawToc[i].adrCtl = p[1];
    rawToc[i].point = p[3];
    rawToc[i].pmin = p[8];
    rawToc[i].psec = p[9];
    rawToc[i].pframe = p[10];
  }

  delete[] data;

  *len = entries;

  return rawToc;
}

long PlextorReader::readTrackData(TrackData::Mode mode,
				  TrackData::SubChannelMode,
				  long lba, long len, unsigned char *buf)
{
  unsigned char cmd[10];
  long blockLen = 2340;
  long i;
  TrackData::Mode actMode;
  int ok = 0;
  const unsigned char *sense;
  int senseLen;
  int softError;

  if (setBlockSize(blockLen) != 0)
    return 0;

  memset(cmd, 0, 10);

  cmd[0] = 0x28; // READ10
  cmd[2] = lba >> 24;
  cmd[3] = lba >> 16;
  cmd[4] = lba >> 8;
  cmd[5] = lba;

  while (len > 0 && !ok) {
    cmd[7] = len >> 8;
    cmd[8] = len;

    memset(transferBuffer_, 0, len * blockLen);
    switch (sendCmd(cmd, 10, NULL, 0, transferBuffer_, len * blockLen, 0)) {
    case 0:
      ok = 1;
      break;

    case 2:
      softError = 0;
      sense = scsiIf_->getSense(senseLen);

      if (senseLen > 0x0c) {
	if ((sense[2] &0x0f) == 5) { // Illegal request
	  switch (sense[12]) {
	  case 0x63: // End of user area encountered on this track
	  case 0x64: // Illegal mode for this track
	    softError = 1;
	    break;
	  }
	}
	else if ((sense[2] & 0x0f) == 3) { // Medium error
	  switch (sense[12]) {
	  case 0x02: // No seek complete, sector not found
	  case 0x11: // L-EC error
	    return -2;
	    break;
	  }
	}
      }

      if (!softError) {
	scsiIf_->printError();
	return -1;
      }
      break;

    default:
      message(-2, "Read error at LBA %ld, len %ld", lba, len);
      return -1;
      break;
    }

    if (!ok) {
      len--;
    }
  }

  unsigned char *sector = transferBuffer_;
  for (i = 0; i < len; i++) {
    actMode = determineSectorMode(sector);

    if (!(actMode == mode ||
	  (mode == TrackData::MODE2_FORM_MIX &&
	   (actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)) ||

	  (mode == TrackData::MODE1_RAW && actMode == TrackData::MODE1) ||

	  (mode == TrackData::MODE2_RAW &&
	   (actMode == TrackData::MODE2 ||
	    actMode == TrackData::MODE2_FORM1 ||
	    actMode == TrackData::MODE2_FORM2)))) {
      message(4, "Stopped because sector with not matching mode %s found.",
	      TrackData::mode2String(actMode));
      message(4, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
	      sector[0], sector[1], sector[2], sector[3],  sector[4],
	      sector[5], sector[6], sector[7], sector[8], sector[9],
	      sector[10], sector[11]);
      return i;
    }

    if (buf != NULL) {
      switch (mode) {
      case TrackData::MODE1:
	memcpy(buf, sector + 4, MODE1_BLOCK_LEN);
	buf += MODE1_BLOCK_LEN;
	break;
      case TrackData::MODE2:
      case TrackData::MODE2_FORM_MIX:
	memcpy(buf, sector + 4, MODE2_BLOCK_LEN);
	buf += MODE2_BLOCK_LEN;
	break;
      case TrackData::MODE2_FORM1:
	memcpy(buf, sector + 12, MODE2_FORM1_DATA_LEN);
	buf += MODE2_FORM1_DATA_LEN;
	break;
      case TrackData::MODE2_FORM2:
	memcpy(buf, sector + 12, MODE2_FORM2_DATA_LEN);
	buf += MODE2_FORM2_DATA_LEN;
	break;
      case TrackData::MODE1_RAW:
      case TrackData::MODE2_RAW:
	memcpy(buf, syncPattern, 12);
	memcpy(buf + 12, sector, 2340);
	buf += AUDIO_BLOCK_LEN;
	break;
      case TrackData::MODE0:
      case TrackData::AUDIO:
	message(-3, "PlextorReader::readTrackData: Illegal mode.");
	return 0;
	break;
      }
    }

    sector += blockLen;
  }  

  return len;
}

Toc *PlextorReader::readDiskToc(int session, const char *audioFilename)
{
  Toc *toc = CdrDriver::readDiskToc(session, audioFilename);

  setBlockSize(MODE1_BLOCK_LEN);
  
  return toc;
}

Toc *PlextorReader::readDisk(int session, const char *dataFilename)
{
  Toc *toc = CdrDriver::readDisk(session, dataFilename);

  setBlockSize(MODE1_BLOCK_LEN);
  
  return toc;
}

// Creates 'Toc' object for inserted CD.
// audioFilename: name of audio file that is placed into TOC
// Return: newly allocated 'Toc' object or 'NULL' on error
int PlextorReader::readAudioRangePlextor(ReadDiskInfo *rinfo, int fd,
					 long startLba, long endLba,
					 int startTrack, int endTrack, 
					 TrackInfo *info)
{
  int i;
  int ret;
  //unsigned char trackCtl; // control nibbles of track
  //int ctlCheckOk;
 
  int blockLength = AUDIO_BLOCK_LEN + 1 + 294 + 96;
  long  blocksPerRead = scsiIf_->maxDataLen() / blockLength;
  long stampLba;

  // first, last, current and number of recorded tracks
  int fat=-1,lat=-1,cat,nat=0;

  // first, last, number, current, remaining and read (audio blocks)
  long fab,lab,nab,cab,rab,n;
  
  // SCSI command 
  unsigned char cmd[12];   
  unsigned char *data;

  // number of byte errors in current audio block (cab)
  int cabFaulty;

  // number of retries and goods of current audio block (cab)
  unsigned long cabRetries=0,cabGoods=0,scsiRetries=0;

  // audio repair block
  unsigned char repairBlock[AUDIO_BLOCK_LEN + 1 + 294 + 96];
  //unsigned char referBlock[AUDIO_BLOCK_LEN + 1 + 294 + 96];
  int repairErrors=-1;  

  int overspeed,cai=-1;

  data = new (unsigned char)[blocksPerRead * blockLength];  

  fat = startTrack;
  lat = endTrack;
  nat = endTrack - startTrack + 1;

  info[lat].bytesWritten = 0;

  // set first, last and number of audio blocks
  fab = startLba;
  lab = endLba;

  nab=lab-fab;

  message(1, "Reading CDDA tracks in range [%lu,%lu].", fat+1,lat+1);

  cat = fat + 1;
 
  message(2, "Reading CDDA blocks in range [%lu,%lu>"
    " (%lu blocks=%lu bytes).",fab,lab,nab,nab*2352);

  memset(cmd,0,12);
  cmd[0]=0xd8;
  cmd[10]=0x09; 
 
#if 0 // for testing purposes I set the position/length of a scratch here
  fab+=3000;
  nab=921;
#endif

  // set current audio block
  cab=fab;
  // set remaining audio blocks
  rab=nab;

  stampLba = cab;

  // start optimistic
  speed(overspeed=20);


  // while still audio blocks left to read
  while (rab != 0)
  { 
    // the maximum number of blocks to read, or just those which are left
    n=(rab > blocksPerRead ? blocksPerRead : rab);
    
    // set starting audio block
    cmd[2] = cab >> 24;
    cmd[3] = cab >> 16;
    cmd[4] = cab >> 8;
    cmd[5] = cab;
    // set number of blocks
    cmd[6] = n >> 24;
    cmd[7] = n >> 16;
    cmd[8] = n >> 8;
    cmd[9] = n;

    message(1, "block %6ld\r",cab);

    if (remote_) {
      if (cab > stampLba + 75) {
	long totalLen = info[cat + 1].start - info[cat].start;
	long progress = cab - info[cat].start;
	long totalProgress;

	if (progress > 0) {
	  progress *= 1000;
	  progress /= totalLen;
	}
	else {
	  progress = 0;
	}

	totalProgress = cab - rinfo->startLba;

	if (totalProgress > 0) {
	  totalProgress *= 1000;
	  totalProgress /= rinfo->endLba - rinfo->startLba;
	}
	else {
	  totalProgress = 0;
	}

	sendReadCdProgressMsg(RCD_EXTRACTING, rinfo->tracks, cat + 1, progress,
			      totalProgress);

	stampLba = cab;
      }
    }

    // SCSI command failed?
    while (sendCmd(cmd,12,NULL,0,data,n*blockLength))
    {
      scsiRetries++;
      if (cabRetries==0)
      {
        if (scsiRetries==10)
        {
          delete[] data;
          return 1;
        }
      }
      else
      {
        scsiRetries=0;
        //cabRetries++;
        break;
      }
    }

    // iterate through the read audio blocks
    for (i=0;i<n;i++)
    {
      // create subchannel object
      PWSubChannel96 chan(data + i*blockLength + AUDIO_BLOCK_LEN + 1 + 294); 
 
      // q subchannel error?
      if (!chan.checkCrc())
      {
        // TODO: Implement heuristic subchannel error correction here
      }
      // q subchannel ok?
      if (chan.checkCrc() && chan.checkConsistency())
      {
        // q subchannel contains mode 1 data?
        if(chan.type() == SubChannel::QMODE1DATA)
	{
          // track relative time as it occurs in subchannel
          Msf qRelTime(chan.min(),chan.sec(),chan.frame());
          // disc absolute time as it occurs in subchannel
          Msf qAbsTime(chan.amin(),chan.asec(),chan.aframe());

          if ((chan.indexNr()!=cai) || (chan.trackNr()!=cat))
          {
            message(2, "(track,index,absolute)=(%2d,%2d,%s)",
              chan.trackNr(),chan.indexNr(),qAbsTime.str());

            // first track start?
            if ((chan.trackNr()==1) && (chan.indexNr()==1))
            {
              message(1, "Found track number %02d at %s (block %d).",
		      chan.trackNr(),qAbsTime.str(),cab);
              if ((qAbsTime.lba()-150)!=info[0].start)
              {
                message(2, "TOC SAYS TRACK STARTS AT %s!",
                  Msf(info[0].start).str());
              }
            }
            // next track start?
            else if ((chan.trackNr()==(cat+1)) && (chan.indexNr()==1))
            {
              message(1, "Found track number %02d at %s.",
                chan.trackNr(),qAbsTime.str());
              if ((qAbsTime.lba()-150)!=info[cat].start)
              {
                message(2, "TOC SAYS TRACK STARTS AT %s!",
			Msf(info[cat].start).str());
              }
            }
            // pregap of next track?
            else if ((chan.trackNr()==(cat+1)) && (chan.indexNr()==0))
            {
              message(2, "Found pregap of track %02d with size %s at ",
		      chan.trackNr(), qRelTime.str());
	      message(2, "%s", qAbsTime.str());
	      if (cat <= lat) {
		info[cat].pregap = qRelTime.lba();
	      }
            }
            // index increment?
            else if ((chan.trackNr()==cat) && (chan.indexNr()==(cai+1)))
            {
              if (chan.indexNr() > 1)
              {
		message(2, "Found index number %02d at %s.",
			chan.indexNr(),qAbsTime.str());

		int indexCnt = info[cat - 1].indexCnt;
		if (cat - 1 <= lat && indexCnt < 98) {
		  info[cat - 1].index[indexCnt] = qRelTime.lba();
		  info[cat - 1].indexCnt += 1;
		}
              }
            }
            else if (chan.trackNr()==0xAA)
            {
              message(2, "LEAD OUT TRACK ENCOUNTERED!");
              n=0;
              i=0;
              rab=0;
              cab-=1;
              lab=cab;
              break;
            }
            else if ((chan.trackNr()==cat) && (chan.indexNr()==cai))
            {
            }
            else
            {
              message(1, "UNEXPECTED!");
            }
            cat=chan.trackNr();
            cai=chan.indexNr();
          }
        }
	else if (chan.type() == SubChannel::QMODE3)
        {
	  // subchannel contains ISRC code
	  if (info[cat - 1].isrcCode[0] == 0) {
	    memcpy(info[cat - 1].isrcCode, chan.isrc(), 13);
	    message(2, "Found ISRC code of track %02d.", cat);
	  }
	}
      }
      // erronous subchannel data     
      else
      {
      }
 
      // read CIRC error indicator
      cabFaulty=data[i*blockLength+AUDIO_BLOCK_LEN];

      // only errorless blocks or often retried blocks are accepted
      if ((cabFaulty==0) || (repairErrors==0) || (cabRetries>80))
      {
        unsigned char *block;
        // the current audio block in the data buffer is perfect
        if (cabFaulty==0)
        {
          block=data + i*blockLength;
          // write this block's audio data
          //fwrite(data + i*blockLength,1,AUDIO_BLOCK_LEN,fd);
          // increment number of consecutive good blocks
          cabGoods++;  
          if (cabRetries)
          {
            message(1,"block %6ld read and written without errors now. \n", cab);
          }
        }
        // we have a fully repaired block
        else if (repairErrors==0)
        {
          // write this block's audio data
          block=repairBlock;
          //fwrite(repairBlock,1,AUDIO_BLOCK_LEN,fd);
          message(1,"block %6ld written after fully being repaired...", cab);
        }
        // we have a partially repaired block
        else if (cabRetries>=0)
        {
          // write this block's audio data
          block=repairBlock;
	  //fwrite(repairBlock,1,AUDIO_BLOCK_LEN,fd);
          message(1,"block %6ld written with %4ld erronous bytes in it!", cab, repairErrors);
        }
        else
        {
           message(1,"PROGRAM BUG!!!! BUG #1! ENTERED ILLEGAL STATE\n");
        }

#if 1 // not if we test please
	if (options_ & OPT_DRV_SWAP_READ_SAMPLES)
	  swapSamples((Sample*)block, SAMPLES_PER_BLOCK);

	if ((ret = fullWrite(fd, block, AUDIO_BLOCK_LEN)) != AUDIO_BLOCK_LEN) {
	  if (ret < 0)
	    message(-2, "Writing of data failed: %s", strerror(errno));
	  else
	    message(-2, "Writing of data failed: Disk full");

          delete[] data;
          return 1;
	}
	info[lat].bytesWritten += AUDIO_BLOCK_LEN;
#endif

        repairErrors=-1;
        cabRetries=0;
        // proceed to next audio block
        //message(0,"PROCEEDING TO NEXT BLOCK");
        cab++;
        rab--;
        if (cabGoods == 1000)
        {
          if (overspeed<20)
          {
            overspeed=((overspeed*2)>=20?20:overspeed*2);
            // crank up the speed again
            // message(0,"Speeding up to %ld overspeed...\r",overspeed);
            speed(overspeed);
            cabGoods=0;
          }
        }
      }
      else
      // the current block contains errors
      {
        int j;
        // first time error on this block?
        if (cabRetries==0)
        {
          // copy faulty block into our repair buffer
          memcpy(repairBlock,data+i*blockLength,AUDIO_BLOCK_LEN+1+294+96);
          // slow down to lower error rate (we hope so!?)
          if (overspeed>1) overspeed/=2;
          // message(0,"Slowing down to %ld...\r",overspeed);
          speed(overspeed);
        }
        // iterate through all audio samples, merge all good samples into
        // a repair audio block

        // Implementor's note: The following info is NOT mentioned in the
        // official Plextor documentation I have, but IS confirmed by
        // Plextor. -- Leon Woestenberg <leon@stack.nl>.

        // NOTE: Any two bytes of a sample (or both) can be damaged.
        // Although the error bit flag can be set for just one byte, the
        // other byte can be changed due to the fact that DSP interpolation
        // is applied to the whole (two-byte) sample.
        // THEREFORE, any of the two bits relating to a two-byte sample
        // indicate the WHOLE sample is incorrect, and we will try to
        // repair the whole sample, not just the one byte.
        // THIS will ensure we have a 100% bit-wise accurate copy of
        // the audio data. 

        repairErrors=0;
        for (j=0;j<AUDIO_BLOCK_LEN;j+=2) // iterate by steps of 1 sample
        {
          // sample erronous in repair block? (bits 7-j and/or 6-j set?)
          if (repairBlock[AUDIO_BLOCK_LEN+1+(j/8)] & (192>>(j%8)))
          {
            // sample good in fresh data block?
            if (!(data[i*blockLength+AUDIO_BLOCK_LEN+1+(j/8)] & (192>>(j%8))))
            {
#if 0
              printf("\nerrenous sample %d (bytes %d/%d) being repaired with %04x",
                j/2,j,j+1,data[i*blockLength+j]*256+data[i*blockLength+j+1]);
#endif
              // then copy it into the repairBlock;
              repairBlock[j]=data[i*blockLength+j];
              repairBlock[j+1]=data[i*blockLength+j+1];
              // clear error bit in repair block
              repairBlock[AUDIO_BLOCK_LEN+1+(j/8)] &= (~(192>>(j%8)));
            }
            // erronous sample could not be replaced by the good one
            else
            {
#if 0
              printf("%d, ",j/2);
#endif
              // increment number of repair block erronous samples
              repairErrors++; 
            }
#if 0
              message(0,"bits %ld/%ld (bits %ld/%ld of byte %ld) was 1",
                j,j+1,7-(j%8),6-(j%8),j/8);
#endif
          }
#if 0
          else
          {
            message(0,"bits %ld/%ld (bits %ld/%ld of byte %ld) were 0",
              j,j+1,7-(j%8),6-(j%8),j/8);
          }
#endif
        }
        if (cabRetries==0)
        {
          message(1,"\nblock %6ld has %3ld error samples\r",
            cab,repairErrors);
        }
        else
        {
          message(1,"block %6ld has %3ld error samples left after %3ld retries\r",
            cab,repairErrors,cabRetries);
        }
        // increase number of retries
        cabRetries++;
        cabGoods=0;
        // break out of for-loop (into while-loop) to re-read current block
        break;
      }
    }
  }  
 
  delete[] data;

  return 0;
}

int PlextorReader::readSubChannels(TrackData::SubChannelMode,
				   long lba, long len, SubChannel ***chans,
				   Sample *audioData)
{
  unsigned char cmd[12];
  int cmdLen;
  int tries = 5;
  int ret;


  memset(cmd, 0, 12);

  if (options_ & OPT_PLEX_DAE_READ10) {
    if (setBlockSize(AUDIO_BLOCK_LEN) != 0)
      return 1;

    cmd[0] = 0x28; // READ10
    cmd[2] = lba >> 24;
    cmd[3] = lba >> 16;
    cmd[4] = lba >> 8;
    cmd[5] = lba;
    cmd[7] = len >> 8;
    cmd[8] = len;

    cmdLen = 10;
  }
  else if (options_ & OPT_PLEX_DAE_D4_12) {
    cmd[0] = 0xd4;
    cmd[2] = lba >> 24;
    cmd[3] = lba >> 16;
    cmd[4] = lba >> 8;
    cmd[5] = lba;
    cmd[8] = len >> 8;
    cmd[9] = len;

    cmdLen = 12;
  }
  else {
    cmd[0]=0xd8; // READ CDDA
    cmd[2] = lba >> 24;
    cmd[3] = lba >> 16;
    cmd[4] = lba >> 8;
    cmd[5] = lba;
    cmd[6] = len >> 24;
    cmd[7] = len >> 16;
    cmd[8] = len >> 8;
    cmd[9] = len;

    cmdLen = 12;
  }

  do {
    ret = sendCmd(cmd, cmdLen, NULL, 0, 
		  (unsigned char*)audioData, len * AUDIO_BLOCK_LEN,
		  (tries == 1) ? 1 : 0);

    if (ret != 0 && tries == 1) {
      message(-2, "Reading of audio data failed at sector %ld.", lba);
      return 1;
    }
    
    tries--;
  } while (ret != 0 && tries > 0);

  *chans = NULL;
  return 0;
}

int PlextorReader::readAudioRange(ReadDiskInfo *rinfo, int fd, long start,
				  long end, int startTrack, int endTrack, 
				  TrackInfo *info)
{
  if (model_ != 0 && (options_ & OPT_PLEX_USE_PARANOIA) == 0) {
    // we have a plextor drive -> use the special plextor method which
    // will also detect pre-gaps, index marks and ISRC codes
    return readAudioRangePlextor(rinfo, fd, start, end, startTrack, endTrack,
				 info);
  }
  
  if (!onTheFly_) {
    int t, i;
    long pregap = 0;
    int indexCnt = 0;
    Msf index[98];
    unsigned char ctl;
    long slba, elba;

    message(1, "Analyzing...");
    
    for (t = startTrack; t <= endTrack; t++) {
      long totalProgress;

      message(1, "Track %d...", t + 1);

      totalProgress = t * 1000;
      totalProgress /= rinfo->tracks;
      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 0,
			    totalProgress);


      if (!fastTocReading_) {
	if (pregap != 0)
	  message(2, "Found pre-gap: %s", Msf(pregap).str());

	slba = info[t].start;
	if (info[t].mode == info[t + 1].mode)
	  elba = info[t + 1].start;
	else
	  elba = info[t + 1].start - 150;
	
	pregap = 0;
	if (analyzeTrackSearch(TrackData::AUDIO, t + 1, slba, elba,
			       index, &indexCnt, &pregap, info[t].isrcCode,
			       &ctl) != 0)
	  return 1;
	
	for (i = 0; i < indexCnt; i++)
	  info[t].index[i] = index[i].lba();
	
	info[t].indexCnt = indexCnt;
	
	if (t < endTrack)
	  info[t + 1].pregap = pregap;
      }
      else {
	info[t].indexCnt = 0;
	info[t + 1].pregap = 0;
      }
      
      info[t].isrcCode[0] = 0;
      readIsrc(t + 1, info[t].isrcCode);
      if (info[t].isrcCode[0] != 0)
	message(2, "Found ISRC code.");

      totalProgress = (t + 1) * 1000;
      totalProgress /= rinfo->tracks;

      sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 1000,
			    totalProgress);
    }

    message(1, "Reading...");
  }

  return CdrDriver::readAudioRangeParanoia(rinfo, fd, start, end, startTrack,
					   endTrack, info);
}
