/* Title: CueSharp Version: 0.5 Released: March 24, 2007 Author: Wyatt O'Day Website: wyday.com/cuesharp */ // MK: fixes to substring processing using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace CueSharp_N { /// /// A CueSheet class used to create, open, edit, and save cuesheets. /// public class CueSheet { #region Private Variables private string[] cueLines; private string m_Catalog = ""; private string m_CDTextFile = ""; private string[] m_Comments = new string[0]; // strings that don't belong or were mistyped in the global part of the cue private string[] m_Garbage = new string[0]; private string m_Performer = ""; private string m_Songwriter = ""; private string m_Title = ""; private Track[] m_Tracks = new Track[0]; #endregion Private Variables #region Properties /// /// Returns/Sets track in this cuefile. /// /// The track in this cuefile. /// Track at the tracknumber. public Track this[int tracknumber] { get { return m_Tracks[tracknumber]; } set { m_Tracks[tracknumber] = value; } } /// /// The catalog number must be 13 digits long and is encoded according to UPC/EAN rules. /// Example: CATALOG 1234567890123 /// public string Catalog { get { return m_Catalog; } set { m_Catalog = value; } } /// /// This command is used to specify the name of the file that contains the encoded CD-TEXT information for the disc. This command is only used with files that were either created with the graphical CD-TEXT editor or generated automatically by the software when copying a CD-TEXT enhanced disc. /// public string CDTextFile { get { return m_CDTextFile; } set { m_CDTextFile = value; } } /// /// This command is used to put comments in your CUE SHEET file. /// public string[] Comments { get { return m_Comments; } set { m_Comments = value; } } /// /// Lines in the cue file that don't belong or have other general syntax errors. /// public string[] Garbage { get { return m_Garbage; } } /// /// This command is used to specify the name of a perfomer for a CD-TEXT enhanced disc. /// public string Performer { get { return m_Performer; } set { m_Performer = value; } } /// /// This command is used to specify the name of a songwriter for a CD-TEXT enhanced disc. /// public string Songwriter { get { return m_Songwriter; } set { m_Songwriter = value; } } /// /// The title of the entire disc as a whole. /// public string Title { get { return m_Title; } set { m_Title = value; } } /// /// The array of tracks on the cuesheet. /// public Track[] Tracks { get { return m_Tracks; } set { m_Tracks = value; } } #endregion Properties #region Constructors /// /// Create a cue sheet from scratch. /// public CueSheet() { } /// /// Parse a cue sheet string. /// /// A string containing the cue sheet data. /// Line delimeters; set to "(char[])null" for default delimeters. public CueSheet(string cueString, char[] lineDelims) { if (lineDelims == null) { lineDelims = new char[] { '\n' }; } cueLines = RemoveEmptyLines(cueString.Split(lineDelims)); ParseCue(cueLines); } /// /// Parses a cue sheet file. /// /// The filename for the cue sheet to open. public CueSheet(string cuefilename) { ReadCueSheet(cuefilename, Encoding.Default); } /// /// Parses a cue sheet file. /// /// The filename for the cue sheet to open. /// The encoding used to open the file. public CueSheet(string cuefilename, Encoding encoding) { ReadCueSheet(cuefilename, encoding); } /// /// Parses a cue sheet file. /// /// The text stream for the cue sheet to read. public CueSheet(StreamReader file) { ReadCueSheet(file); } private void ReadCueSheet(StreamReader file) { // array of delimiters to split the sentence with char[] delimiters = new char[] { '\n' }; //read in file cueLines = RemoveEmptyLines(file.ReadToEnd().Split(delimiters)); ParseCue(cueLines); } private void ReadCueSheet(string filename, Encoding encoding) { // array of delimiters to split the sentence with char[] delimiters = new char[] { '\n' }; // read in the full cue file using (TextReader tr = new StreamReader(filename, encoding)) { //read in file cueLines = RemoveEmptyLines(tr.ReadToEnd().Split(delimiters)); } ParseCue(cueLines); } #endregion Constructors #region Methods /// /// Trims leading and trailing spaces, removes any empty lines, elimating possible trouble. /// /// private string[] RemoveEmptyLines(string[] file) { return file.Select(line => line.Trim()).Where(line => line != string.Empty).ToArray(); } private void ParseCue(string[] file) { //-1 means still global, //all others are track specific int trackOn = -1; AudioFile currentFile = new AudioFile(); foreach (var line in file) { var kv = SplitLine(line); switch (kv.Key) { case "CATALOG": ParseString(kv.Key, kv.Value, trackOn); break; case "CDTEXTFILE": ParseString(kv.Key, kv.Value, trackOn); break; case "FILE": currentFile = ParseFile(kv.Value, trackOn); break; case "FLAGS": ParseFlags(line, trackOn); break; case "INDEX": ParseIndex(kv.Key, kv.Value, trackOn); break; case "ISRC": ParseString(kv.Key, kv.Value, trackOn); break; case "PERFORMER": ParseString(kv.Key, kv.Value, trackOn); break; case "POSTGAP": ParseIndex(kv.Key, kv.Value, trackOn); break; case "PREGAP": ParseIndex(kv.Key, kv.Value, trackOn); break; case "REM": ParseComment(kv.Value, trackOn); break; case "SONGWRITER": ParseString(kv.Key, kv.Value, trackOn); break; case "TITLE": ParseString(kv.Key, kv.Value, trackOn); break; case "TRACK": trackOn++; ParseTrack(kv.Value, trackOn); if (currentFile.Filename != "") //if there's a file { m_Tracks[trackOn].DataFile = currentFile; currentFile = new AudioFile(); } break; default: ParseGarbage(line, trackOn); //save discarded junk and place string[] with track it was found in break; } } } private void ParseComment(string line, int trackOn) { // "REM" already removed if (trackOn == -1) { if (line.Trim() != "") { m_Comments = (string[])ResizeArray(m_Comments, m_Comments.Length + 1); m_Comments[m_Comments.Length - 1] = line; } } else { m_Tracks[trackOn].AddComment(line); } } private AudioFile ParseFile(string line, int trackOn) { string fileType = line.Substring(line.LastIndexOf(' '), line.Length - line.LastIndexOf(' ')).Trim(); line = line.Substring(0, line.LastIndexOf(' ')).Trim(); //if quotes around it, remove them. if (line[0] == '"') { line = line.Substring(1, line.LastIndexOf('"') - 1); } return new AudioFile(line, fileType); } private void ParseFlags(string line, int trackOn) { string temp; if (trackOn != -1) { line = line.Trim(); if (line != "") { var pos = line.IndexOf(' '); temp = (pos >= 0 ? line.Substring(0, pos) : line).ToUpper(); switch (temp) { case "FLAGS": m_Tracks[trackOn].AddFlag(temp); break; case "DATA": m_Tracks[trackOn].AddFlag(temp); break; case "DCP": m_Tracks[trackOn].AddFlag(temp); break; case "4CH": m_Tracks[trackOn].AddFlag(temp); break; case "PRE": m_Tracks[trackOn].AddFlag(temp); break; case "SCMS": m_Tracks[trackOn].AddFlag(temp); break; default: break; } temp = pos >= 0 ? line.Substring(pos, line.Length - pos) : line; //if the flag hasn't already been processed if (temp.ToUpper().Trim() != line.ToUpper().Trim()) { ParseFlags(temp, trackOn); } } } } private void ParseGarbage(string line, int trackOn) { if (trackOn == -1) { if (line.Trim() != "") { m_Garbage = (string[])ResizeArray(m_Garbage, m_Garbage.Length + 1); m_Garbage[m_Garbage.Length - 1] = line; } } else { m_Tracks[trackOn].AddGarbage(line); } } private void ParseIndex(string indexType, string value, int trackOn) { string tempString; int number = 0; int minutes; int seconds; int frames; tempString = value; if (indexType == "INDEX") { //read the index number number = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(' '))); tempString = tempString.Substring(tempString.IndexOf(' '), tempString.Length - tempString.IndexOf(' ')).Trim(); } //extract the minutes, seconds, and frames minutes = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(':'))); seconds = Convert.ToInt32(tempString.Substring(tempString.IndexOf(':') + 1, tempString.LastIndexOf(':') - tempString.IndexOf(':') - 1)); frames = Convert.ToInt32(tempString.Substring(tempString.LastIndexOf(':') + 1, tempString.Length - tempString.LastIndexOf(':') - 1)); if (indexType == "INDEX") { m_Tracks[trackOn].AddIndex(number, minutes, seconds, frames); } else if (indexType == "PREGAP") { m_Tracks[trackOn].PreGap = new Index(0, minutes, seconds, frames); } else if (indexType == "POSTGAP") { m_Tracks[trackOn].PostGap = new Index(0, minutes, seconds, frames); } } private void ParseString(string category, string line, int trackOn) { //get rid of the quotes if (line[0] == '"') { line = line.Substring(1, line.LastIndexOf('"') - 1); } switch (category) { case "CATALOG": if (trackOn == -1) { this.m_Catalog = line; } break; case "CDTEXTFILE": if (trackOn == -1) { this.m_CDTextFile = line; } break; case "ISRC": if (trackOn != -1) { m_Tracks[trackOn].ISRC = line; } break; case "PERFORMER": if (trackOn == -1) { this.m_Performer = line; } else { m_Tracks[trackOn].Performer = line; } break; case "SONGWRITER": if (trackOn == -1) { this.m_Songwriter = line; } else { m_Tracks[trackOn].Songwriter = line; } break; case "TITLE": if (trackOn == -1) { this.m_Title = line; } else { m_Tracks[trackOn].Title = line; } break; default: break; } } /// /// Parses the TRACK command. /// /// The line in the cue file that contains the TRACK command with TRACK part removed. /// The track currently processing. private void ParseTrack(string value, int trackOn) { string tempString; int trackNumber; tempString = value; try { trackNumber = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(' '))); } catch (Exception) { throw; } //find the data type. tempString = tempString.Substring(tempString.IndexOf(' '), tempString.Length - tempString.IndexOf(' ')).Trim(); AddTrack(trackNumber, tempString); } /// /// Reallocates an array with a new size, and copies the contents /// of the old array to the new array. /// /// The old array, to be reallocated. /// The new array size. /// A new array with the same contents. /// Useage: int[] a = {1,2,3}; a = (int[])ResizeArray(a,5); public static System.Array ResizeArray(System.Array oldArray, int newSize) { int oldSize = oldArray.Length; System.Type elementType = oldArray.GetType().GetElementType(); System.Array newArray = System.Array.CreateInstance(elementType, newSize); int preserveLength = System.Math.Min(oldSize, newSize); if (preserveLength > 0) System.Array.Copy(oldArray, newArray, preserveLength); return newArray; } /// /// Add a track to the current cuesheet. /// /// The number of the said track. /// The datatype of the track. private void AddTrack(int tracknumber, string datatype) { m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1); m_Tracks[m_Tracks.Length - 1] = new Track(tracknumber, datatype); } /// /// Add a track to the current cuesheet /// /// The title of the track. /// The performer of this track. public void AddTrack(string title, string performer) { m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1); m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, ""); m_Tracks[m_Tracks.Length - 1].Performer = performer; m_Tracks[m_Tracks.Length - 1].Title = title; } public void AddTrack(string title, string performer, string filename, FileType fType) { m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1); m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, ""); m_Tracks[m_Tracks.Length - 1].Performer = performer; m_Tracks[m_Tracks.Length - 1].Title = title; m_Tracks[m_Tracks.Length - 1].DataFile = new AudioFile(filename, fType); } /// /// Add a track to the current cuesheet /// /// The title of the track. /// The performer of this track. /// The datatype for the track (typically DataType.Audio) public void AddTrack(string title, string performer, DataType datatype) { m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1); m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, datatype); m_Tracks[m_Tracks.Length - 1].Performer = performer; m_Tracks[m_Tracks.Length - 1].Title = title; } /// /// Add a track to the current cuesheet /// /// Track object to add to the cuesheet. public void AddTrack(Track track) { m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1); m_Tracks[m_Tracks.Length - 1] = track; } /// /// Remove a track from the cuesheet. /// /// The index of the track you wish to remove. public void RemoveTrack(int trackIndex) { for (int i = trackIndex; i < m_Tracks.Length - 1; i++) { m_Tracks[i] = m_Tracks[i + 1]; } m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length - 1); } /// /// Add index information to an existing track. /// /// The array index number of track to be modified /// The index number of the new index /// The minute value of the new index /// The seconds value of the new index /// The frames value of the new index public void AddIndex(int trackIndex, int indexNum, int minutes, int seconds, int frames) { m_Tracks[trackIndex].AddIndex(indexNum, minutes, seconds, frames); } /// /// Remove an index from a track. /// /// The array-index of the track. /// The index of the Index you wish to remove. public void RemoveIndex(int trackIndex, int indexIndex) { //Note it is the index of the Index you want to delete, //which may or may not correspond to the number of the index. m_Tracks[trackIndex].RemoveIndex(indexIndex); } /// /// Save the cue sheet file to specified location. /// /// Filename of destination cue sheet file. public void SaveCue(string filename) { SaveCue(filename, Encoding.Default); } /// /// Save the cue sheet file to specified location. /// /// Filename of destination cue sheet file. /// The encoding used to save the file. public void SaveCue(string filename, Encoding encoding) { using (TextWriter tw = new StreamWriter(filename, false, encoding)) tw.WriteLine(this.ToString()); } /// /// Method to output the cuesheet into a single formatted string. /// /// The entire cuesheet formatted to specification. public override string ToString() { StringBuilder output = new StringBuilder(); foreach (string comment in m_Comments) { output.Append("REM " + comment + Environment.NewLine); } if (m_Catalog.Trim() != "") { output.Append("CATALOG " + m_Catalog + Environment.NewLine); } if (m_Performer.Trim() != "") { output.Append("PERFORMER \"" + m_Performer + "\"" + Environment.NewLine); } if (m_Songwriter.Trim() != "") { output.Append("SONGWRITER \"" + m_Songwriter + "\"" + Environment.NewLine); } if (m_Title.Trim() != "") { output.Append("TITLE \"" + m_Title + "\"" + Environment.NewLine); } if (m_CDTextFile.Trim() != "") { output.Append("CDTEXTFILE \"" + m_CDTextFile.Trim() + "\"" + Environment.NewLine); } for (int i = 0; i < m_Tracks.Length; i++) { output.Append(m_Tracks[i].ToString()); if (i != m_Tracks.Length - 1) { //add line break for each track except last output.Append(Environment.NewLine); } } return output.ToString(); } #endregion Methods // split input line at the first space into two parts: UPPERCASE keyword and content, // when there are no spaces entire line is a keyword private static KeyValuePair SplitLine(string line) { var pos = line.IndexOf(' '); // lines have been trimmed, so no leading/trailing spaces are expected here if (pos > 0) return new KeyValuePair(line.Substring(0, pos).ToUpper(), line.Substring(pos + 1)); else return new KeyValuePair(line.ToUpper(), string.Empty); } } /// /// DCP - Digital copy permitted /// 4CH - Four channel audio /// PRE - Pre-emphasis enabled (audio tracks only) /// SCMS - Serial copy management system (not supported by all recorders) /// There is a fourth subcode flag called "DATA" which is set for all non-audio tracks. This flag is set automatically based on the datatype of the track. /// public enum Flags { DCP, CH4, PRE, SCMS, DATA, NONE } /// /// BINARY - Intel binary file (least significant byte first) /// MOTOROLA - Motorola binary file (most significant byte first) /// AIFF - Audio AIFF file /// WAVE - Audio WAVE file /// MP3 - Audio MP3 file /// public enum FileType { BINARY, MOTOROLA, AIFF, WAVE, MP3 } /// /// /// AUDIO - Audio/Music (2352) /// CDG - Karaoke CD+G (2448) /// MODE1/2048 - CDROM Mode1 Data (cooked) /// MODE1/2352 - CDROM Mode1 Data (raw) /// MODE2/2336 - CDROM-XA Mode2 Data /// MODE2/2352 - CDROM-XA Mode2 Data /// CDI/2336 - CDI Mode2 Data /// CDI/2352 - CDI Mode2 Data /// /// public enum DataType { AUDIO, CDG, MODE1_2048, MODE1_2352, MODE2_2336, MODE2_2352, CDI_2336, CDI_2352 } /// /// This command is used to specify indexes (or subindexes) within a track. /// Syntax: /// INDEX [number] [mm:ss:ff] /// public struct Index { //0-99 private int m_number; private int m_minutes; private int m_seconds; private int m_frames; /// /// Index number (0-99) /// public int Number { get { return m_number; } set { if (value > 99) { m_number = 99; } else if (value < 0) { m_number = 0; } else { m_number = value; } } } /// /// Possible values: 0-99 /// public int Minutes { get { return m_minutes; } set { if (value > 99) { m_minutes = 99; } else if (value < 0) { m_minutes = 0; } else { m_minutes = value; } } } /// /// Possible values: 0-59 /// There are 60 seconds/minute /// public int Seconds { get { return m_seconds; } set { if (value >= 60) { m_seconds = 59; } else if (value < 0) { m_seconds = 0; } else { m_seconds = value; } } } /// /// Possible values: 0-74 /// There are 75 frames/second /// public int Frames { get { return m_frames; } set { if (value >= 75) { m_frames = 74; } else if (value < 0) { m_frames = 0; } else { m_frames = value; } } } /// /// The Index of a track. /// /// Index number 0-99 /// Minutes (0-99) /// Seconds (0-59) /// Frames (0-74) public Index(int number, int minutes, int seconds, int frames) { m_number = number; m_minutes = minutes; m_seconds = seconds; m_frames = frames; } } /// /// This command is used to specify a data/audio file that will be written to the recorder. /// public struct AudioFile { private string m_Filename; private FileType m_Filetype; public string Filename { get { return m_Filename; } set { m_Filename = value; } } /// /// BINARY - Intel binary file (least significant byte first) /// MOTOROLA - Motorola binary file (most significant byte first) /// AIFF - Audio AIFF file /// WAVE - Audio WAVE file /// MP3 - Audio MP3 file /// public FileType Filetype { get { return m_Filetype; } set { m_Filetype = value; } } public AudioFile(string filename, string filetype) { m_Filename = filename; switch (filetype.Trim().ToUpper()) { case "BINARY": m_Filetype = FileType.BINARY; break; case "MOTOROLA": m_Filetype = FileType.MOTOROLA; break; case "AIFF": m_Filetype = FileType.AIFF; break; case "WAVE": m_Filetype = FileType.WAVE; break; case "MP3": m_Filetype = FileType.MP3; break; default: m_Filetype = FileType.BINARY; break; } } public AudioFile(string filename, FileType filetype) { m_Filename = filename; m_Filetype = filetype; } } /// /// Track that contains either data or audio. It can contain Indices and comment information. /// public class Track { #region Private Variables private string[] m_Comments; // strings that don't belong or were mistyped in the global part of the cue private AudioFile m_DataFile; private string[] m_Garbage; private Index[] m_Indices; private string m_ISRC; private string m_Performer; private Index m_PostGap; private Index m_PreGap; private string m_Songwriter; private string m_Title; private Flags[] m_TrackFlags; private DataType m_TrackDataType; private int m_TrackNumber; #endregion Private Variables #region Properties /// /// Returns/Sets Index in this track. /// /// Index in the track. /// Index at indexnumber. public Index this[int indexnumber] { get { return m_Indices[indexnumber]; } set { m_Indices[indexnumber] = value; } } public string[] Comments { get { return m_Comments; } set { m_Comments = value; } } public AudioFile DataFile { get { return m_DataFile; } set { m_DataFile = value; } } /// /// Lines in the cue file that don't belong or have other general syntax errors. /// public string[] Garbage { get { return m_Garbage; } set { m_Garbage = value; } } public Index[] Indices { get { return m_Indices; } set { m_Indices = value; } } public string ISRC { get { return m_ISRC; } set { m_ISRC = value; } } public string Performer { get { return m_Performer; } set { m_Performer = value; } } public Index PostGap { get { return m_PostGap; } set { m_PostGap = value; } } public Index PreGap { get { return m_PreGap; } set { m_PreGap = value; } } public string Songwriter { get { return m_Songwriter; } set { m_Songwriter = value; } } /// /// If the TITLE command appears before any TRACK commands, then the string will be encoded as the title of the entire disc. /// public string Title { get { return m_Title; } set { m_Title = value; } } public DataType TrackDataType { get { return m_TrackDataType; } set { m_TrackDataType = value; } } public Flags[] TrackFlags { get { return m_TrackFlags; } set { m_TrackFlags = value; } } public int TrackNumber { get { return m_TrackNumber; } set { m_TrackNumber = value; } } #endregion Properties #region Contructors public Track(int tracknumber, string datatype) { m_TrackNumber = tracknumber; switch (datatype.Trim().ToUpper()) { case "AUDIO": m_TrackDataType = DataType.AUDIO; break; case "CDG": m_TrackDataType = DataType.CDG; break; case "MODE1/2048": m_TrackDataType = DataType.MODE1_2048; break; case "MODE1/2352": m_TrackDataType = DataType.MODE1_2352; break; case "MODE2/2336": m_TrackDataType = DataType.MODE2_2336; break; case "MODE2/2352": m_TrackDataType = DataType.MODE2_2352; break; case "CDI/2336": m_TrackDataType = DataType.CDI_2336; break; case "CDI/2352": m_TrackDataType = DataType.CDI_2352; break; default: m_TrackDataType = DataType.AUDIO; break; } m_TrackFlags = new Flags[0]; m_Songwriter = ""; m_Title = ""; m_ISRC = ""; m_Performer = ""; m_Indices = new Index[0]; m_Garbage = new string[0]; m_Comments = new string[0]; m_PreGap = new Index(-1, 0, 0, 0); m_PostGap = new Index(-1, 0, 0, 0); m_DataFile = new AudioFile(); } public Track(int tracknumber, DataType datatype) { m_TrackNumber = tracknumber; m_TrackDataType = datatype; m_TrackFlags = new Flags[0]; m_Songwriter = ""; m_Title = ""; m_ISRC = ""; m_Performer = ""; m_Indices = new Index[0]; m_Garbage = new string[0]; m_Comments = new string[0]; m_PreGap = new Index(-1, 0, 0, 0); m_PostGap = new Index(-1, 0, 0, 0); m_DataFile = new AudioFile(); } #endregion Contructors #region Methods public void AddFlag(Flags flag) { //if it's not a none tag //and if the tags hasn't already been added if (flag != Flags.NONE && NewFlag(flag) == true) { m_TrackFlags = (Flags[])CueSheet.ResizeArray(m_TrackFlags, m_TrackFlags.Length + 1); m_TrackFlags[m_TrackFlags.Length - 1] = flag; } } public void AddFlag(string flag) { switch (flag.Trim().ToUpper()) { case "DATA": AddFlag(Flags.DATA); break; case "DCP": AddFlag(Flags.DCP); break; case "4CH": AddFlag(Flags.CH4); break; case "PRE": AddFlag(Flags.PRE); break; case "SCMS": AddFlag(Flags.SCMS); break; default: return; } } public void AddGarbage(string garbage) { if (garbage.Trim() != "") { m_Garbage = (string[])CueSheet.ResizeArray(m_Garbage, m_Garbage.Length + 1); m_Garbage[m_Garbage.Length - 1] = garbage; } } public void AddComment(string comment) { if (comment.Trim() != "") { m_Comments = (string[])CueSheet.ResizeArray(m_Comments, m_Comments.Length + 1); m_Comments[m_Comments.Length - 1] = comment; } } public void AddIndex(int number, int minutes, int seconds, int frames) { m_Indices = (Index[])CueSheet.ResizeArray(m_Indices, m_Indices.Length + 1); m_Indices[m_Indices.Length - 1] = new Index(number, minutes, seconds, frames); } public void RemoveIndex(int indexIndex) { for (int i = indexIndex; i < m_Indices.Length - 1; i++) { m_Indices[i] = m_Indices[i + 1]; } m_Indices = (Index[])CueSheet.ResizeArray(m_Indices, m_Indices.Length - 1); } public TimeSpan Offset { get { if (m_Indices.Length == 0) return TimeSpan.Zero; var index = Indices.Length > 1 ? Indices[1] : Indices[0]; return new TimeSpan(0, 0, index.Minutes, index.Seconds, index.Frames * 1000 / 75); } } /// /// Checks if the flag is indeed new in this track. /// /// The new flag to be added to the track. /// True if this flag doesn't already exist. private bool NewFlag(Flags new_flag) { foreach (Flags flag in m_TrackFlags) { if (flag == new_flag) { return false; } } return true; } /// /// CueSheet string representation for debugging purposes /// /// public override string ToString() { StringBuilder output = new StringBuilder(); //write file if (m_DataFile.Filename != null && m_DataFile.Filename.Trim() != "") { output.Append("FILE \"" + m_DataFile.Filename.Trim() + "\" " + m_DataFile.Filetype.ToString() + Environment.NewLine); } output.Append(" TRACK " + m_TrackNumber.ToString().PadLeft(2, '0') + " " + m_TrackDataType.ToString().Replace('_', '/')); //write comments foreach (string comment in m_Comments) { output.Append(Environment.NewLine + " REM " + comment); } if (m_Performer.Trim() != "") { output.Append(Environment.NewLine + " PERFORMER \"" + m_Performer + "\""); } if (m_Songwriter.Trim() != "") { output.Append(Environment.NewLine + " SONGWRITER \"" + m_Songwriter + "\""); } if (m_Title.Trim() != "") { output.Append(Environment.NewLine + " TITLE \"" + m_Title + "\""); } //write flags if (m_TrackFlags.Length > 0) { output.Append(Environment.NewLine + " FLAGS"); } foreach (Flags flag in m_TrackFlags) { output.Append(" " + flag.ToString().Replace("CH4", "4CH")); } //write isrc if (m_ISRC.Trim() != "") { output.Append(Environment.NewLine + " ISRC " + m_ISRC.Trim()); } //write pregap if (m_PreGap.Number != -1) { output.Append(Environment.NewLine + " PREGAP " + m_PreGap.Minutes.ToString().PadLeft(2, '0') + ":" + m_PreGap.Seconds.ToString().PadLeft(2, '0') + ":" + m_PreGap.Frames.ToString().PadLeft(2, '0')); } //write Indices for (int j = 0; j < m_Indices.Length; j++) { output.Append(Environment.NewLine + " INDEX " + this[j].Number.ToString().PadLeft(2, '0') + " " + this[j].Minutes.ToString().PadLeft(2, '0') + ":" + this[j].Seconds.ToString().PadLeft(2, '0') + ":" + this[j].Frames.ToString().PadLeft(2, '0')); } //write postgap if (m_PostGap.Number != -1) { output.Append(Environment.NewLine + " POSTGAP " + m_PostGap.Minutes.ToString().PadLeft(2, '0') + ":" + m_PostGap.Seconds.ToString().PadLeft(2, '0') + ":" + m_PostGap.Frames.ToString().PadLeft(2, '0')); } return output.ToString(); } #endregion Methods } }