2011-05-08 09:07:46 +00:00
using System ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.IO ;
using System.Collections.Generic ;
2011-06-20 09:09:21 +00:00
//this rule is not supported correctly: `The first track number can be greater than one, but all track numbers after the first must be sequential.`
2011-08-03 00:57:01 +00:00
namespace BizHawk.DiscSystem
2011-05-08 09:07:46 +00:00
{
partial class Disc
{
2012-01-21 21:23:19 +00:00
/// <summary>
/// finds a file in the same directory with an extension alternate to the supplied one.
/// If two are found, an exception is thrown (later, we may have heuristics to try to acquire the desired content)
/// </summary>
string FindAlternateExtensionFile ( string path , bool caseSensitive )
{
string targetFragment = Path . GetFileNameWithoutExtension ( path ) ;
var di = new FileInfo ( path ) . Directory ;
var results = new List < FileInfo > ( ) ;
foreach ( var fi in di . GetFiles ( ) )
{
string fragment = Path . GetFileNameWithoutExtension ( fi . FullName ) ;
int cmp = string . Compare ( fragment , targetFragment , ! caseSensitive ) ;
if ( cmp = = 0 )
results . Add ( fi ) ;
}
if ( results . Count = = 0 ) throw new DiscReferenceException ( path , "Cannot find the specified file" ) ;
if ( results . Count > 1 ) throw new DiscReferenceException ( path , "Cannot choose between multiple options" ) ;
return results [ 0 ] . FullName ;
}
void FromCuePathInternal ( string cuePath , CueBinPrefs prefs )
2011-05-08 09:07:46 +00:00
{
string cueDir = Path . GetDirectoryName ( cuePath ) ;
var cue = new Cue ( ) ;
cue . LoadFromPath ( cuePath ) ;
var session = new DiscTOC . Session ( ) ;
session . num = 1 ;
TOC . Sessions . Add ( session ) ;
2011-06-20 09:09:21 +00:00
var pregap_sector = new Sector_Zero ( ) ;
int curr_track = 1 ;
2011-05-08 09:07:46 +00:00
foreach ( var cue_file in cue . Files )
{
2011-06-20 09:09:21 +00:00
//structural validation
if ( cue_file . Tracks . Count < 1 ) throw new Cue . CueBrokenException ( "`You must specify at least one track per file.`" ) ;
2011-08-06 10:43:05 +00:00
string blobPath = Path . Combine ( cueDir , cue_file . Path ) ;
2011-06-20 09:09:21 +00:00
int blob_sectorsize = Cue . BINSectorSizeForTrackType ( cue_file . Tracks [ 0 ] . TrackType ) ;
2011-08-15 10:43:36 +00:00
int blob_length_aba , blob_leftover ;
2011-08-06 10:43:05 +00:00
IBlob cue_blob = null ;
2011-06-20 09:09:21 +00:00
2012-01-21 21:23:19 +00:00
//try any way we can to acquire a file
if ( ! File . Exists ( blobPath ) & & prefs . ExtensionAware )
{
blobPath = FindAlternateExtensionFile ( blobPath , prefs . CaseSensitive ) ;
}
if ( ! File . Exists ( blobPath ) )
throw new DiscReferenceException ( blobPath , "" ) ;
//some simple rules to mutate the file type if we received something fishy
string blobPathExt = Path . GetExtension ( blobPath ) . ToLower ( ) ;
if ( blobPathExt = = ".ape" ) cue_file . FileType = Cue . CueFileType . Wave ;
if ( blobPathExt = = ".mp3" ) cue_file . FileType = Cue . CueFileType . Wave ;
if ( blobPathExt = = ".mpc" ) cue_file . FileType = Cue . CueFileType . Wave ;
if ( blobPathExt = = ".flac" ) cue_file . FileType = Cue . CueFileType . Wave ;
if ( cue_file . FileType = = Cue . CueFileType . Binary | | cue_file . FileType = = Cue . CueFileType . Unspecified )
2011-08-06 10:43:05 +00:00
{
//make a blob for the file
Blob_RawFile blob = new Blob_RawFile ( ) ;
blob . PhysicalPath = blobPath ;
Blobs . Add ( blob ) ;
2011-08-15 10:43:36 +00:00
blob_length_aba = ( int ) ( blob . Length / blob_sectorsize ) ;
blob_leftover = ( int ) ( blob . Length - blob_length_aba * blob_sectorsize ) ;
2011-08-06 10:43:05 +00:00
cue_blob = blob ;
}
else if ( cue_file . FileType = = Cue . CueFileType . Wave )
{
Blob_WaveFile blob = new Blob_WaveFile ( ) ;
2011-09-04 06:26:35 +00:00
Blobs . Add ( blob ) ;
2011-08-08 01:48:31 +00:00
2011-08-06 10:43:05 +00:00
try
{
2011-08-08 06:09:44 +00:00
//check whether we can load the wav directly
bool loaded = false ;
if ( File . Exists ( blobPath ) & & Path . GetExtension ( blobPath ) . ToUpper ( ) = = ".WAV" )
{
try
{
blob . Load ( blobPath ) ;
loaded = true ;
}
catch
{
}
}
//if that didnt work or wasnt possible, try loading it through ffmpeg
if ( ! loaded )
2011-08-08 01:48:31 +00:00
{
2011-08-08 02:02:01 +00:00
FFMpeg ffmpeg = new FFMpeg ( ) ;
if ( ! ffmpeg . QueryServiceAvailable ( ) )
{
2011-08-08 06:09:44 +00:00
throw new InvalidOperationException ( "No decoding service was available (make sure ffmpeg.exe is available. even though this may be a wav, ffmpeg is used to load oddly formatted wave files)" ) ;
2011-08-08 02:02:01 +00:00
}
2011-08-08 01:48:31 +00:00
AudioDecoder dec = new AudioDecoder ( ) ;
byte [ ] buf = dec . AcquireWaveData ( blobPath ) ;
blob . Load ( new MemoryStream ( buf ) ) ;
WasSlowLoad = true ;
}
2011-08-06 10:43:05 +00:00
}
catch ( Exception ex )
{
throw new DiscReferenceException ( blobPath , ex ) ;
}
2011-05-08 09:07:46 +00:00
2011-08-15 10:43:36 +00:00
blob_length_aba = ( int ) ( blob . Length / blob_sectorsize ) ;
blob_leftover = ( int ) ( blob . Length - blob_length_aba * blob_sectorsize ) ;
2011-08-06 10:43:05 +00:00
cue_blob = blob ;
}
2012-01-21 21:23:19 +00:00
else throw new Exception ( "Internal error - Unhandled cue blob type" ) ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//TODO - make CueTimestamp better, and also make it a struct, and also just make it DiscTimestamp
//start timekeeping for the blob. every time we hit an index, this will advance
int blob_timestamp = 0 ;
2011-05-08 09:07:46 +00:00
2011-08-15 10:43:36 +00:00
//the aba that this cue blob starts on
int blob_disc_aba_start = Sectors . Count ;
2011-08-06 10:43:05 +00:00
2011-06-20 09:09:21 +00:00
//for each track within the file, create an index 0 if it is missing.
//also check to make sure there is an index 1
for ( int t = 0 ; t < cue_file . Tracks . Count ; t + + )
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
var cue_track = cue_file . Tracks [ t ] ;
if ( ! cue_track . Indexes . ContainsKey ( 1 ) )
throw new Cue . CueBrokenException ( "Track was missing an index 01" ) ;
if ( ! cue_track . Indexes . ContainsKey ( 0 ) )
{
//index 0 will default to the same as index 1.
//i am not sure whether it is valid to have two indexes with the same timestamp.
//we will do this to simplify some processing, but we can purge it in a later pass if we need to.
var cti = new Cue . CueTrackIndex ( 0 ) ;
cue_track . Indexes [ 0 ] = cti ;
cti . Timestamp = cue_track . Indexes [ 1 ] . Timestamp ;
}
}
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//validate that the first index in the file is 00:00:00
2011-08-15 10:43:36 +00:00
if ( cue_file . Tracks [ 0 ] . Indexes [ 0 ] . Timestamp . ABA ! = 0 ) throw new Cue . CueBrokenException ( "`The first index of a blob must start at 00:00:00.`" ) ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//for each track within the file:
for ( int t = 0 ; t < cue_file . Tracks . Count ; t + + )
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
var cue_track = cue_file . Tracks [ t ] ;
2011-05-08 09:07:46 +00:00
2011-08-15 10:43:36 +00:00
//record the disc ABA that this track started on
int track_disc_aba_start = Sectors . Count ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//record the pregap location. it will default to the start of the track unless we supplied a pregap command
2011-08-15 10:43:36 +00:00
int track_disc_pregap_aba = track_disc_aba_start ;
2011-05-08 09:07:46 +00:00
2011-08-06 21:40:52 +00:00
int blob_track_start = blob_timestamp ;
2011-06-20 09:09:21 +00:00
//enforce a rule of our own: every track within the file must have the same sector size
//we do know that files can change between track types within a file, but we're not sure what to do if the sector size changes
2011-08-06 10:43:05 +00:00
if ( Cue . BINSectorSizeForTrackType ( cue_track . TrackType ) ! = blob_sectorsize ) throw new Cue . CueBrokenException ( "Found different sector sizes within a cue blob. We don't know how to handle that." ) ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//check integrity of track sequence and setup data structures
//TODO - check for skipped tracks in cue parser instead
if ( cue_track . TrackNum ! = curr_track ) throw new Cue . CueBrokenException ( "Found a cue with skipped tracks" ) ;
var toc_track = new DiscTOC . Track ( ) ;
toc_track . num = curr_track ;
toc_track . TrackType = cue_track . TrackType ;
session . Tracks . Add ( toc_track ) ;
2011-05-08 09:07:46 +00:00
2011-08-06 21:40:52 +00:00
if ( curr_track = = 1 )
{
2011-08-15 10:43:36 +00:00
if ( cue_track . PreGap . ABA ! = 0 )
2011-08-14 23:13:31 +00:00
throw new InvalidOperationException ( "not supported (yet): cue files with track 1 pregaps" ) ;
//but now we add one anyway, because every existing cue+bin seems to implicitly specify this
cue_track . PreGap = new Timestamp ( 150 ) ;
2011-08-06 21:40:52 +00:00
}
//check whether a pregap is requested.
//this causes empty sectors to get generated without consuming data from the blob
2011-08-15 10:43:36 +00:00
if ( cue_track . PreGap . ABA > 0 )
2011-05-08 09:07:46 +00:00
{
2011-08-15 10:43:36 +00:00
for ( int i = 0 ; i < cue_track . PreGap . ABA ; i + + )
2011-08-06 21:40:52 +00:00
{
Sectors . Add ( new SectorEntry ( pregap_sector ) ) ;
}
2011-05-08 09:07:46 +00:00
}
2011-08-06 21:40:52 +00:00
//look ahead to the next track's index 1 so we can see how long this track's last index is
2011-06-20 09:09:21 +00:00
//or, for the last track, use the length of the file
2011-08-15 10:43:36 +00:00
int track_length_aba ;
2011-06-20 09:09:21 +00:00
if ( t = = cue_file . Tracks . Count - 1 )
2011-08-15 10:43:36 +00:00
track_length_aba = blob_length_aba - blob_timestamp ;
else track_length_aba = cue_file . Tracks [ t + 1 ] . Indexes [ 1 ] . Timestamp . ABA - blob_timestamp ;
//toc_track.length_aba = track_length_aba; //xxx
2011-06-20 09:09:21 +00:00
//find out how many indexes we have
int num_indexes = 0 ;
for ( num_indexes = 0 ; num_indexes < = 99 ; num_indexes + + )
if ( ! cue_track . Indexes . ContainsKey ( num_indexes ) ) break ;
//for each index, calculate length of index and then emit it
for ( int index = 0 ; index < num_indexes ; index + + )
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
bool is_last_index = index = = num_indexes - 1 ;
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//install index into hierarchy
var toc_index = new DiscTOC . Index ( ) ;
toc_index . num = index ;
toc_track . Indexes . Add ( toc_index ) ;
2011-08-07 09:05:10 +00:00
if ( index = = 0 )
{
2011-08-15 10:43:36 +00:00
toc_index . aba = track_disc_pregap_aba - ( cue_track . Indexes [ 1 ] . Timestamp . ABA - cue_track . Indexes [ 0 ] . Timestamp . ABA ) ;
2011-08-07 09:05:10 +00:00
}
2011-08-15 10:43:36 +00:00
else toc_index . aba = Sectors . Count ;
2011-06-20 09:09:21 +00:00
//calculate length of the index
//if it is the last index then we use our calculation from before, otherwise we check the next index
2011-08-15 10:43:36 +00:00
int index_length_aba ;
2011-06-20 09:09:21 +00:00
if ( is_last_index )
2011-08-15 10:43:36 +00:00
index_length_aba = track_length_aba - ( blob_timestamp - blob_track_start ) ;
else index_length_aba = cue_track . Indexes [ index + 1 ] . Timestamp . ABA - blob_timestamp ;
2011-06-20 09:09:21 +00:00
//emit sectors
2011-08-15 10:43:36 +00:00
for ( int aba = 0 ; aba < index_length_aba ; aba + + )
2011-06-20 09:09:21 +00:00
{
2011-08-15 10:43:36 +00:00
bool is_last_aba_in_index = ( aba = = index_length_aba - 1 ) ;
bool is_last_aba_in_track = is_last_aba_in_index & & is_last_index ;
2011-06-20 09:09:21 +00:00
switch ( cue_track . TrackType )
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
case ETrackType . Audio : //all 2352 bytes are present
case ETrackType . Mode1_2352 : //2352 bytes are present, containing 2048 bytes of user data as well as ECM
case ETrackType . Mode2_2352 : //2352 bytes are present, containing 2336 bytes of user data, with no ECM
{
//these cases are all 2352 bytes
//in all these cases, either no ECM is present or ECM is provided.
//so we just emit a Sector_Raw
Sector_RawBlob sector_rawblob = new Sector_RawBlob ( ) ;
2011-08-06 10:43:05 +00:00
sector_rawblob . Blob = cue_blob ;
2011-06-20 09:09:21 +00:00
sector_rawblob . Offset = ( long ) blob_timestamp * 2352 ;
Sector_Raw sector_raw = new Sector_Raw ( ) ;
sector_raw . BaseSector = sector_rawblob ;
//take care to handle final sectors that are too short.
2011-08-15 10:43:36 +00:00
if ( is_last_aba_in_track & & blob_leftover > 0 )
2011-06-20 09:09:21 +00:00
{
Sector_ZeroPad sector_zeropad = new Sector_ZeroPad ( ) ;
sector_zeropad . BaseSector = sector_rawblob ;
sector_zeropad . BaseLength = 2352 - blob_leftover ;
sector_raw . BaseSector = sector_zeropad ;
Sectors . Add ( new SectorEntry ( sector_raw ) ) ;
}
Sectors . Add ( new SectorEntry ( sector_raw ) ) ;
break ;
}
case ETrackType . Mode1_2048 :
//2048 bytes are present. ECM needs to be generated to create a full sector
{
//ECM needs to know the sector number so we have to record that here
2011-08-15 10:43:36 +00:00
int curr_disc_aba = Sectors . Count ;
var sector_2048 = new Sector_Mode1_2048 ( curr_disc_aba + 150 ) ;
2011-08-06 10:43:05 +00:00
sector_2048 . Blob = new ECMCacheBlob ( cue_blob ) ;
2011-06-20 09:09:21 +00:00
sector_2048 . Offset = ( long ) blob_timestamp * 2048 ;
if ( blob_leftover > 0 ) throw new Cue . CueBrokenException ( "TODO - Incomplete 2048 byte/sector bin files (iso files) not yet supported." ) ;
2011-08-06 10:43:05 +00:00
Sectors . Add ( new SectorEntry ( sector_2048 ) ) ;
2011-06-20 09:09:21 +00:00
break ;
}
} //switch(TrackType)
2011-08-15 10:43:36 +00:00
//we've emitted an ABA, so consume it from the blob
2011-06-20 09:09:21 +00:00
blob_timestamp + + ;
2011-08-15 10:43:36 +00:00
} //aba emit loop
2011-06-20 09:09:21 +00:00
} //index loop
//check whether a postgap is requested. if it is, we need to generate silent sectors
2011-08-15 10:43:36 +00:00
for ( int i = 0 ; i < cue_track . PostGap . ABA ; i + + )
2011-06-20 09:09:21 +00:00
{
Sectors . Add ( new SectorEntry ( pregap_sector ) ) ;
2011-05-08 09:07:46 +00:00
}
2011-06-20 09:09:21 +00:00
//we're done with the track now.
//record its length:
2011-08-15 10:43:36 +00:00
toc_track . length_aba = Sectors . Count - toc_track . Indexes [ 1 ] . aba ;
2011-06-20 09:09:21 +00:00
curr_track + + ;
} //track loop
} //file loop
2011-05-08 09:07:46 +00:00
2011-06-20 09:09:21 +00:00
//finally, analyze the length of the sessions and the entire disc by summing the lengths of the tracks
//this is a little more complex than it looks, because the length of a thing is not determined by summing it
2011-08-15 10:43:36 +00:00
//but rather by the difference in abas between start and end
TOC . length_aba = 0 ;
2011-06-20 09:09:21 +00:00
foreach ( var toc_session in TOC . Sessions )
{
var firstTrack = toc_session . Tracks [ 0 ] ;
2011-08-06 21:40:52 +00:00
//track 0, index 0 is actually -150. but cue sheets will never say that
2011-08-15 10:43:36 +00:00
//firstTrack.Indexes[0].aba -= 150;
2011-08-06 21:40:52 +00:00
2011-06-20 09:09:21 +00:00
var lastTrack = toc_session . Tracks [ toc_session . Tracks . Count - 1 ] ;
2011-08-15 10:43:36 +00:00
session . length_aba = lastTrack . Indexes [ 1 ] . aba + lastTrack . length_aba - firstTrack . Indexes [ 0 ] . aba ;
TOC . length_aba + = toc_session . length_aba ;
2011-06-20 09:09:21 +00:00
}
2011-05-08 09:07:46 +00:00
}
}
public class Cue
{
//TODO - export from isobuster and observe the SESSION directive, as well as the MSF directive.
public string DebugPrint ( )
{
StringBuilder sb = new StringBuilder ( ) ;
foreach ( CueFile cf in Files )
{
sb . AppendFormat ( "FILE \"{0}\"" , cf . Path ) ;
2011-08-06 10:43:05 +00:00
if ( cf . FileType = = CueFileType . Binary ) sb . Append ( " BINARY" ) ;
if ( cf . FileType = = CueFileType . Wave ) sb . Append ( " WAVE" ) ;
2011-05-08 09:07:46 +00:00
sb . AppendLine ( ) ;
foreach ( CueTrack ct in cf . Tracks )
{
sb . AppendFormat ( " TRACK {0:D2} {1}\n" , ct . TrackNum , ct . TrackType . ToString ( ) . Replace ( "_" , "/" ) . ToUpper ( ) ) ;
foreach ( CueTrackIndex cti in ct . Indexes . Values )
{
sb . AppendFormat ( " INDEX {0:D2} {1}\n" , cti . IndexNum , cti . Timestamp . Value ) ;
}
}
}
return sb . ToString ( ) ;
}
2011-08-06 10:43:05 +00:00
public enum CueFileType
{
Unspecified , Binary , Wave
}
2011-05-08 09:07:46 +00:00
public class CueFile
{
public string Path ;
public List < CueTrack > Tracks = new List < CueTrack > ( ) ;
2011-08-06 10:43:05 +00:00
public CueFileType FileType = CueFileType . Unspecified ;
public string StrFileType ;
2011-05-08 09:07:46 +00:00
}
public List < CueFile > Files = new List < CueFile > ( ) ;
2011-06-20 09:09:21 +00:00
public static int BINSectorSizeForTrackType ( ETrackType type )
{
2011-08-14 23:13:31 +00:00
switch ( type )
2011-06-20 09:09:21 +00:00
{
case ETrackType . Mode1_2352 :
case ETrackType . Mode2_2352 :
case ETrackType . Audio :
return 2352 ;
case ETrackType . Mode1_2048 :
return 2048 ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
}
public static string TrackTypeStringForTrackType ( ETrackType type )
2011-05-08 09:07:46 +00:00
{
2011-06-20 09:09:21 +00:00
switch ( type )
{
case ETrackType . Mode1_2352 : return "MODE1/2352" ;
case ETrackType . Mode2_2352 : return "MODE2/2352" ;
case ETrackType . Audio : return "AUDIO" ;
case ETrackType . Mode1_2048 : return "MODE1/2048" ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
}
public static string RedumpTypeStringForTrackType ( ETrackType type )
{
switch ( type )
{
case ETrackType . Mode1_2352 : return "Data/Mode 1" ;
case ETrackType . Mode1_2048 : throw new InvalidOperationException ( "guh dunno what to put here" ) ;
case ETrackType . Mode2_2352 : return "Data/Mode 2" ;
case ETrackType . Audio : return "Audio" ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
2011-05-08 09:07:46 +00:00
}
public class CueTrack
{
2011-06-20 09:09:21 +00:00
public ETrackType TrackType ;
2011-05-08 09:07:46 +00:00
public int TrackNum ;
2011-08-14 23:13:31 +00:00
public Timestamp PreGap = new Timestamp ( ) ;
public Timestamp PostGap = new Timestamp ( ) ;
2011-05-08 09:07:46 +00:00
public Dictionary < int , CueTrackIndex > Indexes = new Dictionary < int , CueTrackIndex > ( ) ;
}
public class CueTrackIndex
{
2011-06-20 09:09:21 +00:00
public CueTrackIndex ( int num ) { IndexNum = num ; }
2011-05-08 09:07:46 +00:00
public int IndexNum ;
2011-08-14 23:13:31 +00:00
public Timestamp Timestamp ;
2011-05-08 09:07:46 +00:00
}
public class CueBrokenException : Exception
{
public CueBrokenException ( string why )
: base ( why )
{
}
}
public void LoadFromPath ( string cuePath )
{
FileInfo fiCue = new FileInfo ( cuePath ) ;
if ( ! fiCue . Exists ) throw new FileNotFoundException ( ) ;
File . ReadAllText ( cuePath ) ;
TextReader tr = new StreamReader ( cuePath ) ;
2011-06-20 09:09:21 +00:00
bool track_has_pregap = false ;
bool track_has_postgap = false ;
int last_index_num = - 1 ;
2011-05-08 09:07:46 +00:00
CueFile currFile = null ;
CueTrack currTrack = null ;
for ( ; ; )
{
string line = tr . ReadLine ( ) ;
if ( line = = null ) break ;
if ( line = = "" ) continue ;
line = line . Trim ( ) ;
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 ( ) ;
2011-08-06 10:43:05 +00:00
switch ( temp )
{
case "BINARY" :
currFile . FileType = CueFileType . Binary ;
break ;
case "WAVE" :
2011-08-08 04:48:28 +00:00
case "MP3" :
2011-08-06 10:43:05 +00:00
currFile . FileType = CueFileType . Wave ;
break ;
}
currFile . StrFileType = temp ;
2011-05-08 09:07:46 +00:00
}
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" ) ;
2011-06-20 09:09:21 +00:00
if ( tracknum < 0 | | tracknum > 99 ) throw new CueBrokenException ( "`All track numbers must be between 1 and 99 inclusive.`" ) ;
2011-05-08 09:07:46 +00:00
string strtracktype = clp . ReadToken ( ) . ToUpper ( ) ;
currTrack = new CueTrack ( ) ;
switch ( strtracktype )
{
2011-06-20 09:09:21 +00:00
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 ;
2011-05-08 09:07:46 +00:00
default :
throw new CueBrokenException ( "unhandled track type" ) ;
}
currTrack . TrackNum = tracknum ;
currFile . Tracks . Add ( currTrack ) ;
2011-06-20 09:09:21 +00:00
track_has_pregap = false ;
track_has_postgap = false ;
last_index_num = - 1 ;
2011-05-08 09:07:46 +00:00
break ;
}
case "INDEX" :
{
if ( currTrack = = null ) throw new CueBrokenException ( "invalid cue structure" ) ;
if ( clp . EOF ) throw new CueBrokenException ( "invalid cue structure" ) ;
2011-06-20 09:09:21 +00:00
if ( track_has_postgap ) throw new CueBrokenException ( "`The POSTGAP command must appear after all INDEX commands for the current track.`" ) ;
2011-05-08 09:07:46 +00:00
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 ( ) ;
2011-08-14 23:13:31 +00:00
if ( indexnum < 0 | | indexnum > 99 ) throw new CueBrokenException ( "`All index numbers must be between 0 and 99 inclusive.`" ) ;
2011-06-20 09:09:21 +00:00
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 ) ;
2011-08-14 23:13:31 +00:00
cti . Timestamp = new Timestamp ( str_timestamp ) ;
2011-05-08 09:07:46 +00:00
cti . IndexNum = indexnum ;
currTrack . Indexes [ indexnum ] = cti ;
break ;
}
case "PREGAP" :
2011-06-20 09:09:21 +00:00
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.`" ) ;
2011-08-14 23:13:31 +00:00
currTrack . PreGap = new Timestamp ( clp . ReadToken ( ) ) ;
2011-06-20 09:09:21 +00:00
track_has_pregap = true ;
break ;
2011-05-08 09:07:46 +00:00
case "POSTGAP" :
2011-08-09 02:33:03 +00:00
if ( track_has_postgap ) throw new CueBrokenException ( "`Only one POSTGAP command is allowed per track.`" ) ;
2011-06-20 09:09:21 +00:00
track_has_postgap = true ;
2011-08-14 23:13:31 +00:00
currTrack . PostGap = new Timestamp ( clp . ReadToken ( ) ) ;
2011-06-20 09:09:21 +00:00
break ;
2011-08-07 09:05:10 +00:00
case "CATALOG" :
case "PERFORMER" :
case "SONGWRITER" :
case "TITLE" :
case "ISRC" :
//TODO - keep these for later?
break ;
2011-05-08 09:07:46 +00:00
default :
throw new CueBrokenException ( "unsupported cue command: " + key ) ;
}
2011-06-20 09:09:21 +00:00
} //end cue parsing loop
2011-05-08 09:07:46 +00:00
}
class CueLineParser
{
int index ;
string str ;
public bool EOF ;
public CueLineParser ( string line )
{
this . str = line ;
}
public string ReadPath ( ) { return ReadToken ( true ) ; }
public string ReadToken ( ) { return ReadToken ( false ) ; }
public string ReadToken ( bool isPath )
{
if ( EOF ) return null ;
int startIndex = index ;
bool inToken = false ;
bool inQuote = false ;
for ( ; ; )
{
bool done = false ;
char c = str [ index ] ;
bool isWhiteSpace = ( c = = ' ' | | c = = '\t' ) ;
if ( isWhiteSpace )
{
if ( inQuote )
index + + ;
else
{
if ( inToken )
done = true ;
else
index + + ;
}
}
else
{
bool startedQuote = false ;
if ( ! inToken )
{
startIndex = index ;
if ( isPath & & c = = '"' )
startedQuote = inQuote = true ;
inToken = true ;
}
switch ( str [ index ] )
{
case '"' :
index + + ;
if ( inQuote & & ! startedQuote )
{
done = true ;
}
break ;
case '\\' :
index + + ;
break ;
default :
index + + ;
break ;
}
}
if ( index = = str . Length )
{
EOF = true ;
done = true ;
}
if ( done ) break ;
}
return str . Substring ( startIndex , index - startIndex ) ;
}
}
}
}