public void Run() { //in params var cue = IN_CueFile; //output state OUT_GlobalCDText = new CompiledCDText(); OUT_CompiledDiscInfo = new CompiledDiscInfo(); OUT_CompiledCueFiles = new List<CompiledCueFile>(); OUT_CompiledCueTracks = new List<CompiledCueTrack>(); //add a track 0, for addressing convenience. //note: for future work, track 0 may need emulation (accessible by very negative LBA--the TOC is stored there) var track0 = new CompiledCueTrack() { Number = 0, }; OUT_CompiledCueTracks.Add(track0); //global cd text will acquire the cdtext commands set before track commands curr_cdtext = OUT_GlobalCDText; for (int i = 0; i < cue.Commands.Count; i++) { var cmd = cue.Commands[i]; //these commands get dealt with globally. nothing to be done here //(but in the future we need to accumulate them into the compile pass output) if (cmd is CUE_File.Command.CATALOG || cmd is CUE_File.Command.CDTEXTFILE) continue; //nothing to be done for comments if (cmd is CUE_File.Command.REM) continue; if (cmd is CUE_File.Command.COMMENT) continue; //CD-text and related if (cmd is CUE_File.Command.PERFORMER) curr_cdtext.Performer = (cmd as CUE_File.Command.PERFORMER).Value; if (cmd is CUE_File.Command.SONGWRITER) curr_cdtext.Songwriter = (cmd as CUE_File.Command.SONGWRITER).Value; if (cmd is CUE_File.Command.TITLE) curr_cdtext.Title = (cmd as CUE_File.Command.TITLE).Value; if (cmd is CUE_File.Command.ISRC) curr_cdtext.ISRC = (cmd as CUE_File.Command.ISRC).Value; //flags can only be set when a track command is running if (cmd is CUE_File.Command.FLAGS) { if (curr_track == null) Warn("Ignoring invalid flag commands outside of a track command"); else //take care to |= it here, so the data flag doesn't get cleared curr_track.Flags |= (cmd as CUE_File.Command.FLAGS).Flags; } if (cmd is CUE_File.Command.TRACK) { CloseTrack(); OpenTrack(cmd as CUE_File.Command.TRACK); } if (cmd is CUE_File.Command.FILE) { CloseFile(); OpenFile(cmd as CUE_File.Command.FILE); } if (cmd is CUE_File.Command.INDEX) { //todo - validate no postgap specified AddIndex(cmd as CUE_File.Command.INDEX); } if (cmd is CUE_File.Command.PREGAP) { //validate track open //validate no indexes curr_track.PregapLength = (cmd as CUE_File.Command.PREGAP).Length; } if (cmd is CUE_File.Command.POSTGAP) { curr_track.PostgapLength = (cmd as CUE_File.Command.POSTGAP).Length; } } //it's a bit odd to close the file before closing the track, but... //we need to be sure to CloseFile first to make sure the track is marked as the final one in the file CloseFile(); CloseTrack(); CreateTrack1Pregap(); FinalAnalysis(); FinishLog(); } //Run()
void EmitRawTOCEntry(CompiledCueTrack cct) { SubchannelQ toc_sq = new SubchannelQ(); //absent some kind of policy for how to set it, this is a safe assumption: byte toc_ADR = 1; toc_sq.SetStatus(toc_ADR, (EControlQ)(int)cct.Flags); toc_sq.q_tno.BCDValue = 0; //kind of a little weird here.. the track number becomes the 'point' and put in the index instead. 0 is the track number here. toc_sq.q_index = BCD2.FromDecimal(cct.Number); //not too sure about these yet toc_sq.min = BCD2.FromDecimal(0); toc_sq.sec = BCD2.FromDecimal(0); toc_sq.frame = BCD2.FromDecimal(0); toc_sq.AP_Timestamp = new Timestamp(OUT_Disc.Sectors.Count); OUT_Disc.RawTOCEntries.Add(new RawTOCEntry { QData = toc_sq }); }
void OpenTrack(CUE_File.Command.TRACK trackCommand) { curr_track = new CompiledCueTrack(); //spill cdtext data into this track curr_cdtext = curr_track.CDTextData; curr_track.BlobIndex = curr_blobIndex; curr_track.Number = trackCommand.Number; curr_track.TrackType = trackCommand.Type; //default flags if (curr_track.TrackType != CueTrackType.Audio) curr_track.Flags = CueTrackFlags.DATA; if (!curr_fileHasTrack) { curr_fileHasTrack = curr_track.IsFirstInFile = true; } UpdateDiscInfo(trackCommand); }
void CloseTrack() { if (curr_track == null) return; //normalize: if an index 0 is missing, add it here if (curr_track.Indexes[0].Number != 0) { var index0 = new CompiledCueIndex(); var index1 = curr_track.Indexes[0]; index0.Number = 0; index0.FileMSF = index1.FileMSF; //same MSF as index 1 will make it effectively nonexistent //well now, if it's the first in the file, an implicit index will take its value from 00:00:00 in the file //this is the kind of thing I sought to solve originally by 'interpreting' the file, but it seems easy enough to handle this way //my carlin.cue tests this but test cases shouldnt be hard to find if (curr_track.IsFirstInFile) index0.FileMSF = new Timestamp(0); curr_track.Indexes.Insert(0, index0); } OUT_CompiledCueTracks.Add(curr_track); curr_track = null; }
void OpenTrack(CUE_File.Command.TRACK trackCommand) { //assert that a file is open if(curr_file == null) { Error("Track command encountered with no active file"); throw new DiscJobAbortException(); } curr_track = new CompiledCueTrack(); //spill cdtext data into this track curr_cdtext = curr_track.CDTextData; curr_track.BlobIndex = curr_blobIndex; curr_track.Number = trackCommand.Number; curr_track.TrackType = trackCommand.Type; //default flags if (curr_track.TrackType != CueTrackType.Audio) curr_track.Flags = CueTrackFlags.DATA; if (!curr_fileHasTrack) { curr_fileHasTrack = curr_track.IsFirstInFile = true; } UpdateDiscInfo(trackCommand); }
public override void Run() { //params var compiled = IN_CompileJob; var context = compiled.IN_CueContext; OUT_Disc = new Disc(); //generation state int curr_index; int curr_blobIndex = -1; int curr_blobMSF = -1; BlobInfo curr_blobInfo = null; long curr_blobOffset = -1; //mount all input files MountBlobs(); //unhappily, we cannot determine the length of all the tracks without knowing the length of the files //now that the files are mounted, we can figure the track lengths AnalyzeTracks(); //loop from track 1 to 99 //(track 0 isnt handled yet, that's way distant work) for (int t = 1; t < TrackInfos.Count; t++) { TrackInfo ti = TrackInfos[t]; CompiledCueTrack cct = ti.CompiledCueTrack; //--------------------------------- //setup track pregap processing //per "Example 05" on digitalx.org, pregap can come from index specification and pregap command int specifiedPregapLength = cct.PregapLength.Sector; int impliedPregapLength = cct.Indexes[1].FileMSF.Sector - cct.Indexes[0].FileMSF.Sector; int totalPregapLength = specifiedPregapLength + impliedPregapLength; //from now on we'll track relative timestamp and increment it continually int relMSF = -totalPregapLength; //read more at policies declaration //if (!context.DiscMountPolicy.CUE_PauseContradictionModeA) // relMSF += 1; //--------------------------------- //--------------------------------- //generate sectors for this track. //advance to the next file if needed if (curr_blobIndex != cct.BlobIndex) { curr_blobIndex = cct.BlobIndex; curr_blobOffset = 0; curr_blobMSF = 0; curr_blobInfo = BlobInfos[curr_blobIndex]; } //work until the next track is reached, or the end of the current file is reached, depending on the track type curr_index = 0; for (; ;) { bool trackDone = false; bool generateGap = false; if (specifiedPregapLength > 0) { //if burning through a specified pregap, count it down generateGap = true; specifiedPregapLength--; } else { //if burning through the file, select the appropriate index by inspecting the next index and seeing if we've reached it for (; ;) { if (curr_index == cct.Indexes.Count - 1) { break; } if (curr_blobMSF >= cct.Indexes[curr_index + 1].FileMSF.Sector) { curr_index++; if (curr_index == 1) { //WE ARE NOW AT INDEX 1: generate the RawTOCEntry for this track EmitRawTOCEntry(cct); } } else { break; } } } //select the track type for the subQ //it's obviously the same as the main track type usually, but during a pregap it can be different TrackInfo qTrack = ti; int qRelMSF = relMSF; if (curr_index == 0) { //tweak relMSF due to ambiguity/contradiction in yellowbook docs if (!context.DiscMountPolicy.CUE_PregapContradictionModeA) { qRelMSF++; } //[IEC10149] says there's two "intervals" of a pregap. //mednafen's pseudocode interpretation of this: //if this is a data track and the previous track was not data, the last 150 sectors of the pregap match this track and the earlier sectors (at least 75) math the previous track //I agree, so let's do it that way if (t != 1 && cct.TrackType != CueTrackType.Audio && TrackInfos[t - 1].CompiledCueTrack.TrackType == CueTrackType.Audio) { if (relMSF < -150) { qTrack = TrackInfos[t - 1]; } } } //generate the right kind of sector synth for this track SS_Base ss = null; if (generateGap) { ss = new SS_Gap { TrackType = qTrack.CompiledCueTrack.TrackType }; } else { int sectorSize = int.MaxValue; switch (qTrack.CompiledCueTrack.TrackType) { case CueTrackType.Audio: case CueTrackType.CDI_2352: case CueTrackType.Mode1_2352: case CueTrackType.Mode2_2352: ss = new SS_2352(); sectorSize = 2352; break; case CueTrackType.Mode1_2048: ss = new SS_Mode1_2048(); sectorSize = 2048; break; default: case CueTrackType.Mode2_2336: throw new InvalidOperationException($"Not supported: {cct.TrackType}"); } ss.Blob = curr_blobInfo.Blob; ss.BlobOffset = curr_blobOffset; curr_blobOffset += sectorSize; curr_blobMSF++; } ss.Policy = context.DiscMountPolicy; //setup subQ byte ADR = 1; //absent some kind of policy for how to set it, this is a safe assumption: ss.sq.SetStatus(ADR, (EControlQ)(int)qTrack.CompiledCueTrack.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count; ss.sq.Timestamp = qRelMSF; //setup subP if (curr_index == 0) { ss.Pause = true; } OUT_Disc._Sectors.Add(ss); relMSF++; if (cct.IsFinalInFile) { //sometimes, break when the file is exhausted if (curr_blobOffset >= curr_blobInfo.Length) { trackDone = true; } } else { //other times, break when the track is done //(this check is safe because it's not the final track overall if it's not the final track in a file) if (curr_blobMSF >= TrackInfos[t + 1].CompiledCueTrack.Indexes[0].FileMSF.Sector) { trackDone = true; } } if (trackDone) { break; } } //--------------------------------- //gen postgap sectors int specifiedPostgapLength = cct.PostgapLength.Sector; for (int s = 0; s < specifiedPostgapLength; s++) { var ss = new SS_Gap { TrackType = cct.TrackType // TODO - old track type in some < -150 cases? }; //-subq- byte ADR = 1; ss.sq.SetStatus(ADR, (EControlQ)(int)cct.Flags); ss.sq.q_tno = BCD2.FromDecimal(cct.Number); ss.sq.q_index = BCD2.FromDecimal(curr_index); ss.sq.AP_Timestamp = OUT_Disc._Sectors.Count; ss.sq.Timestamp = relMSF; //-subP- //always paused--is this good enough? ss.Pause = true; OUT_Disc._Sectors.Add(ss); relMSF++; } } //end track loop //add RawTOCEntries A0 A1 A2 to round out the TOC var TOCMiscInfo = new Synthesize_A0A1A2_Job { IN_FirstRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.FirstRecordedTrackNumber, IN_LastRecordedTrackNumber = IN_CompileJob.OUT_CompiledDiscInfo.LastRecordedTrackNumber, IN_Session1Format = IN_CompileJob.OUT_CompiledDiscInfo.SessionFormat, IN_LeadoutTimestamp = OUT_Disc._Sectors.Count }; TOCMiscInfo.Run(OUT_Disc.RawTOCEntries); //TODO - generate leadout, or delegates at least //blech, old crap, maybe //OUT_Disc.Structure.Synthesize_TOCPointsFromSessions(); //FinishLog(); } //Run()
public override void Run() { //in params var cue = IN_CueFile; //output state OUT_GlobalCDText = new CompiledCDText(); OUT_CompiledDiscInfo = new CompiledDiscInfo(); OUT_CompiledCueFiles = new List <CompiledCueFile>(); OUT_CompiledCueTracks = new List <CompiledCueTrack>(); //add a track 0, for addressing convenience. //note: for future work, track 0 may need emulation (accessible by very negative LBA--the TOC is stored there) var track0 = new CompiledCueTrack() { Number = 0, }; OUT_CompiledCueTracks.Add(track0); //global cd text will acquire the cdtext commands set before track commands curr_cdtext = OUT_GlobalCDText; foreach (var cmd in cue.Commands) { switch (cmd) { case CUE_File.Command.CATALOG: case CUE_File.Command.CDTEXTFILE: // these commands get dealt with globally. nothing to be done here // (but in the future we need to accumulate them into the compile pass output) continue; case CUE_File.Command.REM: case CUE_File.Command.COMMENT: // nothing to be done for comments continue; case CUE_File.Command.PERFORMER performerCmd: curr_cdtext.Performer = performerCmd.Value; break; case CUE_File.Command.SONGWRITER songwriterCmd: curr_cdtext.Songwriter = songwriterCmd.Value; break; case CUE_File.Command.TITLE titleCmd: curr_cdtext.Title = titleCmd.Value; break; case CUE_File.Command.ISRC isrcCmd: curr_cdtext.ISRC = isrcCmd.Value; break; case CUE_File.Command.FLAGS flagsCmd: // flags can only be set when a track command is running if (curr_track == null) { Warn("Ignoring invalid flag commands outside of a track command"); } else { curr_track.Flags |= flagsCmd.Flags; // take care to |= it here, so the data flag doesn't get cleared } break; case CUE_File.Command.TRACK trackCmd: CloseTrack(); OpenTrack(trackCmd); break; case CUE_File.Command.FILE fileCmd: CloseFile(); OpenFile(fileCmd); break; case CUE_File.Command.INDEX indexCmd: //TODO validate no postgap specified AddIndex(indexCmd); break; case CUE_File.Command.PREGAP pregapCmd: //TODO validate track open //TODO validate no indexes curr_track.PregapLength = pregapCmd.Length; break; case CUE_File.Command.POSTGAP postgapCmd: curr_track.PostgapLength = postgapCmd.Length; break; } } //it's a bit odd to close the file before closing the track, but... //we need to be sure to CloseFile first to make sure the track is marked as the final one in the file CloseFile(); CloseTrack(); CreateTrack1Pregap(); FinalAnalysis(); FinishLog(); } //Run()