/* cdrdao - write audio CD-Rs in disc-at-once mode * * Copyright (C) 1998-2001 Andreas Mueller * * 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 #include #include #include #include "PQChannelEncoder.h" #include "Msf.h" #include "Track.h" #include "util.h" PQChannelEncoder::PQChannelEncoder() { cueSheet_ = NULL; cueSheetLen_ = 0; toc_ = NULL; tocLen_ = 0; discType_ = 0; current_ = NULL; catalog_ = NULL; isrc_ = NULL; } PQChannelEncoder::~PQChannelEncoder() { int i; for (i = 0; i < tocLen_; i++) { delete toc_[i]; } delete[] toc_; delete[] cueSheet_; delete current_; delete catalog_; delete isrc_; } int PQChannelEncoder::setCueSheet(SubChannel *chan, unsigned char discType, const unsigned char *sheet, int len, const Msf &leadInStart) { int tocEnt, i; subChannel_ = chan; // Convert toc type to decimal numbers so that they look like the // corresponding hex value when stored as BCD in the sub-channel switch (discType) { case 0: break; case 0x10: discType = 10; break; case 0x20: discType = 20; break; default: message(-3, "Illegal disc type."); return 1; } if ((len % sizeof(CueSheetEntry)) != 0) { message(-3, "Illegal cue sheet length."); return 1; } discType_ = discType; cueSheetLen_ = len / sizeof(CueSheetEntry); cueSheet_ = new CueSheetEntry[cueSheetLen_ + 1]; memcpy(cueSheet_, sheet, len); // mark end of queue sheet with a zeroed entry memset(cueSheet_ + cueSheetLen_, 0, sizeof(CueSheetEntry)); // create some sub channel objects catalog_ = subChannel_->makeSubChannel(SubChannel::QMODE2); isrc_ = subChannel_->makeSubChannel(SubChannel::QMODE3); current_ = subChannel_->makeSubChannel(SubChannel::QMODE1DATA); if (analyzeCueSheet() != 0) { return 1; } // create PQ sub channels for toc in lead-in tocLen_ = lastTrackNr_ - firstTrackNr_ + 1 + 3/*A0, A1, A2*/; toc_ = new SubChannel*[tocLen_]; for (i = 0; i < tocLen_; i++) { toc_[i] = subChannel_->makeSubChannel(SubChannel::QMODE1TOC); } CueSheetEntry *run; for (run = actCueSheetEntry_, tocEnt = 0; (run->ctlAdr & 0x0f) != 1 || run->trackNr != 0xaa; run++) { if ((run->ctlAdr & 0x0f) == 1 && run->trackNr > 0 && run->trackNr <= 99 && run->indexNr == 1) { toc_[tocEnt]->ctl(run->ctlAdr & 0xf0); toc_[tocEnt]->point(run->trackNr); toc_[tocEnt]->pmin(run->min); toc_[tocEnt]->psec(run->sec); toc_[tocEnt]->pframe(run->frame); tocEnt++; } } toc_[tocEnt]->point(0xa0); toc_[tocEnt]->pmin(firstTrackNr_); toc_[tocEnt]->psec(discType_); toc_[tocEnt]->ctl(firstTrackCtlAdr_ & 0xf0); tocEnt++; toc_[tocEnt]->point(0xa1); toc_[tocEnt]->pmin(lastTrackNr_); toc_[tocEnt]->ctl(lastTrackCtlAdr_ & 0xf0); tocEnt++; toc_[tocEnt]->point(0xa2); toc_[tocEnt]->pmin(leadOutStart_.min()); toc_[tocEnt]->psec(leadOutStart_.sec()); toc_[tocEnt]->pframe(leadOutStart_.frac()); toc_[tocEnt]->ctl(leadOutCtlAdr_ & 0xf0); tocEnt++; assert(tocEnt == tocLen_); // setup encoder dynamic data abslba_ = leadInStart.lba() - 450150; trlba_ = 0; writeIsrc_ = 0; deferredCatalog_ = 0; deferredIsrc_ = 0; actTocEnt_ = actTocCount_ = 0; run = nextCueSheetEntry(actCueSheetEntry_, 1); assert(run != NULL); nextTransitionLba_ = Msf(run->min, run->sec, run->frame).lba() - 150; run = nextCueSheetEntry(actCueSheetEntry_, firstTrackNr_, 1); assert(run != NULL); nextTrackStartLba_ = Msf(run->min, run->sec, run->frame).lba() - 150; return 0; } int PQChannelEncoder::analyzeCueSheet() { int i; CueSheetEntry *ent; int prevTrackNr = -1; long prevLba = -1; long lba; firstTrackNr_ = 0; firstTrackCtlAdr_ = 0; lastTrackNr_ = 0; lastTrackCtlAdr_ = 0; leadOutStart_ = 0; leadOutCtlAdr_ = 0; actCueSheetEntry_ = NULL; writeCatalog_ = 0; for (ent = cueSheet_, i = 0; i < cueSheetLen_; ent++, i++) { switch (ent->ctlAdr & 0x0f) { case 1: if (ent->min > 99 || ent->sec > 59 || ent->frame > 74) { message(-3, "Illegal time field value at cue sheet entry: %d", i); return 1; } if (ent->trackNr == 0) { // indicates lead-in if (i == 0 || (writeCatalog_ && i == 2)) { // must be first entry actCueSheetEntry_ = ent; } else { message(-3, "Illegal track number at cue sheet entry: %d", i); return 1; } } else if (ent->trackNr == 0xaa) { // indicates lead-out if (i == cueSheetLen_ - 1) { // must be last entry leadOutStart_ = Msf(ent->min, ent->sec, ent->frame); leadOutCtlAdr_ = ent->ctlAdr; } else { message(-3, "Illegal track number at cue sheet entry: %d", i); return 1; } } else if (ent->trackNr <= 99) { // data track if (firstTrackNr_ == 0) { firstTrackNr_ = ent->trackNr; firstTrackCtlAdr_ = ent->ctlAdr; prevTrackNr = ent->trackNr; } else { if (ent->trackNr != prevTrackNr && ent->trackNr != prevTrackNr + 1) { message(-3, "Wrong track number sequence at cue sheet entry: %d", i); return 1; } prevTrackNr = ent->trackNr; } lastTrackNr_ = ent->trackNr; lastTrackCtlAdr_ = ent->ctlAdr; } else { message(-3, "Illegal track number at cue sheet entry: %d", i); return 1; } if (ent->trackNr != 0) { lba = Msf(ent->min, ent->sec, ent->frame).lba(); if (lba <= prevLba) { message(-3, "Time field does not increase at cue sheet entry: %d", i); return 1; } prevLba = lba; } break; case 2: if (i != 0) { message(-3, "Catalog number must be first cue sheet entry."); return 1; } if ((cueSheet_[1].ctlAdr & 0x0f) != 2) { message(-3, "Missing second catalog number entry."); return 1; } writeCatalog_ = 1; catalog_->catalog(ent->trackNr, ent->indexNr, ent->dataForm, ent->scms, ent->min, ent->sec, ent->frame, cueSheet_[1].trackNr, cueSheet_[1].indexNr, cueSheet_[1].dataForm, cueSheet_[1].scms, cueSheet_[1].min, cueSheet_[1].sec); // skip next entry ent++; i++; break; case 3: if (((ent + 1)->ctlAdr & 0x0f) != 3) { message(-3, "Missing second ISRC code entry."); return 1; } // skip next entry ent++; i++; break; default: message(-3, "Illegal adr field at cue sheet entry: %d.", i); return 1; break; } } if (actCueSheetEntry_ == NULL) { message(-3, "Cue sheet contains no lead-in entry."); return 1; } if (leadOutStart_.lba() == 0) { message(-3, "Cue sheet contains no lead-out entry."); return 1; } if (firstTrackNr_ == 0) { message(-3, "Cue sheet contains no data track."); return 1; } return 0; } void PQChannelEncoder::encode(long lba, unsigned char *out, long blocks) { long clen = subChannel_->dataLength(); const SubChannel *chan; long i; for (i = 0; i < blocks; i++, lba++) { chan = encodeSubChannel(lba); memcpy(out, chan->data(), clen); out += clen; } } const SubChannel *PQChannelEncoder::encodeSubChannel(long lba) { int newTransition = 0; SubChannel *chan = NULL; // check consistency of internal lba with external lba assert(lba == abslba_); if (lba == nextTransitionLba_) { // switch to next transition //message(3, "Switching to next transition at lba: %ld", lba); nextTransition(); newTransition = 1; } if (actCueSheetEntry_->trackNr == 0) { // lead-in sector chan = toc_[actTocEnt_]; Msf m(trlba_); chan->min(m.min()); chan->sec(m.sec()); chan->frame(m.frac()); // advance to next to entry if (++actTocCount_ == 3) { actTocCount_ = 0; if (++actTocEnt_ == tocLen_) { actTocEnt_ = 0; } } } else { // data or lead-out sector // Q channel setup // catalog number if (writeCatalog_ && (deferredCatalog_ || (abslba_ % 90) == 0)) { if (newTransition) { deferredCatalog_ = 1; } else { deferredCatalog_ = 0; chan = catalog_; chan->aframe(Msf(abslba_ + 150).frac()); } } // ISRC code if (writeIsrc_ && (deferredIsrc_ || (abslba_ % 90) == 50 || (abslba_ % 90) == -40)) { if (newTransition) { deferredIsrc_ = 1; } else { deferredIsrc_ = 0; chan = isrc_; chan->aframe(Msf(abslba_ + 150).frac()); } } if (chan == NULL) { // Current position chan = current_; Msf m(trlba_ < 0 ? -trlba_ : trlba_); chan->min(m.min()); chan->sec(m.sec()); chan->frame(m.frac()); m = Msf(abslba_ + 150); chan->amin(m.min()); chan->asec(m.sec()); chan->aframe(m.frac()); } // P channel setup if (trlba_ <= 0 || (abslba_ >= nextTrackStartLba_ - 150 && abslba_ <= nextTrackStartLba_)) { // set P channel flag in pre-gap and 2 seconds before next track starts chan->pChannel(1); } else if (abslba_ >= leadOutStart_.lba() ) { // P channel flag is 0 2 secs from start of lead-out, after that // flag alternates at 2 Hz. chan->pChannel(((trlba_ - 150) % 38) < 19 ? 1 : 0); } else { chan->pChannel(0); } } abslba_++; trlba_++; assert(chan != NULL); chan->calcCrc(); return chan; } void PQChannelEncoder::nextTransition() { CueSheetEntry *nextEnt, *ent , *ent1; nextEnt = nextCueSheetEntry(actCueSheetEntry_, 1); assert(nextEnt != NULL); if (nextEnt->trackNr != actCueSheetEntry_->trackNr) { // new track started // check for ISRC code cue sheet entry if ((ent = nextCueSheetEntry(actCueSheetEntry_, 3)) != NULL && ent->trackNr == nextEnt->trackNr) { ent1 = ent + 1; isrc_->isrc(ent->indexNr, ent->dataForm, ent->scms, ent->min, ent->sec, ent->frame, ent1->indexNr, ent1->dataForm, ent1->scms, ent1->min, ent1->sec, ent1->frame); writeIsrc_ = 1; } else { writeIsrc_ = 0; } deferredIsrc_ = 0; // setup 'trlba' if (nextEnt->indexNr == 0) { // track has a pre-gap -> determine length ent = nextCueSheetEntry(nextEnt, nextEnt->trackNr, 1); assert(ent != NULL); trlba_ = Msf(nextEnt->min, nextEnt->sec, nextEnt->frame).lba() - Msf(ent->min, ent->sec, ent->frame).lba(); } else { trlba_ = 0; } } actCueSheetEntry_ = nextEnt; current_->ctl(actCueSheetEntry_->ctlAdr & 0xf0); current_->trackNr(actCueSheetEntry_->trackNr); current_->indexNr(actCueSheetEntry_->indexNr); if (writeCatalog_) catalog_->ctl(actCueSheetEntry_->ctlAdr & 0xf0); if (writeIsrc_) isrc_->ctl(actCueSheetEntry_->ctlAdr & 0xf0); if (actCueSheetEntry_->trackNr != 0xaa) { // find next transition point ent = nextCueSheetEntry(actCueSheetEntry_, 1); assert(ent != NULL); nextTransitionLba_ = Msf(ent->min, ent->sec, ent->frame).lba() - 150; if (actCueSheetEntry_->indexNr == 1) { // find next track start lba if (actCueSheetEntry_->trackNr == lastTrackNr_) ent = nextCueSheetEntry(actCueSheetEntry_, 0xaa, 1); else ent = nextCueSheetEntry(actCueSheetEntry_, actCueSheetEntry_->trackNr + 1, 1); assert(ent != NULL); nextTrackStartLba_ = Msf(ent->min, ent->sec, ent->frame).lba() - 150; } } } CueSheetEntry *PQChannelEncoder::nextCueSheetEntry(CueSheetEntry *act, int adr) { if (act->trackNr == 0xaa) { return NULL; } act++; while (act->ctlAdr != 0) { if ((act->ctlAdr & 0x0f) == adr) { return act; } act++; } return NULL; } CueSheetEntry *PQChannelEncoder::nextCueSheetEntry(CueSheetEntry *act, int trackNr, int indexNr) { if (act->trackNr == 0xaa) { return NULL; } act++; while (act->ctlAdr != 0) { if ((act->ctlAdr & 0x0f) == 1 && act->trackNr == trackNr && act->indexNr == indexNr) { return act; } act++; } return NULL; }