Exemple #1
0
void MPEG::XingHeader::parse(const ByteVector &data)
{
  // Check to see if a valid Xing header is available.

  if(!data.startsWith("Xing") && !data.startsWith("Info"))
    return;

  // If the XingHeader doesn't contain the number of frames and the total stream
  // info it's invalid.

  if(!(data[7] & 0x01)) {
    debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames.");
    return;
  }

  if(!(data[7] & 0x02)) {
    debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size.");
    return;
  }

  d->frames = data.mid(8, 4).toUInt();
  d->size = data.mid(12, 4).toUInt();

  d->valid = true;
}
Exemple #2
0
ByteVector TagLib::Utils::readHeader(IOStream *stream, unsigned int length,
                                     bool skipID3v2, long *headerOffset)
{
  if(!stream || !stream->isOpen())
    return ByteVector();

  const long originalPosition = stream->tell();
  long bufferOffset = 0;

  if(skipID3v2) {
    stream->seek(0);
    const ByteVector data = stream->readBlock(ID3v2::Header::size());
    if(data.startsWith(ID3v2::Header::fileIdentifier()))
      bufferOffset = ID3v2::Header(data).completeTagSize();
  }

  stream->seek(bufferOffset);
  const ByteVector header = stream->readBlock(length);
  stream->seek(originalPosition);

  if(headerOffset)
    *headerOffset = bufferOffset;

  return header;
}
Exemple #3
0
void MPEG::VbriHeader::parse(const ByteVector &data)
{
  // Check to see if a valid VBRI header is available.

  if(!data.startsWith("VBRI"))
    return;

  d->size   = data.toUInt(10, true);
  d->frames = data.toUInt(14, true);

  d->valid = true;
}
Exemple #4
0
void ID3v1::Tag::read()
{
  if(d->file && d->file->isValid()) {
    d->file->seek(d->tagOffset);
    // read the tag -- always 128 bytes
    const ByteVector data = d->file->readBlock(128);

    // some initial sanity checking
    if(data.size() == 128 && data.startsWith("TAG"))
      parse(data);
    else
      debug("ID3v1 tag is not valid or could not be read at the specified offset.");
  }
}
Exemple #5
0
void Speex::File::read(bool readProperties)
{
  ByteVector speexHeaderData = packet(0);

  if(!speexHeaderData.startsWith("Speex   ")) {
    debug("Speex::File::read() -- invalid Speex identification header");
    return;
  }

  ByteVector commentHeaderData = packet(1);

  d->comment = new Ogg::XiphComment(commentHeaderData);

  if(readProperties)
    d->properties = new AudioProperties(this);
}
Exemple #6
0
void ID3v2::Tag::read()
{
  if(!d->file)
    return;

  if(!d->file->isOpen())
    return;

  d->file->seek(d->tagOffset);
  d->header.setData(d->file->readBlock(Header::size()));

  // If the tag size is 0, then this is an invalid tag (tags must contain at
  // least one frame)

  if(d->header.tagSize() != 0)
    parse(d->file->readBlock(d->header.tagSize()));

  // Look for duplicate ID3v2 tags and treat them as an extra blank of this one.
  // It leads to overwriting them with zero when saving the tag.

  // This is a workaround for some faulty files that have duplicate ID3v2 tags.
  // Unfortunately, TagLib itself may write such duplicate tags until v1.10.

  unsigned int extraSize = 0;

  while(true) {

    d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize);

    const ByteVector data = d->file->readBlock(Header::size());
    if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier()))
      break;

    extraSize += Header(data).completeTagSize();
  }

  if(extraSize != 0) {
    debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found.");
    d->header.setTagSize(d->header.tagSize() + extraSize);
  }
}
Exemple #7
0
void APE::Properties::analyzeOld(File *file)
{
  const ByteVector header = file->readBlock(26);
  if(header.size() < 26) {
    debug("APE::Properties::analyzeOld() -- MAC header is too short.");
    return;
  }

  const unsigned int totalFrames = header.toUInt(18, false);

  // Fail on 0 length APE files (catches non-finalized APE files)
  if(totalFrames == 0)
    return;

  const short compressionLevel = header.toShort(0, false);
  unsigned int blocksPerFrame;
  if(d->version >= 3950)
    blocksPerFrame = 73728 * 4;
  else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000))
    blocksPerFrame = 73728;
  else
    blocksPerFrame = 9216;

  // Get the APE info
  d->channels   = header.toShort(4, false);
  d->sampleRate = header.toUInt(6, false);

  const unsigned int finalFrameBlocks = header.toUInt(22, false);
  d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;

  // Get the bit depth from the RIFF-fmt chunk.
  file->seek(16, File::Current);
  const ByteVector fmt = file->readBlock(28);
  if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) {
    debug("APE::Properties::analyzeOld() -- fmt header is too short.");
    return;
  }

  d->bitsPerSample = fmt.toShort(26, false);
}
Exemple #8
0
void RIFF::WAV::File::read(bool readProperties)
{
  for(uint i = 0; i < chunkCount(); ++i) {
    const ByteVector name = chunkName(i);
    if(name == "ID3 " || name == "id3 ") {
      if(!d->tag[ID3v2Index]) {
        d->tagChunkID = name;
        d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i)));
        d->hasID3v2 = true;
      }
      else {
        debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found.");
      }
    }
    else if(name == "LIST") {
      const ByteVector data = chunkData(i);
      if(data.startsWith("INFO")) {
        if(!d->tag[InfoIndex]) {
          d->tag.set(InfoIndex, new RIFF::Info::Tag(data));
          d->hasInfo = true;
        }
        else {
          debug("RIFF::WAV::File::read() - Duplicate INFO tag found.");
        }
      }
    }
  }

  if(!d->tag[ID3v2Index])
    d->tag.set(ID3v2Index, new ID3v2::Tag());

  if(!d->tag[InfoIndex])
    d->tag.set(InfoIndex, new RIFF::Info::Tag());

  if(readProperties)
    d->properties = new Properties(this, Properties::Average);
}
Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const
{
  ByteVector data = origData;
  uint version = tagHeader->majorVersion();
  Frame::Header *header = new Frame::Header(data, version);
  ByteVector frameID = header->frameID();

  // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1
  // characters.  Also make sure that there is data in the frame.

  if(!frameID.size() == (version < 3 ? 3 : 4) ||
     header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) ||
     header->frameSize() > data.size())
  {
    delete header;
    return 0;
  }

  for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
    if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
      delete header;
      return 0;
    }
  }

  if(version > 3 && (tagHeader->unsynchronisation() || header->unsynchronisation())) {
    // Data lengths are not part of the encoded data, but since they are synch-safe
    // integers they will be never actually encoded.
    ByteVector frameData = data.mid(Frame::Header::size(version), header->frameSize());
    frameData = SynchData::decode(frameData);
    data = data.mid(0, Frame::Header::size(version)) + frameData;
  }

  // TagLib doesn't mess with encrypted frames, so just treat them
  // as unknown frames.

