Exemple #1
0
		void LoadFromString(ParseCueJob job)
		{
			string cueString = job.IN_CueString;
			TextReader tr = new StringReader(cueString);

			for (; ; )
			{
				job.CurrentLine++;
				string line = tr.ReadLine();
				if (line == null) break;
				line = line.Trim();
				if (line == "") continue;
				var clp = new CueLineParser(line);

				string key = clp.ReadToken().ToUpperInvariant();
				if (key.StartsWith(";"))
				{
					clp.EOF = true;
					OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT() { Value = line });
				}
				else switch (key)
				{
					default:
						job.Warn("Unknown command: " + key);
						break;

					case "CATALOG":
						if (OUT_CueFile.GlobalDiscInfo.Catalog != null)
							job.Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CATALOG command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.Catalog = new CUE_File.Command.CATALOG() { Value = clp.ReadToken() });
						break;

					case "CDTEXTFILE":
						if (OUT_CueFile.GlobalDiscInfo.CDTextFile != null)
							job.Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CDTEXTFILE command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.CDTextFile = new CUE_File.Command.CDTEXTFILE() { Path = clp.ReadPath() });
						break;

					case "FILE":
						{
							var path = clp.ReadPath();
							CueFileType ft;
							if (clp.EOF)
							{
								job.Error("FILE command is missing file type.");
								ft = CueFileType.Unspecified;
							}
							else
							{
								var strType = clp.ReadToken().ToUpperInvariant();
								switch (strType)
								{
									default:
										job.Error("Unknown FILE type: " + strType);
										ft = CueFileType.Unspecified;
										break;
									case "BINARY": ft = CueFileType.BINARY; break;
									case "MOTOROLA": ft = CueFileType.MOTOROLA; break;
									case "BINARAIFF": ft = CueFileType.AIFF; break;
									case "WAVE": ft = CueFileType.WAVE; break;
									case "MP3": ft = CueFileType.MP3; break;
								}
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.FILE() { Path = path, Type = ft });
						}
						break;

					case "FLAGS":
						{
							var cmd = new CUE_File.Command.FLAGS();
							OUT_CueFile.Commands.Add(cmd);
							while (!clp.EOF)
							{
								var flag = clp.ReadToken().ToUpperInvariant();
								switch (flag)
								{
									case "DATA":
									default:
										job.Warn("Unknown FLAG: " + flag);
										break;
									case "DCP": cmd.Flags |= CueTrackFlags.DCP; break;
									case "4CH": cmd.Flags |= CueTrackFlags._4CH; break;
									case "PRE": cmd.Flags |= CueTrackFlags.PRE; break;
									case "SCMS": cmd.Flags |= CueTrackFlags.SCMS; break;
								}
							}
							if (cmd.Flags == CueTrackFlags.None)
								job.Warn("Empty FLAG command");
						}
						break;

					case "INDEX":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete INDEX command");
								break;
							}
							string strindexnum = clp.ReadToken();
							int indexnum;
							if (!int.TryParse(strindexnum, out indexnum) || indexnum < 0 || indexnum > 99)
							{
								job.Error("Invalid INDEX number: " + strindexnum);
								break;
							}
							string str_timestamp = clp.ReadToken();
							var ts = new Timestamp(str_timestamp);
							if (!ts.Valid)
							{
								job.Error("Invalid INDEX timestamp: " + str_timestamp);
								break;
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.INDEX() { Number = indexnum, Timestamp = ts });
						}
						break;

					case "ISRC":
						if (OUT_CueFile.GlobalDiscInfo.ISRC != null)
							job.Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty ISRC command");
						else
						{
							var isrc = clp.ReadToken();
							if (isrc.Length != 12)
								job.Warn("Invalid ISRC code ignored: " + isrc);
							else
							{
								OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.ISRC = new CUE_File.Command.ISRC() { Value = isrc });
							}
						}
						break;

					case "PERFORMER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.PERFORMER() { Value = clp.ReadPath() ?? "" });
						break;

					case "POSTGAP":
					case "PREGAP":
						{
							var str_msf = clp.ReadToken();
							var msf = new Timestamp(str_msf);
							if (!msf.Valid)
								job.Error("Ignoring {0} with invalid length MSF: " + str_msf, key);
							else
							{
								if (key == "POSTGAP")
									OUT_CueFile.Commands.Add(new CUE_File.Command.POSTGAP() { Length = msf });
								else
									OUT_CueFile.Commands.Add(new CUE_File.Command.PREGAP() { Length = msf });
							}
						}
						break;

					case "REM":
						OUT_CueFile.Commands.Add(new CUE_File.Command.REM() { Value = clp.ReadLine() });
						break;

					case "SONGWRITER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.SONGWRITER() { Value = clp.ReadPath() ?? "" });
						break;

					case "TITLE":
						OUT_CueFile.Commands.Add(new CUE_File.Command.TITLE() { Value = clp.ReadPath() ?? "" });
						break;

					case "TRACK":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete TRACK command");
								break;
							}
							string str_tracknum = clp.ReadToken();
							int tracknum;
							if (!int.TryParse(str_tracknum, out tracknum) || tracknum < 1 || tracknum > 99)
							{
								job.Error("Invalid TRACK number: " + str_tracknum);
								break;
							}

							//TODO - check sequentiality? maybe as a warning

							CueTrackType tt;
							var str_trackType = clp.ReadToken();
							switch (str_trackType.ToUpperInvariant())
							{
								default:
									job.Error("Unknown TRACK type: " + str_trackType);
									tt = CueTrackType.Unknown;
									break;
								case "AUDIO": tt = CueTrackType.Audio; break;
								case "CDG": tt = CueTrackType.CDG; break;
								case "MODE1/2048": tt = CueTrackType.Mode1_2048; break;
								case "MODE1/2352": tt = CueTrackType.Mode1_2352; break;
								case "MODE2/2336": tt = CueTrackType.Mode2_2336; break;
								case "MODE2/2352": tt = CueTrackType.Mode2_2352; break;
								case "CDI/2336": tt = CueTrackType.CDI_2336; break;
								case "CDI/2352": tt = CueTrackType.CDI_2352; break;
							}

							OUT_CueFile.Commands.Add(new CUE_File.Command.TRACK() { Number = tracknum, Type = tt });
						}
						break;
				}

				if (!clp.EOF)
				{
					var remainder = clp.ReadLine();
					if (remainder.TrimStart().StartsWith(";"))
					{
						//add a comment
						OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT() { Value = remainder });
					}
					else job.Warn("Unknown text at end of line after processing command: " + key);
				}

			} //end cue parsing loop

			job.FinishLog();
		} //LoadFromString
