private static void ReadAudioMetaFields(StreamBuffer sb, MusicMatchTag tag)
        {
            if (sb == null)
                throw new ArgumentNullException("sb");

            // Single-line text fields
            tag.SongTitle = ReadTextField(sb);
            tag.AlbumTitle = ReadTextField(sb);
            tag.ArtistName = ReadTextField(sb);
            tag.Genre = ReadTextField(sb);
            tag.Tempo = ReadTextField(sb);
            tag.Mood = ReadTextField(sb);
            tag.Situation = ReadTextField(sb);
            tag.Preference = ReadTextField(sb);

            // Non-text fields
            tag.SongDuration = ReadTextField(sb);
            tag.CreationDate = DateTime.FromOADate(sb.ReadDouble());
            tag.PlayCounter = sb.ReadInt32();
            tag.OriginalFilename = ReadTextField(sb);
            tag.SerialNumber = ReadTextField(sb);
            tag.TrackNumber = sb.ReadInt16();

            // Multi-line text fields
            tag.Notes = ReadTextField(sb);
            tag.ArtistBio = ReadTextField(sb);
            tag.Lyrics = ReadTextField(sb);

            // Internet addresses
            tag.ArtistUrl = ReadTextField(sb);
            tag.BuyCdUrl = ReadTextField(sb);
            tag.ArtistEmail = ReadTextField(sb);
        }
        ////------------------------------------------------------------------------------------------------------------------------------
        /// <inheritdoc/>
        public IAudioTagOffset ReadFromStream(Stream stream, TagOrigin tagOrigin)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            if (!stream.CanRead)
                throw new InvalidOperationException("stream can not be read");

            if (!stream.CanSeek)
                throw new InvalidOperationException("stream can not be seeked");

            StreamBuffer sb = stream as StreamBuffer ?? new StreamBuffer(stream);
            MusicMatchTag tag = new MusicMatchTag();

            // Should we read the header or footer?
            MusicMatchHeader headerOrFooter = (tagOrigin == TagOrigin.Start) ? ReadHeader(sb, tagOrigin) : ReadFooter(sb, tagOrigin);
            if (headerOrFooter == null)
                return null;

            long startOffset = 0, endOffset = 0, streamLength = sb.Length;
            MusicMatchHeader header = null, footer = null, versionInformation = null;
            if (tagOrigin == TagOrigin.Start)
            {
                header = headerOrFooter;
                tag.UseHeader = true;
                startOffset = header.Position;
            }
            else if (tagOrigin == TagOrigin.End)
            {
                footer = headerOrFooter;
                endOffset = Math.Min(footer.Position + MusicMatchTag.FooterSize, streamLength);

                // Seek to the Audio metadata offset struct
                sb.Position = Math.Max(endOffset - MusicMatchTag.FooterSize - MusicMatchTag.DataOffsetFieldsSize, 0);

                // Read the Audio Meta-data Offsets
                MusicMatchDataOffsets dataOffsets = ReadDataOffsets(sb);

                long startPosition = Math.Max(sb.Position + MusicMatchTag.FooterSize, 0);
                foreach (int audioMetaDataOffset in AudioMetaDataSizes.OrderBy(a => a))
                {
                    // Seek to the start of the version information, based on the audioMetaDataOffset
                    long startPositionHeader = Math.Max(startPosition - audioMetaDataOffset - MusicMatchTag.HeaderSize, 0);
                    long endPositionHeader = startPositionHeader + HeaderIdentifierBytes.Length;

                    // Read the version information
                    versionInformation = ReadHeaderFooter(sb, startPositionHeader, endPositionHeader, HeaderIdentifierBytes, TagOrigin.Start);
                    if (versionInformation == null)
                        continue;

                    startOffset = (versionInformation.Position + MusicMatchTag.HeaderSize) - dataOffsets.TotalDataOffsetsSize;
                    sb.Position = Math.Max(startOffset - MusicMatchTag.HeaderSize, 0);
                    header = ReadHeader(sb, TagOrigin.Start);
                    break;
                }

                // We've got a problem, Houston.
                if (versionInformation == null)
                    return null;

                // Header found? set new startPosition
                if (header != null)
                {
                    if (header.Position == versionInformation.Position)
                    {
                        header = null;
                    }
                    else
                    {
                        tag.UseHeader = true;
                        startOffset = header.Position;
                    }
                }
            }

            sb.Position = startOffset + (tag.UseHeader ? MusicMatchTag.HeaderSize : 0);

            // Read image
            tag.Image = MusicMatchImage.ReadFromStream(sb);

            // Unused, null padding
            byte[] nullPadding = new byte[4];
            sb.Read(nullPadding, nullPadding.Length);

            // Read version info
            if (versionInformation == null)
            {
                versionInformation = ReadHeader(sb, TagOrigin.Start);
                if (versionInformation == null)
                {
            #if DEBUG
                    throw new InvalidDataException("Version information not found in the tag.");
            #else
                    return null;
            #endif
                }
            }
            else
            {
                // version information has the same size as the header size.
                sb.Position += MusicMatchTag.HeaderSize;
            }

            if (tag.UseHeader && !versionInformation.Equals(header))
            {
            #if DEBUG
                throw new InvalidDataException("Version information field(s) mismatch the header fields.");
            #else
                return null;
            #endif
            }

            tag.XingEncoderVersion = versionInformation.XingEncoderVersion;
            tag.Version = versionInformation.MusicMatchVersion;

            // Read the Audio meta-data
            ReadAudioMetaFields(sb, tag);

            // Padding
            sb.Position += 16;

            if (tagOrigin == TagOrigin.Start)
            {
                long startPosition = sb.Position;
                foreach (int audioMetaDataOffset in AudioMetaDataSizes.OrderBy(a => a))
                {
                    //Look for the start of the footer.
                    long startPositionFooter = Math.Max(startPosition + (audioMetaDataOffset - (sb.Position - startOffset) - (tag.UseHeader ? MusicMatchTag.HeaderSize : 0)), 0);
                    long endPositionFooter = startPositionFooter + FooterIdentifierBytes.Length;

                    // Read the footer.
                    footer = ReadHeaderFooter(sb, startPositionFooter, endPositionFooter, FooterIdentifierBytes, TagOrigin.End);
                    if (footer != null)
                        break;
                }

                // We've got a problem, Houston.
                if (footer == null)
                    return null;
            }
            return new AudioTagOffset(tagOrigin, startOffset, endOffset, tag);
        }
        /// <summary>
        /// Equals the specified <see cref="MusicMatchTag"/>.
        /// </summary>
        /// <param name="tag">The <see cref="MusicMatchTag"/>.</param>
        /// <returns>true if equal; false otherwise.</returns>
        public bool Equals(MusicMatchTag tag)
        {
            if (ReferenceEquals(null, tag))
                return false;

            if (ReferenceEquals(this, tag))
                return true;

            return true;
        }