Blob Blame History Raw
/*  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 <string.h>
#include <assert.h>

#include "CDD2600.h"
#include "SubChannel.h"
#include "PQSubChannel16.h"

#include "Toc.h"
#include "log.h"

CDD2600::CDD2600(ScsiIf *scsiIf, unsigned long options)
  : CdrDriver(scsiIf, options|OPT_DRV_NO_PREGAP_READ), CDD2600Base(this)
{
  driverName_ = "CDD2600 - Version 1.1";
  
  leadInLength_ = leadOutLength_ = 0;
  speed_ = 2;
  simulate_ = true;
  encodingMode_ = 0;

  // reads big endian samples
  audioDataByteOrder_ = 1;

  memset(&diskInfo_, 0, sizeof(DiskInfo));
}

CDD2600::~CDD2600()
{
}

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

// sets speed
// return: 0: OK
//         1: illegal speed
int CDD2600::speed(int s)
{
  if (s >= 0 && s <= 2) {
    speed_ = s;
    return 0;
  }
  else if (s > 2) {
    speed_ = 2;
    return 0;
  }
  else {
    return 1;
  }
}

// loads ('unload' == 0) or ejects ('unload' == 1) tray
// return: 0: OK
//         1: scsi command failed

int CDD2600::loadUnload(int unload) const
{
  unsigned char cmd[10];

  memset(cmd, 0, 10);

  cmd[0] = 0xe7; // MEDIUM LOAD/UNLOAD
  if (unload) {
    cmd[8] |= 0x01;
  }
  
  if (sendCmd(cmd, 10, NULL, 0, NULL, 0) != 0) {
    log_message(-2, "Cannot load/unload medium.");
    return 1;
  }

  return 0;
}

// sets various audio play parameters, output channels are set to stereo mode
// and given volume
// immediate: 0: wait until audio play command finished
//            1: command finishs immediately after playback has started
// sotc:      0: play across track boundaries
//            1: stop playing at track boundaries
int CDD2600::modeSelectPlay(int immediate, int sotc, unsigned char volume)
{
  unsigned char mp[16];

  memset(mp, 0, 16);

  mp[0] = 0x0e; // PLAY page code
  mp[1] = 14; // parameter length
  if (immediate != 0) {
    mp[2] |= 0x04;
  }
  if (sotc != 0) {
    mp[2] |= 0x02;
  }
  mp[8]  = 1;
  mp[9]  = volume;
  mp[10] = 2;
  mp[11] = volume;

  if (setModePage(mp, NULL, NULL, 1) != 0) {
    log_message(-2, "Cannot set play parameters.");
    return 1;
  }

  return 0;
}

int CDD2600::initDao(const Toc *toc)
{
  long n;
  blockLength_ = AUDIO_BLOCK_LEN;
  blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_;

  assert(blocksPerWrite_ > 0);

  toc_ = toc;

  diskInfo();

  if (!diskInfo_.valid.empty || !diskInfo_.valid.append) {
    log_message(-2, "Cannot determine status of inserted medium.");
    return 1;
  }

  if (!diskInfo_.append) {
    log_message(-2, "Inserted medium is not appendable.");
    return 1;
  }

  if (modeSelectBlockSize(blockLength_, 1) != 0 ||
      modeSelectSpeed(-1, speed_, simulate_, 1) != 0 ||
      modeSelectCatalog(toc_) != 0 ||
      readSessionInfo(&leadInLength_, &leadOutLength_, 1) != 0) {
    return 1;
  }

  // allocate buffer for write zeros
  n = blocksPerWrite_ * blockLength_;
  delete[] zeroBuffer_;
  zeroBuffer_ = new char[n];
  memset(zeroBuffer_, 0, n);

  return 0;
}

int CDD2600::startDao()
{
  long lba;

  if (writeSession(toc_, multiSession_, diskInfo_.thisSessionLba) != 0) {
    return 1;
  }

  log_message(2, "Writing lead-in and gap...");

  lba = diskInfo_.thisSessionLba - 150 - leadInLength_;

  // write lead-in
  if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, lba + 150,
		 leadInLength_) != 0) {
    flushCache();
    return 1;
  }

  log_message(5, "Lba after lead-in: %ld", lba);

  // write gap (2 seconds)
  if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE,
		 lba, lba + 150, 150) != 0) {
    flushCache();
    return 1;
  }

  log_message(2, "");

  return 0;
}

int CDD2600::finishDao()
{
  long lba = diskInfo_.thisSessionLba + toc_->length().lba();

  log_message(2, "Writing lead-out...");

  // write lead-out
  if (writeZeros(toc_->leadOutMode(), TrackData::SUBCHAN_NONE, lba, lba + 150,
		 leadOutLength_) != 0) {
    flushCache();
    return 1;
  }

  log_message(2, "\nFlushing cache...");
  
  if (flushCache() != 0) {
    return 1;
  }

  log_message(2, "");

  blockLength_ = MODE1_BLOCK_LEN;
  modeSelectBlockSize(blockLength_, 1);

  delete[] zeroBuffer_, zeroBuffer_ = NULL;

  return 0;
}

void CDD2600::abortDao()
{
  flushCache();

  blockLength_ = MODE1_BLOCK_LEN;
  modeSelectBlockSize(blockLength_, 1);
}

// Writes data to target, the block length depends on the actual writing mode
// and is stored internally. 'len' is number of blocks to write.
// 'lba' specifies the next logical block address for writing and is updated
// by this function but not used for writing
// return: 0: OK
//         1: scsi command failed
int CDD2600::writeData(TrackData::Mode mode, TrackData::SubChannelMode sm,
		       long &lba, const char *buf, long len)
{
  assert(blocksPerWrite_ > 0);
  assert(blockLength_ > 0);
  assert(mode == TrackData::AUDIO);
  int nwritten = 0;
  int writeLen = 0;
  unsigned char cmd[10];

  memset(cmd, 0, 10);
  cmd[0] = 0x2a; // WRITE1
  
  while (len > 0) {
    writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len);

    cmd[7] = writeLen >> 8;
    cmd[8] = writeLen & 0xff;

    if (sendCmd(cmd, 10, (unsigned char *)(buf + (nwritten * blockLength_)),
		writeLen * blockLength_, NULL, 0) != 0) {
      log_message(-2, "Write data failed.");
      return 1;
    }

    lba += writeLen;

    len -= writeLen;
    nwritten += writeLen;
  }
      
  return 0;
}

Toc *CDD2600::readDiskToc(int session, const char *audioFilename)
{

  blockLength_ = AUDIO_BLOCK_LEN;
  if (modeSelectBlockSize(blockLength_, 1) != 0) {
    return NULL;
  }

  modeSelectSpeed(2, -1, 1, 0);

  Toc *toc = CdrDriver::readDiskToc(session, audioFilename);

  setBlockSize(MODE1_BLOCK_LEN);
  
  return toc;
}

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

  setBlockSize(MODE1_BLOCK_LEN);

  return toc;
}

int CDD2600::readIsrc(int trackNr, char *buf)
{
  unsigned char cmd[10];
  unsigned short dataLen = 0x30;
  unsigned char data[0x30];
  int i;

  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) {
    log_message(-1, "Cannot read ISRC code.");
    return 1;
  }
  else {
    if (data[0x08] & 0x80) {
      for (i = 0; i < 12; i++) {
	buf[i] = data[0x09 + i];
      }
      buf[12] = 0;
    }
  }

  return 0;
}

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

int CDD2600::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) {
    log_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;
}

int CDD2600::analyzeTrack(TrackData::Mode mode, int trackNr, long startLba,
			  long endLba,
			  Msf *index, int *indexCnt, long *pregap,
			  char *isrcCode, unsigned char *ctl)
{
  blockLength_ = AUDIO_BLOCK_LEN;
  modeSelectBlockSize(blockLength_, 1);

  int ret = analyzeTrackSearch(mode, trackNr, startLba, endLba,
			       index, indexCnt, pregap, isrcCode, ctl);

  *isrcCode = 0;

  if (mode == TrackData::AUDIO) {
    // read ISRC code from sub channel
    readIsrc(trackNr, isrcCode);
  }

  return ret;
}

int CDD2600::getTrackIndex(long lba, int *trackNr, int *indexNr, 
			   unsigned char *ctl)
{
  long relPos;

  readBlock(lba);

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

int CDD2600::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] = 0x00; // get sub Q channel data
  cmd[6] = 0;
  cmd[7] = dataLen >> 8;
  cmd[8] = dataLen;

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

  *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;
}

// reads a single block of length 'blockLength_' from given sector
// return: 0: OK
//         1: error occured
void CDD2600::readBlock(unsigned long sector)
{
  unsigned char cmd[10];
  unsigned long dataLen = 2 * blockLength_;
  unsigned char *data = new unsigned char[dataLen];


  // read sub channel information
  memset(cmd, 0, 10);
  cmd[0] = 0x28; // READ10
  cmd[2] = sector >> 24;
  cmd[3] = sector >> 16;
  cmd[4] = sector >> 8;
  cmd[5] = sector;
  cmd[7] = 0;
  cmd[8] = 2;

  if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) {
    log_message(-2, "Cannot read block - ignored.");
  }

  delete[] data;
}

int CDD2600::nextWritableAddress(long *lba, int showError)
{
  unsigned char cmd[10];
  unsigned char data[6];

  memset(data, 0, 6);
  memset(cmd, 0, 10);

  cmd[0] = 0xe2; // FIRST WRITABLE ADDRESS
  cmd[3] = 1 /*<< 2*/; // AUDIO
  // cmd[7] = 1; // NPA
  cmd[8] = 6; // allocation length

  if (sendCmd(cmd, 10, NULL, 0, data, 6, showError) != 0) {
    if (showError)
      log_message(-2, "Cannot retrieve next writable address.");
    return 1;
  }

  *lba = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4];

  return 0;
}