Exemple #2
0
		public void LoadFromString(string cueString)
		{
			TextReader tr = new StringReader(cueString);

			bool track_has_pregap = false;
			bool track_has_postgap = false;
			int last_index_num = -1;
			CueFile currFile = null;
			CueTrack currTrack = null;
			for (; ; )
			{
				string line = tr.ReadLine();
				if (line == null) break;
				line = line.Trim();
				if (line == "") continue;
				var clp = new CueLineParser(line);

				string key = clp.ReadToken().ToUpper();
				switch (key)
				{
					case "REM":
						break;

					case "FILE":
						{
							currTrack = null;
							currFile = new CueFile();
							Files.Add(currFile);
							currFile.Path = clp.ReadPath().Trim('"');
							if (!clp.EOF)
							{
								string temp = clp.ReadToken().ToUpper();
								switch (temp)
								{
									case "BINARY":
										currFile.FileType = CueFileType.Binary;
										break;
									case "WAVE":
									case "MP3":
										currFile.FileType = CueFileType.Wave;
										break;
								}
								currFile.StrFileType = temp;
							}
							break;
						}
					case "TRACK":
						{
							if (currFile == null) throw new CueBrokenException("invalid cue structure");
							if (clp.EOF) throw new CueBrokenException("invalid cue structure");
							string strtracknum = clp.ReadToken();
							int tracknum;
							if (!int.TryParse(strtracknum, out tracknum))
								throw new CueBrokenException("malformed track number");
							if (clp.EOF) throw new CueBrokenException("invalid cue structure");
							if (tracknum < 0 || tracknum > 99) throw new CueBrokenException("`All track numbers must be between 1 and 99 inclusive.`");
							string strtracktype = clp.ReadToken().ToUpper();
							currTrack = new CueTrack();
							switch (strtracktype)
							{
								case "MODE1/2352": currTrack.TrackType = ETrackType.Mode1_2352; break;
								case "MODE1/2048": currTrack.TrackType = ETrackType.Mode1_2048; break;
								case "MODE2/2352": currTrack.TrackType = ETrackType.Mode2_2352; break;
								case "AUDIO": currTrack.TrackType = ETrackType.Audio; break;
								default:
									throw new CueBrokenException("unhandled track type");
							}
							currTrack.TrackNum = tracknum;
							currFile.Tracks.Add(currTrack);
							track_has_pregap = false;
							track_has_postgap = false;
							last_index_num = -1;
							break;
						}
					case "INDEX":
						{
							if (currTrack == null) throw new CueBrokenException("invalid cue structure");
							if (clp.EOF) throw new CueBrokenException("invalid cue structure");
							if (track_has_postgap) throw new CueBrokenException("`The POSTGAP command must appear after all INDEX commands for the current track.`");
							string strindexnum = clp.ReadToken();
							int indexnum;
							if (!int.TryParse(strindexnum, out indexnum))
								throw new CueBrokenException("malformed index number");
							if (clp.EOF) throw new CueBrokenException("invalid cue structure (missing index timestamp)");
							string str_timestamp = clp.ReadToken();
							if (indexnum < 0 || indexnum > 99) throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`");
							if (indexnum != 1 && indexnum != last_index_num + 1) throw new CueBrokenException("`The first index must be 0 or 1 with all other indexes being sequential to the first one.`");
							last_index_num = indexnum;
							CueTrackIndex cti = new CueTrackIndex(indexnum)
								{
									Timestamp = new Timestamp(str_timestamp), IndexNum = indexnum
								};
							currTrack.Indexes[indexnum] = cti;
							break;
						}
					case "PREGAP":
						if (track_has_pregap) throw new CueBrokenException("`Only one PREGAP command is allowed per track.`");
						if (currTrack.Indexes.Count > 0) throw new CueBrokenException("`The PREGAP command must appear after a TRACK command, but before any INDEX commands.`");
						currTrack.PreGap = new Timestamp(clp.ReadToken());
						track_has_pregap = true;
						break;
					case "POSTGAP":
						if (track_has_postgap) throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`");
						track_has_postgap = true;
						currTrack.PostGap = new Timestamp(clp.ReadToken());
						break;
					case "CATALOG":
					case "PERFORMER":
					case "SONGWRITER":
					case "TITLE":
					case "ISRC":
						//TODO - keep these for later?
						break;
					default:
						throw new CueBrokenException("unsupported cue command: " + key);
				}
			} //end cue parsing loop
		}
Exemple #3
0
        public void LoadFromString(string cueString)
        {
            TextReader tr = new StringReader(cueString);

            bool     track_has_pregap  = false;
            bool     track_has_postgap = false;
            int      last_index_num    = -1;
            CueFile  currFile          = null;
            CueTrack currTrack         = null;

            for (; ;)
            {
                string line = tr.ReadLine();
                if (line == null)
                {
                    break;
                }
                line = line.Trim();
                if (line == "")
                {
                    continue;
                }
                var clp = new CueLineParser(line);

                string key = clp.ReadToken().ToUpper();
                switch (key)
                {
                case "REM":
                    break;

                case "FILE":
                {
                    currTrack = null;
                    currFile  = new CueFile();
                    Files.Add(currFile);
                    currFile.Path = clp.ReadPath().Trim('"');
                    if (!clp.EOF)
                    {
                        string temp = clp.ReadToken().ToUpper();
                        switch (temp)
                        {
                        case "BINARY":
                            currFile.FileType = CueFileType.Binary;
                            break;

                        case "WAVE":
                        case "MP3":
                            currFile.FileType = CueFileType.Wave;
                            break;
                        }
                        currFile.StrFileType = temp;
                    }
                    break;
                }

                case "TRACK":
                {
                    if (currFile == null)
                    {
                        throw new CueBrokenException("invalid cue structure");
                    }
                    if (clp.EOF)
                    {
                        throw new CueBrokenException("invalid cue structure");
                    }
                    string strtracknum = clp.ReadToken();
                    int    tracknum;
                    if (!int.TryParse(strtracknum, out tracknum))
                    {
                        throw new CueBrokenException("malformed track number");
                    }
                    if (clp.EOF)
                    {
                        throw new CueBrokenException("invalid cue structure");
                    }
                    if (tracknum < 0 || tracknum > 99)
                    {
                        throw new CueBrokenException("`All track numbers must be between 1 and 99 inclusive.`");
                    }
                    string strtracktype = clp.ReadToken().ToUpper();
                    currTrack = new CueTrack();
                    switch (strtracktype)
                    {
                    case "MODE1/2352": currTrack.TrackType = ETrackType.Mode1_2352; break;

                    case "MODE1/2048": currTrack.TrackType = ETrackType.Mode1_2048; break;

                    case "MODE2/2352": currTrack.TrackType = ETrackType.Mode2_2352; break;

                    case "AUDIO": currTrack.TrackType = ETrackType.Audio; break;

                    default:
                        throw new CueBrokenException("unhandled track type");
                    }
                    currTrack.TrackNum = tracknum;
                    currFile.Tracks.Add(currTrack);
                    track_has_pregap  = false;
                    track_has_postgap = false;
                    last_index_num    = -1;
                    break;
                }

                case "INDEX":
                {
                    if (currTrack == null)
                    {
                        throw new CueBrokenException("invalid cue structure");
                    }
                    if (clp.EOF)
                    {
                        throw new CueBrokenException("invalid cue structure");
                    }
                    if (track_has_postgap)
                    {
                        throw new CueBrokenException("`The POSTGAP command must appear after all INDEX commands for the current track.`");
                    }
                    string strindexnum = clp.ReadToken();
                    int    indexnum;
                    if (!int.TryParse(strindexnum, out indexnum))
                    {
                        throw new CueBrokenException("malformed index number");
                    }
                    if (clp.EOF)
                    {
                        throw new CueBrokenException("invalid cue structure (missing index timestamp)");
                    }
                    string str_timestamp = clp.ReadToken();
                    if (indexnum < 0 || indexnum > 99)
                    {
                        throw new CueBrokenException("`All index numbers must be between 0 and 99 inclusive.`");
                    }
                    if (indexnum != 1 && indexnum != last_index_num + 1)
                    {
                        throw new CueBrokenException("`The first index must be 0 or 1 with all other indexes being sequential to the first one.`");
                    }
                    last_index_num = indexnum;
                    CueTrackIndex cti = new CueTrackIndex(indexnum)
                    {
                        Timestamp = new Timestamp(str_timestamp), IndexNum = indexnum
                    };
                    currTrack.Indexes[indexnum] = cti;
                    break;
                }

                case "PREGAP":
                    if (track_has_pregap)
                    {
                        throw new CueBrokenException("`Only one PREGAP command is allowed per track.`");
                    }
                    if (currTrack.Indexes.Count > 0)
                    {
                        throw new CueBrokenException("`The PREGAP command must appear after a TRACK command, but before any INDEX commands.`");
                    }
                    currTrack.PreGap = new Timestamp(clp.ReadToken());
                    track_has_pregap = true;
                    break;

                case "POSTGAP":
                    if (track_has_postgap)
                    {
                        throw new CueBrokenException("`Only one POSTGAP command is allowed per track.`");
                    }
                    track_has_postgap = true;
                    currTrack.PostGap = new Timestamp(clp.ReadToken());
                    break;

                case "CATALOG":
                case "PERFORMER":
                case "SONGWRITER":
                case "TITLE":
                case "ISRC":
                    //TODO - keep these for later?
                    break;

                default:
                    throw new CueBrokenException("unsupported cue command: " + key);
                }
            }             //end cue parsing loop
        }
Exemple #4
0
		void LoadFromString(ParseCueJob job)
		{
			string cueString = job.IN_CueString;
			TextReader tr = new StringReader(cueString);

			for (; ; )
			{
				job.CurrentLine++;
				string line = tr.ReadLine();
				if (line == null) break;
				line = line.Trim();
				if (line == "") continue;
				var clp = new CueLineParser(line);

				string key = clp.ReadToken().ToUpperInvariant();
				
				//remove nonsense at beginning
				if (!IN_Strict)
				{
					while (key.Length > 0)
					{
						char c = key[0];
						if(c == ';') break;
						if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) break;
						key = key.Substring(1);
					}
				}

				bool startsWithSemicolon = key.StartsWith(";");

				if (startsWithSemicolon)
				{
					clp.EOF = true;
					OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT() { Value = line });
				}
				else switch (key)
				{
					default:
						job.Warn("Unknown command: " + key);
						break;

					case "CATALOG":
						if (OUT_CueFile.GlobalDiscInfo.Catalog != null)
							job.Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CATALOG command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.Catalog = new CUE_File.Command.CATALOG() { Value = clp.ReadToken() });
						break;

					case "CDTEXTFILE":
						if (OUT_CueFile.GlobalDiscInfo.CDTextFile != null)
							job.Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CDTEXTFILE command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.CDTextFile = new CUE_File.Command.CDTEXTFILE() { Path = clp.ReadPath() });
						break;

					case "FILE":
						{
							var path = clp.ReadPath();
							CueFileType ft;
							if (clp.EOF)
							{
								job.Error("FILE command is missing file type.");
								ft = CueFileType.Unspecified;
							}
							else
							{
								var strType = clp.ReadToken().ToUpperInvariant();
								switch (strType)
								{
									default:
										job.Error("Unknown FILE type: " + strType);
										ft = CueFileType.Unspecified;
										break;
									case "BINARY": ft = CueFileType.BINARY; break;
									case "MOTOROLA": ft = CueFileType.MOTOROLA; break;
									case "BINARAIFF": ft = CueFileType.AIFF; break;
									case "WAVE": ft = CueFileType.WAVE; break;
									case "MP3": ft = CueFileType.MP3; break;
								}
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.FILE() { Path = path, Type = ft });
						}
						break;

					case "FLAGS":
						{
							var cmd = new CUE_File.Command.FLAGS();
							OUT_CueFile.Commands.Add(cmd);
							while (!clp.EOF)
							{
								var flag = clp.ReadToken().ToUpperInvariant();
								switch (flag)
								{
									case "DATA":
									default:
										job.Warn("Unknown FLAG: " + flag);
										break;
									case "DCP": cmd.Flags |= CueTrackFlags.DCP; break;
									case "4CH": cmd.Flags |= CueTrackFlags._4CH; break;
									case "PRE": cmd.Flags |= CueTrackFlags.PRE; break;
									case "SCMS": cmd.Flags |= CueTrackFlags.SCMS; break;
								}
							}
							if (cmd.Flags == CueTrackFlags.None)
								job.Warn("Empty FLAG command");
						}
						break;

					case "INDEX":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete INDEX command");
								break;
							}
							string strindexnum = clp.ReadToken();
							int indexnum;
							if (!int.TryParse(strindexnum, out indexnum) || indexnum < 0 || indexnum > 99)
							{
								job.Error("Invalid INDEX number: " + strindexnum);
								break;
							}
							string str_timestamp = clp.ReadToken();
							var ts = new Timestamp(str_timestamp);
							if (!ts.Valid && !IN_Strict)
							{
								//try cleaning it up
								str_timestamp = Regex.Replace(str_timestamp, "[^0-9:]", "");
								ts = new Timestamp(str_timestamp);
							}
							if (!ts.Valid)
							{
								if (IN_Strict)
									job.Error("Invalid INDEX timestamp: " + str_timestamp);
								break;
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.INDEX() { Number = indexnum, Timestamp = ts });
						}
						break;

					case "ISRC":
						if (OUT_CueFile.GlobalDiscInfo.ISRC != null)
							job.Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty ISRC command");
						else
						{
							var isrc = clp.ReadToken();
							if (isrc.Length != 12)
								job.Warn("Invalid ISRC code ignored: " + isrc);
							else
							{
								OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.ISRC = new CUE_File.Command.ISRC() { Value = isrc });
							}
						}
						break;

					case "PERFORMER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.PERFORMER() { Value = clp.ReadPath() ?? "" });
						break;

					case "POSTGAP":
					case "PREGAP":
						{
							var str_msf = clp.ReadToken();
							var msf = new Timestamp(str_msf);
							if (!msf.Valid)
								job.Error("Ignoring {0} with invalid length MSF: " + str_msf, key);
							else
							{
								if (key == "POSTGAP")
									OUT_CueFile.Commands.Add(new CUE_File.Command.POSTGAP() { Length = msf });
								else
									OUT_CueFile.Commands.Add(new CUE_File.Command.PREGAP() { Length = msf });
							}
						}
						break;

					case "REM":
						OUT_CueFile.Commands.Add(new CUE_File.Command.REM() { Value = clp.ReadLine() });
						break;

					case "SONGWRITER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.SONGWRITER() { Value = clp.ReadPath() ?? "" });
						break;

					case "TITLE":
						OUT_CueFile.Commands.Add(new CUE_File.Command.TITLE() { Value = clp.ReadPath() ?? "" });
						break;

					case "TRACK":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete TRACK command");
								break;
							}
							string str_tracknum = clp.ReadToken();
							int tracknum;
							if (!int.TryParse(str_tracknum, out tracknum) || tracknum < 1 || tracknum > 99)
							{
								job.Error("Invalid TRACK number: " + str_tracknum);
								break;
							}

							//TODO - check sequentiality? maybe as a warning

							CueTrackType tt;
							var str_trackType = clp.ReadToken();
							switch (str_trackType.ToUpperInvariant())
							{
								default:
									job.Error("Unknown TRACK type: " + str_trackType);
									tt = CueTrackType.Unknown;
									break;
								case "AUDIO": tt = CueTrackType.Audio; break;
								case "CDG": tt = CueTrackType.CDG; break;
								case "MODE1/2048": tt = CueTrackType.Mode1_2048; break;
								case "MODE1/2352": tt = CueTrackType.Mode1_2352; break;
								case "MODE2/2336": tt = CueTrackType.Mode2_2336; break;
								case "MODE2/2352": tt = CueTrackType.Mode2_2352; break;
								case "CDI/2336": tt = CueTrackType.CDI_2336; break;
								case "CDI/2352": tt = CueTrackType.CDI_2352; break;
							}

							OUT_CueFile.Commands.Add(new CUE_File.Command.TRACK() { Number = tracknum, Type = tt });
						}
						break;
				}

				if (!clp.EOF)
				{
					var remainder = clp.ReadLine();
					if (remainder.TrimStart().StartsWith(";"))
					{
						//add a comment
						OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT() { Value = remainder });
					}
					else job.Warn("Unknown text at end of line after processing command: " + key);
				}

			} //end cue parsing loop

			job.FinishLog();
		} //LoadFromString
		private void LoadFromString(ParseCueJob job)
		{
			string cueString = job.IN_CueString;
			TextReader tr = new StringReader(cueString);

			for (; ; )
			{
				job.CurrentLine++;
				string line = tr.ReadLine();
				if (line == null) break;
				line = line.Trim();
				if (line == "") continue;
				var clp = new CueLineParser(line);

				string key = clp.ReadToken().ToUpperInvariant();
				
				//remove nonsense at beginning
				if (!IN_Strict)
				{
					while (key.Length > 0)
					{
						char c = key[0];
						if(c == ';') break;
						if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) break;
						key = key.Substring(1);
					}
				}

				bool startsWithSemicolon = key.StartsWith(";");

				if (startsWithSemicolon)
				{
					clp.EOF = true;
					OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT(line));
				}
				else switch (key)
				{
					default:
						job.Warn($"Unknown command: {key}");
						break;

					case "CATALOG":
						if (OUT_CueFile.GlobalDiscInfo.Catalog != null)
							job.Warn("Multiple CATALOG commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CATALOG command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.Catalog = new CUE_File.Command.CATALOG(clp.ReadToken()));
						break;

					case "CDTEXTFILE":
						if (OUT_CueFile.GlobalDiscInfo.CDTextFile != null)
							job.Warn("Multiple CDTEXTFILE commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty CDTEXTFILE command");
						else OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.CDTextFile = new CUE_File.Command.CDTEXTFILE(clp.ReadPath()));
						break;

					case "FILE":
						{
							var path = clp.ReadPath();
							CueFileType ft;
							if (clp.EOF)
							{
								job.Error("FILE command is missing file type.");
								ft = CueFileType.Unspecified;
							}
							else
							{
								var strType = clp.ReadToken().ToUpperInvariant();
								switch (strType)
								{
									default:
										job.Error($"Unknown FILE type: {strType}");
										ft = CueFileType.Unspecified;
										break;
									case "BINARY": ft = CueFileType.BINARY; break;
									case "MOTOROLA": ft = CueFileType.MOTOROLA; break;
									case "BINARAIFF": ft = CueFileType.AIFF; break;
									case "WAVE": ft = CueFileType.WAVE; break;
									case "MP3": ft = CueFileType.MP3; break;
								}
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.FILE(path, ft));
						}
						break;

					case "FLAGS":
						{
							CueTrackFlags flags = default;
							while (!clp.EOF)
							{
								var flag = clp.ReadToken().ToUpperInvariant();
								switch (flag)
								{
									case "DATA":
									default:
										job.Warn($"Unknown FLAG: {flag}");
										break;
									case "DCP": flags |= CueTrackFlags.DCP; break;
									case "4CH": flags |= CueTrackFlags._4CH; break;
									case "PRE": flags |= CueTrackFlags.PRE; break;
									case "SCMS": flags |= CueTrackFlags.SCMS; break;
								}
							}
							if (flags == CueTrackFlags.None)
								job.Warn("Empty FLAG command");
							OUT_CueFile.Commands.Add(new CUE_File.Command.FLAGS(flags));
						}
						break;

					case "INDEX":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete INDEX command");
								break;
							}
							string strindexnum = clp.ReadToken();
							if (!int.TryParse(strindexnum, out var indexnum) || indexnum < 0 || indexnum > 99)
							{
								job.Error($"Invalid INDEX number: {strindexnum}");
								break;
							}
							string str_timestamp = clp.ReadToken();
							var ts = new Timestamp(str_timestamp);
							if (!ts.Valid && !IN_Strict)
							{
								//try cleaning it up
								str_timestamp = Regex.Replace(str_timestamp, "[^0-9:]", "");
								ts = new Timestamp(str_timestamp);
							}
							if (!ts.Valid)
							{
								if (IN_Strict)
									job.Error($"Invalid INDEX timestamp: {str_timestamp}");
								break;
							}
							OUT_CueFile.Commands.Add(new CUE_File.Command.INDEX(indexnum, ts));
						}
						break;

					case "ISRC":
						if (OUT_CueFile.GlobalDiscInfo.ISRC != null)
							job.Warn("Multiple ISRC commands detected. Subsequent ones are ignored.");
						else if (clp.EOF)
							job.Warn("Ignoring empty ISRC command");
						else
						{
							var isrc = clp.ReadToken();
							if (isrc.Length != 12)
								job.Warn($"Invalid ISRC code ignored: {isrc}");
							else
							{
								OUT_CueFile.Commands.Add(OUT_CueFile.GlobalDiscInfo.ISRC = new CUE_File.Command.ISRC(isrc));
							}
						}
						break;

					case "PERFORMER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.PERFORMER(clp.ReadPath() ?? ""));
						break;

					case "POSTGAP":
					case "PREGAP":
						{
							var str_msf = clp.ReadToken();
							var msf = new Timestamp(str_msf);
							if (!msf.Valid)
								job.Error($"Ignoring {{0}} with invalid length MSF: {str_msf}", key);
							else
							{
								if (key == "POSTGAP")
									OUT_CueFile.Commands.Add(new CUE_File.Command.POSTGAP(msf));
								else
									OUT_CueFile.Commands.Add(new CUE_File.Command.PREGAP(msf));
							}
						}
						break;

					case "REM":
						OUT_CueFile.Commands.Add(new CUE_File.Command.REM(clp.ReadLine()));
						break;

					case "SONGWRITER":
						OUT_CueFile.Commands.Add(new CUE_File.Command.SONGWRITER(clp.ReadPath() ?? ""));
						break;

					case "TITLE":
						OUT_CueFile.Commands.Add(new CUE_File.Command.TITLE(clp.ReadPath() ?? ""));
						break;

					case "TRACK":
						{
							if (clp.EOF)
							{
								job.Error("Incomplete TRACK command");
								break;
							}

							string str_tracknum = clp.ReadToken();
							if (!int.TryParse(str_tracknum, out int tracknum) || tracknum < 1 || tracknum > 99)
							{
								job.Error($"Invalid TRACK number: {str_tracknum}");
								break;
							}

							// TODO - check sequentiality? maybe as a warning

							CueTrackType tt;
							var str_trackType = clp.ReadToken();
							switch (str_trackType.ToUpperInvariant())
							{
								default:
									job.Error($"Unknown TRACK type: {str_trackType}");
									tt = CueTrackType.Unknown;
									break;
								case "AUDIO": tt = CueTrackType.Audio; break;
								case "CDG": tt = CueTrackType.CDG; break;
								case "MODE1/2048": tt = CueTrackType.Mode1_2048; break;
								case "MODE1/2352": tt = CueTrackType.Mode1_2352; break;
								case "MODE2/2336": tt = CueTrackType.Mode2_2336; break;
								case "MODE2/2352": tt = CueTrackType.Mode2_2352; break;
								case "CDI/2336": tt = CueTrackType.CDI_2336; break;
								case "CDI/2352": tt = CueTrackType.CDI_2352; break;
							}

							OUT_CueFile.Commands.Add(new CUE_File.Command.TRACK(tracknum, tt));
						}
						break;
				}

				if (!clp.EOF)
				{
					var remainder = clp.ReadLine();
					if (remainder.TrimStart().StartsWith(";"))
					{
						//add a comment
						OUT_CueFile.Commands.Add(new CUE_File.Command.COMMENT(remainder));
					}
					else job.Warn($"Unknown text at end of line after processing command: {key}");
				}

			} //end cue parsing loop

			job.FinishLog();
		} //LoadFromString