#if HAVE_ZLIB == 0
  if(header->compression()) {
    debug("Compressed frames are currently not supported.");
    return new UnknownFrame(data, header);
  }
#endif
  if(header->encryption()) {
    debug("Encrypted frames are currently not supported.");
    return new UnknownFrame(data, header);
  }

  if(!updateFrame(header)) {
    header->setTagAlterPreservation(true);
    return new UnknownFrame(data, header);
  }

  // updateFrame() might have updated the frame ID.

  frameID = header->frameID();

  // This is where things get necissarily nasty.  Here we determine which
  // Frame subclass (or if none is found simply an Frame) based
  // on the frame ID.  Since there are a lot of possibilities, that means
  // a lot of if blocks.

  // Text Identification (frames 4.2)

  if(frameID.startsWith("T")) {

    TextIdentificationFrame *f = frameID != "TXXX"
      ? new TextIdentificationFrame(data, header)
      : new UserTextIdentificationFrame(data, header);

    d->setTextEncoding(f);

    if(frameID == "TCON")
      updateGenre(f);

    return f;
  }

  // Comments (frames 4.10)

  if(frameID == "COMM") {
    CommentsFrame *f = new CommentsFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // Attached Picture (frames 4.14)

  if(frameID == "APIC") {
    AttachedPictureFrame *f = new AttachedPictureFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // ID3v2.2 Attached Picture

	if(frameID == "PIC") {
    AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header);
    d->setTextEncoding(f);
    return f;
  }

	// Relative Volume Adjustment (frames 4.11)

  if(frameID == "RVA2")
    return new RelativeVolumeFrame(data, header);

  // Unique File Identifier (frames 4.1)

  if(frameID == "UFID")
    return new UniqueFileIdentifierFrame(data, header);

  // General Encapsulated Object (frames 4.15)

  if(frameID == "GEOB") {
    GeneralEncapsulatedObjectFrame *f = new GeneralEncapsulatedObjectFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // URL link (frames 4.3)

  if(frameID.startsWith("W")) {
    if(frameID != "WXXX") {
      return new UrlLinkFrame(data, header);
    }
    else {
      UserUrlLinkFrame *f = new UserUrlLinkFrame(data, header);
      d->setTextEncoding(f);
      return f;
    }
  }

  // Unsynchronized lyric/text transcription (frames 4.8)

  if(frameID == "USLT") {
    UnsynchronizedLyricsFrame *f = new UnsynchronizedLyricsFrame(data, header);
    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);
    return f;
  }

  // Popularimeter (frames 4.17)

  if(frameID == "POPM")
    return new PopularimeterFrame(data, header);

  // Private (frames 4.27)

  if(frameID == "PRIV")
    return new PrivateFrame(data, header);

  return new UnknownFrame(data, header);
}
Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const
{
  Frame::Header *header = new Frame::Header(data, version);
  ByteVector frameID = header->frameID();

  // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1
  // characters.  Also make sure that there is data in the frame.

  if(!frameID.size() == (version < 3 ? 3 : 4) ||
     header->frameSize() <= 0 ||
     header->frameSize() > data.size())
  {
    delete header;
    return 0;
  }

  for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
    if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
      delete header;
      return 0;
    }
  }

  // TagLib doesn't mess with encrypted frames, so just treat them
  // as unknown frames.