// Retrieve disk information.
// return: DiskInfo structure or 'NULL' on error
DiskInfo *CDD2600::diskInfo()
{
  unsigned char cmd[10];
  unsigned char data[34];
  long nwa;
  int i;

  memset(&diskInfo_, 0, sizeof(DiskInfo));

  if (readCapacity(&(diskInfo_.capacity), 0) == 0) {
    diskInfo_.valid.capacity = 1;
  }
  
  if (readSessionInfo(&leadInLength_, &leadOutLength_, 0) == 0) {
    diskInfo_.valid.manufacturerId = 1;
    
    // start time of lead-in
    diskInfo_.manufacturerId = Msf(450150 - leadInLength_ - 150 );
    diskInfo_.append = 1; // this is for the CDD2000 which does not support
                         // READ DISK INFORMATION
  }
  else {
    diskInfo_.append = 0; // this is for the CDD2000 which does not support
                         // READ DISK INFORMATION
  }
  diskInfo_.valid.empty = 1;
  diskInfo_.valid.append = 1;

  memset(cmd, 0, 10);
  memset(data, 0, 4);

  cmd[0] = 0x43; // READ TOC
  cmd[6] = 0;
  cmd[8] = 4;

  if (sendCmd(cmd, 10, NULL, 0, data, 4, 0) == 0) {
    log_message(5, "First track %u, last track %u", data[2], data[3]);
    diskInfo_.lastTrackNr = data[3];
  }
  else {
    log_message(5, "READ TOC (format 0) failed.");
  }

  if (diskInfo_.lastTrackNr > 0) {
    // the lead-in length does not specify the manufacturer ID anymore
    diskInfo_.valid.manufacturerId = 0;

    diskInfo_.empty = 0; // CD-R is not empty
    diskInfo_.diskTocType = 0xff; // undefined

    if (diskInfo_.lastTrackNr < 99 && nextWritableAddress(&nwa, 0) == 0) {
      log_message(5, "NWA: %ld", nwa);
      diskInfo_.thisSessionLba = nwa;
      diskInfo_.append = 1;
    }
    else {
      diskInfo_.append = 0;
    }  

    
    memset(cmd, 0, 10);
    memset(data, 0, 12);

    cmd[0] = 0x43; // READ TOC
    cmd[6] = 0;
    cmd[8] = 12;
    cmd[9] = 1 << 6;

    if (sendCmd(cmd, 10, NULL, 0, data, 12) == 0) {
      diskInfo_.sessionCnt = data[3];
      diskInfo_.lastSessionLba = (data[8] << 24) | (data[9] << 16) |
                                 (data[10] << 8) | data[11];

      log_message(5, "First session %u, last session %u, last session start %ld",
	      data[2], data[3], diskInfo_.lastSessionLba);
    }
    else {
      log_message(5, "READ TOC (format 1) failed.");
    }

    if (diskInfo_.sessionCnt > 0) {
      int len;
      CdRawToc *toc = getRawToc(diskInfo_.sessionCnt, &len);

      if (toc != NULL) {
	for (i = 0; i < len; i++) {
	  if (toc[i].sessionNr == diskInfo_.sessionCnt) {
#if 0
	    if (toc[i].point == 0xb0 && toc[i].min != 0xff &&
		toc[i].sec != 0xff && toc[i].frame != 0xff) {
	      int m = toc[i].min;
	      int s = toc[i].sec;
	      int f = toc[i].frame;
	      
	      if (m < 90 && s < 60 && f < 75) {
		diskInfo_.thisSessionLba = Msf(m, s, f).lba(); // + 150 - 150
		diskInfo_.thisSessionLba += leadInLength_;
	      }
	    }
#endif
	    if (toc[i].point == 0xa0) {
	      diskInfo_.diskTocType = toc[i].psec;
	    }
	  }

	  // The point C0 entry may be only stored in the first session's
	  // lead-in
	  if (toc[i].point == 0xc0 && toc[i].pmin <= 99 &&
	      toc[i].psec < 60 && toc[i].pframe < 75) {
	    diskInfo_.manufacturerId =
	      Msf(toc[i].pmin, toc[i].psec, toc[i].pframe);
	    diskInfo_.valid.manufacturerId = 1;
	  }
	}

#if 0
	if (diskInfo_.thisSessionLba > 0) {
	  if (diskInfo_.lastTrackNr < 99)
	    diskInfo_.append = 1;
	}
	else {
	  log_message(4, "Did not find BO pointer in session %d.",
		  diskInfo_.sessionCnt);
	  
	}
#endif

	delete[] toc;
      }
      else {
	log_message(5, "getRawToc failed.");
      }
    }
  }
  else {
    // disk is empty and appendable
    diskInfo_.empty = 1;
  }

  if (diskInfo_.append == 0)
    diskInfo_.empty = 0;

  return &diskInfo_;
}