#if HAVE_ZLIB == 0
  if(header->compression()) {
    debug("Compressed frames are currently not supported.");
    return new UnknownFrame(data, header);
  }
#endif
  if(header->encryption()) {
    debug("Encrypted frames are currently not supported.");
    return new UnknownFrame(data, header);
  }

  if(!updateFrame(header)) {
    header->setTagAlterPreservation(true);
    return new UnknownFrame(data, header);
  }

  // updateFrame() might have updated the frame ID.

  frameID = header->frameID();

  // This is where things get necissarily nasty.  Here we determine which
  // Frame subclass (or if none is found simply an Frame) based
  // on the frame ID.  Since there are a lot of possibilities, that means
  // a lot of if blocks.

  // Text Identification (frames 4.2)

  if(frameID.startsWith("T")) {
    TextIdentificationFrame *f = frameID != "TXXX"
      ? new TextIdentificationFrame(data, header)
      : new UserTextIdentificationFrame(data, header);

    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);

    if(frameID == "TCON")
      updateGenre(f);

    return f;
  }

  // Comments (frames 4.10)

  if(frameID == "COMM") {
    CommentsFrame *f = new CommentsFrame(data, header);
    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);
    return f;
  }

  // Attached Picture (frames 4.14)

  if(frameID == "APIC") {
    AttachedPictureFrame *f = new AttachedPictureFrame(data, header);
    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);
    return f;
  }

  // Relative Volume Adjustment (frames 4.11)

  if(frameID == "RVA2")
    return new RelativeVolumeFrame(data, header);

  // Unique File Identifier (frames 4.1)

  if(frameID == "UFID")
    return new UniqueFileIdentifierFrame(data, header);

  // General Encapsulated Object (frames 4.15)

  if(frameID == "GEOB")
    return new GeneralEncapsulatedObjectFrame(data, header);

  return new UnknownFrame(data, header);
}
Exemple #11
0
void Ogg::FLAC::File::scan()
{
  // Scan the metadata pages

  if(d->scanned)
    return;

  if(!isValid())
    return;

  int ipacket = 0;
  long overhead = 0;

  ByteVector metadataHeader = packet(ipacket);
  if(metadataHeader.isEmpty())
    return;

  if(!metadataHeader.startsWith("fLaC"))  {
    // FLAC 1.1.2+
    if(metadataHeader.mid(1, 4) != "FLAC")
      return;

    if(metadataHeader[5] != 1)
      return; // not version 1

    metadataHeader = metadataHeader.mid(13);
  }
  else {
    // FLAC 1.1.0 & 1.1.1
    metadataHeader = packet(++ipacket);
  }

  ByteVector header = metadataHeader.mid(0, 4);
  if(header.size() != 4) {
    debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
    return;
  }

  // Header format (from spec):
  // <1> Last-metadata-block flag
  // <7> BLOCK_TYPE
  //    0 : STREAMINFO
  //    1 : PADDING
  //    ..
  //    4 : VORBIS_COMMENT
  //    ..
  // <24> Length of metadata to follow

  char blockType = header[0] & 0x7f;
  bool lastBlock = (header[0] & 0x80) != 0;
  uint length = header.toUInt(1, 3, true);
  overhead += length;

  // Sanity: First block should be the stream_info metadata

  if(blockType != 0) {
    debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC stream");
    return;
  }

  d->streamInfoData = metadataHeader.mid(4, length);

  // Search through the remaining metadata

  while(!lastBlock) {
    metadataHeader = packet(++ipacket);
    header = metadataHeader.mid(0, 4);
    if(header.size() != 4) {
      debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
      return;
    }

    blockType = header[0] & 0x7f;
    lastBlock = (header[0] & 0x80) != 0;
    length = header.toUInt(1, 3, true);
    overhead += length;

    if(blockType == 1) {
      // debug("Ogg::FLAC::File::scan() -- Padding found");
    }
    else if(blockType == 4) {
      // debug("Ogg::FLAC::File::scan() -- Vorbis-comments found");
      d->xiphCommentData = metadataHeader.mid(4, length);
      d->hasXiphComment = true;
      d->commentPacket = ipacket;
    }
    else if(blockType > 5) {
      debug("Ogg::FLAC::File::scan() -- Unknown metadata block");
    }
  }

  // End of metadata, now comes the datastream
  d->streamStart = overhead;
  d->streamLength = File::length() - d->streamStart;

  d->scanned = true;
}
Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHeader) const
{
  ByteVector data = origData;
  unsigned int version = tagHeader->majorVersion();
  Frame::Header *header = new Frame::Header(data, version);
  ByteVector frameID = header->frameID();

  // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1
  // characters.  Also make sure that there is data in the frame.

  if(frameID.size() != (version < 3 ? 3 : 4) ||
     header->frameSize() <= static_cast<unsigned int>(header->dataLengthIndicator() ? 4 : 0) ||
     header->frameSize() > data.size())
  {
    delete header;
    return 0;
  }

#ifndef NO_ITUNES_HACKS
  if(version == 3 && frameID.size() == 4 && frameID[3] == '\0') {
    // iTunes v2.3 tags store v2.2 frames - convert now
    frameID = frameID.mid(0, 3);
    header->setFrameID(frameID);
    header->setVersion(2);
    updateFrame(header);
    header->setVersion(3);
  }
#endif

  for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
    if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) {
      delete header;
      return 0;
    }
  }

  if(version > 3 && (tagHeader->unsynchronisation() || header->unsynchronisation())) {
    // Data lengths are not part of the encoded data, but since they are synch-safe
    // integers they will be never actually encoded.
    ByteVector frameData = data.mid(Frame::Header::size(version), header->frameSize());
    frameData = SynchData::decode(frameData);
    data = data.mid(0, Frame::Header::size(version)) + frameData;
  }

  // TagLib doesn't mess with encrypted frames, so just treat them
  // as unknown frames.