CdRawToc *CDD2600::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;
  CdRawToc *rawToc;
  int entries;

  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) {
    log_message(-2, "Cannot read raw disk toc.");
    return NULL;
  }

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

  log_message(5, "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) {
    log_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
    log_message(0, "%d %02x %02d %2x %02d:%02d:%02d %02x %02d:%02d:%02d",
	    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].min = p[4];
    rawToc[i].sec = p[5];
    rawToc[i].frame = p[6];
    rawToc[i].pmin = p[8];
    rawToc[i].psec = p[9];
    rawToc[i].pframe = p[10];
  }

  delete[] data;

  *len = entries;

  return rawToc;
}

long CDD2600::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;

  /*  
  if (mode == TrackData::MODE2_FORM1 || mode == TrackData::MODE2_FORM2)
    cmd[9] = 1 << 6; // MIX flag
    */

  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) {
	  switch (sense[12]) {
	  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:
      log_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)))) {
      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:
	log_message(-3, "CDD2600::readTrackData: Illegal mode.");
	return 0;
	break;
      }
    }

    sector += blockLen;
  }

  return len;
}

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

  if (setBlockSize(AUDIO_BLOCK_LEN) != 0)
    return 1;

  memset(cmd, 0, 10);

  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;

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

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

  *chans = NULL;
  return 0;
}

int CDD2600::readAudioRange(ReadDiskInfo *rinfo, int fd, long start, long end,
			    int startTrack, int endTrack,
			    TrackInfo *info)
{
  if (!onTheFly_) {
    int t;

    log_message(1, "Analyzing...");

    for (t = startTrack; t <= endTrack; t++) {
      long totalProgress;

      totalProgress = t * 1000;
      totalProgress /= rinfo->tracks;

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

      log_message(2, "Track %d...", t + 1);
      info[t].isrcCode[0] = 0;
      readIsrc(t + 1, info[t].isrcCode);

      if (info[t].isrcCode[0] != 0)
	log_message(2, "Found ISRC code.");

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

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

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

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