#if !defined(HAVE_ZLIB) || HAVE_ZLIB == 0
  if(header->compression()) {
    debug("Compressed frames are currently not supported.");
    return new UnknownFrame(data, header);
  }
#endif
  if(header->encryption()) {
    debug("Encrypted frames are currently not supported.");
    return new UnknownFrame(data, header);
  }

  if(!updateFrame(header)) {
    header->setTagAlterPreservation(true);
    return new UnknownFrame(data, header);
  }

  // updateFrame() might have updated the frame ID.

  frameID = header->frameID();

  // This is where things get necissarily nasty.  Here we determine which
  // Frame subclass (or if none is found simply an Frame) based
  // on the frame ID.  Since there are a lot of possibilities, that means
  // a lot of if blocks.

  // Text Identification (frames 4.2)

  // Apple proprietary WFED (Podcast URL) is in fact a text frame.
  if(frameID.startsWith("T") || frameID == "WFED") {

    TextIdentificationFrame *f = frameID != "TXXX"
      ? new TextIdentificationFrame(data, header)
      : new UserTextIdentificationFrame(data, header);

    d->setTextEncoding(f);

    if(frameID == "TCON")
      updateGenre(f);

    return f;
  }

  // Comments (frames 4.10)

  if(frameID == "COMM") {
    CommentsFrame *f = new CommentsFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // Attached Picture (frames 4.14)

  if(frameID == "APIC") {
    AttachedPictureFrame *f = new AttachedPictureFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // ID3v2.2 Attached Picture

  if(frameID == "PIC") {
    AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // Relative Volume Adjustment (frames 4.11)

  if(frameID == "RVA2")
    return new RelativeVolumeFrame(data, header);

  // Unique File Identifier (frames 4.1)

  if(frameID == "UFID")
    return new UniqueFileIdentifierFrame(data, header);

  // General Encapsulated Object (frames 4.15)

  if(frameID == "GEOB") {
    GeneralEncapsulatedObjectFrame *f = new GeneralEncapsulatedObjectFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // URL link (frames 4.3)

  if(frameID.startsWith("W")) {
    if(frameID != "WXXX") {
      return new UrlLinkFrame(data, header);
    }
    else {
      UserUrlLinkFrame *f = new UserUrlLinkFrame(data, header);
      d->setTextEncoding(f);
      return f;
    }
  }

  // Unsynchronized lyric/text transcription (frames 4.8)

  if(frameID == "USLT") {
    UnsynchronizedLyricsFrame *f = new UnsynchronizedLyricsFrame(data, header);
    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);
    return f;
  }

  // Synchronised lyrics/text (frames 4.9)

  if(frameID == "SYLT") {
    SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header);
    if(d->useDefaultEncoding)
      f->setTextEncoding(d->defaultEncoding);
    return f;
  }

  // Event timing codes (frames 4.5)

  if(frameID == "ETCO")
    return new EventTimingCodesFrame(data, header);

  // Popularimeter (frames 4.17)

  if(frameID == "POPM")
    return new PopularimeterFrame(data, header);

  // Private (frames 4.27)

  if(frameID == "PRIV")
    return new PrivateFrame(data, header);

  // Ownership (frames 4.22)

  if(frameID == "OWNE") {
    OwnershipFrame *f = new OwnershipFrame(data, header);
    d->setTextEncoding(f);
    return f;
  }

  // Chapter (ID3v2 chapters 1.0)

  if(frameID == "CHAP")
    return new ChapterFrame(tagHeader, data, header);

  // Table of contents (ID3v2 chapters 1.0)

  if(frameID == "CTOC")
    return new TableOfContentsFrame(tagHeader, data, header);

  // Apple proprietary PCST (Podcast)

  if(frameID == "PCST")
    return new PodcastFrame(data, header);

  return new UnknownFrame(data, header);
}
void Ogg::PageHeader::read(Ogg::File *file, long pageOffset)
{
  file->seek(pageOffset);

  // An Ogg page header is at least 27 bytes, so we'll go ahead and read that
  // much and then get the rest when we're ready for it.

  const ByteVector data = file->readBlock(27);

  // Sanity check -- make sure that we were in fact able to read as much data as
  // we asked for and that the page begins with "OggS".

  if(data.size() != 27 || !data.startsWith("OggS")) {
    debug("Ogg::PageHeader::read() -- error reading page header");
    return;
  }

  const std::bitset<8> flags(data[5]);

  d->firstPacketContinued = flags.test(0);
  d->firstPageOfStream    = flags.test(1);
  d->lastPageOfStream     = flags.test(2);

  d->absoluteGranularPosition = data.toLongLong(6, false);
  d->streamSerialNumber = data.toUInt(14, false);
  d->pageSequenceNumber = data.toUInt(18, false);

  // Byte number 27 is the number of page segments, which is the only variable
  // length portion of the page header.  After reading the number of page
  // segments we'll then read in the corresponding data for this count.

  int pageSegmentCount = static_cast<unsigned char>(data[26]);

  const ByteVector pageSegments = file->readBlock(pageSegmentCount);

  // Another sanity check.

  if(pageSegmentCount < 1 || int(pageSegments.size()) != pageSegmentCount)
    return;

  // The base size of an Ogg page 27 bytes plus the number of lacing values.

  d->size = 27 + pageSegmentCount;

  int packetSize = 0;

  for(int i = 0; i < pageSegmentCount; i++) {
    d->dataSize += static_cast<unsigned char>(pageSegments[i]);
    packetSize += static_cast<unsigned char>(pageSegments[i]);

    if(static_cast<unsigned char>(pageSegments[i]) < 255) {
      d->packetSizes.append(packetSize);
      packetSize = 0;
    }
  }

  if(packetSize > 0) {
    d->packetSizes.append(packetSize);
    d->lastPacketCompleted = false;
  }
  else
    d->lastPacketCompleted = true;

  d->isValid = true;
}
Exemple #14
0
void Mod::File::read(bool)
{
  if(!isOpen())
    return;

  seek(1080);
  ByteVector modId = readBlock(4);
  READ_ASSERT(modId.size() == 4);

  int          channels    =  4;
  unsigned int instruments = 31;
  if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") {
    d->tag.setTrackerName("ProTracker");
    channels = 4;
  }
  else if(modId.startsWith("FLT") || modId.startsWith("TDZ")) {
    d->tag.setTrackerName("StarTrekker");
    char digit = modId[3];
    READ_ASSERT(digit >= '0' && digit <= '9');
    channels = digit - '0';
  }
  else if(modId.endsWith("CHN")) {
    d->tag.setTrackerName("StarTrekker");
    char digit = modId[0];
    READ_ASSERT(digit >= '0' && digit <= '9');
    channels = digit - '0';
  }
  else if(modId == "CD81" || modId == "OKTA") {
    d->tag.setTrackerName("Atari Oktalyzer");
    channels = 8;
  }
  else if(modId.endsWith("CH") || modId.endsWith("CN")) {
    d->tag.setTrackerName("TakeTracker");
    char digit = modId[0];
    READ_ASSERT(digit >= '0' && digit <= '9');
    channels = (digit - '0') * 10;
    digit = modId[1];
    READ_ASSERT(digit >= '0' && digit <= '9');
    channels += digit - '0';
  }
  else {
    // Not sure if this is correct. I'd need a file
    // created with NoiseTracker to check this.
    d->tag.setTrackerName("NoiseTracker"); // probably
    channels    =  4;
    instruments = 15;
  }
  d->properties.setChannels(channels);
  d->properties.setInstrumentCount(instruments);

  seek(0);
  READ_STRING(d->tag.setTitle, 20);

  StringList comment;
  for(unsigned int i = 0; i < instruments; ++ i) {
    READ_STRING_AS(instrumentName, 22);
    // value in words, * 2 (<< 1) for bytes:
    READ_U16B_AS(sampleLength);

    READ_BYTE_AS(fineTuneByte);
    int fineTune = fineTuneByte & 0xF;
    // > 7 means negative value
    if(fineTune > 7) fineTune -= 16;

    READ_BYTE_AS(volume);
    if(volume > 64) volume = 64;
    // volume in decibels: 20 * log10(volume / 64)

    // value in words, * 2 (<< 1) for bytes:
    READ_U16B_AS(repeatStart);
    // value in words, * 2 (<< 1) for bytes:
    READ_U16B_AS(repatLength);

    comment.append(instrumentName);
  }

  READ_BYTE(d->properties.setLengthInPatterns);

  d->tag.setComment(comment.toString("\n"));
}
Exemple #15
0
bool DSF::File::isSupported(IOStream *stream)
{
    // A DSF file has to start with "DSD "
    const ByteVector id = Utils::readHeader(stream, 4, false);
    return id.startsWith("DSD ");
}