CueSharp (1).cs 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361
  1. /*
  2. Title: CueSharp
  3. Version: 0.5
  4. Released: March 24, 2007
  5. Author: Wyatt O'Day
  6. Website: wyday.com/cuesharp
  7. */
  8. // MK: fixes to substring processing
  9. using System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Text;
  14. namespace CueSharp_N
  15. {
  16. /// <summary>
  17. /// A CueSheet class used to create, open, edit, and save cuesheets.
  18. /// </summary>
  19. public class CueSheet
  20. {
  21. #region Private Variables
  22. private string[] cueLines;
  23. private string m_Catalog = "";
  24. private string m_CDTextFile = "";
  25. private string[] m_Comments = new string[0];
  26. // strings that don't belong or were mistyped in the global part of the cue
  27. private string[] m_Garbage = new string[0];
  28. private string m_Performer = "";
  29. private string m_Songwriter = "";
  30. private string m_Title = "";
  31. private Track[] m_Tracks = new Track[0];
  32. #endregion Private Variables
  33. #region Properties
  34. /// <summary>
  35. /// Returns/Sets track in this cuefile.
  36. /// </summary>
  37. /// <param name="tracknumber">The track in this cuefile.</param>
  38. /// <returns>Track at the tracknumber.</returns>
  39. public Track this[int tracknumber]
  40. {
  41. get { return m_Tracks[tracknumber]; }
  42. set { m_Tracks[tracknumber] = value; }
  43. }
  44. /// <summary>
  45. /// The catalog number must be 13 digits long and is encoded according to UPC/EAN rules.
  46. /// Example: CATALOG 1234567890123
  47. /// </summary>
  48. public string Catalog
  49. {
  50. get { return m_Catalog; }
  51. set { m_Catalog = value; }
  52. }
  53. /// <summary>
  54. /// 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.
  55. /// </summary>
  56. public string CDTextFile
  57. {
  58. get { return m_CDTextFile; }
  59. set { m_CDTextFile = value; }
  60. }
  61. /// <summary>
  62. /// This command is used to put comments in your CUE SHEET file.
  63. /// </summary>
  64. public string[] Comments
  65. {
  66. get { return m_Comments; }
  67. set { m_Comments = value; }
  68. }
  69. /// <summary>
  70. /// Lines in the cue file that don't belong or have other general syntax errors.
  71. /// </summary>
  72. public string[] Garbage
  73. {
  74. get { return m_Garbage; }
  75. }
  76. /// <summary>
  77. /// This command is used to specify the name of a perfomer for a CD-TEXT enhanced disc.
  78. /// </summary>
  79. public string Performer
  80. {
  81. get { return m_Performer; }
  82. set { m_Performer = value; }
  83. }
  84. /// <summary>
  85. /// This command is used to specify the name of a songwriter for a CD-TEXT enhanced disc.
  86. /// </summary>
  87. public string Songwriter
  88. {
  89. get { return m_Songwriter; }
  90. set { m_Songwriter = value; }
  91. }
  92. /// <summary>
  93. /// The title of the entire disc as a whole.
  94. /// </summary>
  95. public string Title
  96. {
  97. get { return m_Title; }
  98. set { m_Title = value; }
  99. }
  100. /// <summary>
  101. /// The array of tracks on the cuesheet.
  102. /// </summary>
  103. public Track[] Tracks
  104. {
  105. get { return m_Tracks; }
  106. set { m_Tracks = value; }
  107. }
  108. #endregion Properties
  109. #region Constructors
  110. /// <summary>
  111. /// Create a cue sheet from scratch.
  112. /// </summary>
  113. public CueSheet()
  114. { }
  115. /// <summary>
  116. /// Parse a cue sheet string.
  117. /// </summary>
  118. /// <param name="cueString">A string containing the cue sheet data.</param>
  119. /// <param name="lineDelims">Line delimeters; set to "(char[])null" for default delimeters.</param>
  120. public CueSheet(string cueString, char[] lineDelims)
  121. {
  122. if (lineDelims == null)
  123. {
  124. lineDelims = new char[] { '\n' };
  125. }
  126. cueLines = RemoveEmptyLines(cueString.Split(lineDelims));
  127. ParseCue(cueLines);
  128. }
  129. /// <summary>
  130. /// Parses a cue sheet file.
  131. /// </summary>
  132. /// <param name="cuefilename">The filename for the cue sheet to open.</param>
  133. public CueSheet(string cuefilename)
  134. {
  135. ReadCueSheet(cuefilename, Encoding.Default);
  136. }
  137. /// <summary>
  138. /// Parses a cue sheet file.
  139. /// </summary>
  140. /// <param name="cuefilename">The filename for the cue sheet to open.</param>
  141. /// <param name="encoding">The encoding used to open the file.</param>
  142. public CueSheet(string cuefilename, Encoding encoding)
  143. {
  144. ReadCueSheet(cuefilename, encoding);
  145. }
  146. /// <summary>
  147. /// Parses a cue sheet file.
  148. /// </summary>
  149. /// <param name="file">The text stream for the cue sheet to read.</param>
  150. public CueSheet(StreamReader file)
  151. {
  152. ReadCueSheet(file);
  153. }
  154. private void ReadCueSheet(StreamReader file)
  155. {
  156. // array of delimiters to split the sentence with
  157. char[] delimiters = new char[] { '\n' };
  158. //read in file
  159. cueLines = RemoveEmptyLines(file.ReadToEnd().Split(delimiters));
  160. ParseCue(cueLines);
  161. }
  162. private void ReadCueSheet(string filename, Encoding encoding)
  163. {
  164. // array of delimiters to split the sentence with
  165. char[] delimiters = new char[] { '\n' };
  166. // read in the full cue file
  167. using (TextReader tr = new StreamReader(filename, encoding))
  168. {
  169. //read in file
  170. cueLines = RemoveEmptyLines(tr.ReadToEnd().Split(delimiters));
  171. }
  172. ParseCue(cueLines);
  173. }
  174. #endregion Constructors
  175. #region Methods
  176. /// <summary>
  177. /// Trims leading and trailing spaces, removes any empty lines, elimating possible trouble.
  178. /// </summary>
  179. /// <param name="file"></param>
  180. private string[] RemoveEmptyLines(string[] file)
  181. {
  182. return file.Select(line => line.Trim()).Where(line => line != string.Empty).ToArray();
  183. }
  184. private void ParseCue(string[] file)
  185. {
  186. //-1 means still global,
  187. //all others are track specific
  188. int trackOn = -1;
  189. AudioFile currentFile = new AudioFile();
  190. foreach (var line in file)
  191. {
  192. var kv = SplitLine(line);
  193. switch (kv.Key)
  194. {
  195. case "CATALOG":
  196. ParseString(kv.Key, kv.Value, trackOn);
  197. break;
  198. case "CDTEXTFILE":
  199. ParseString(kv.Key, kv.Value, trackOn);
  200. break;
  201. case "FILE":
  202. currentFile = ParseFile(kv.Value, trackOn);
  203. break;
  204. case "FLAGS":
  205. ParseFlags(line, trackOn);
  206. break;
  207. case "INDEX":
  208. ParseIndex(kv.Key, kv.Value, trackOn);
  209. break;
  210. case "ISRC":
  211. ParseString(kv.Key, kv.Value, trackOn);
  212. break;
  213. case "PERFORMER":
  214. ParseString(kv.Key, kv.Value, trackOn);
  215. break;
  216. case "POSTGAP":
  217. ParseIndex(kv.Key, kv.Value, trackOn);
  218. break;
  219. case "PREGAP":
  220. ParseIndex(kv.Key, kv.Value, trackOn);
  221. break;
  222. case "REM":
  223. ParseComment(kv.Value, trackOn);
  224. break;
  225. case "SONGWRITER":
  226. ParseString(kv.Key, kv.Value, trackOn);
  227. break;
  228. case "TITLE":
  229. ParseString(kv.Key, kv.Value, trackOn);
  230. break;
  231. case "TRACK":
  232. trackOn++;
  233. ParseTrack(kv.Value, trackOn);
  234. if (currentFile.Filename != "") //if there's a file
  235. {
  236. m_Tracks[trackOn].DataFile = currentFile;
  237. currentFile = new AudioFile();
  238. }
  239. break;
  240. default:
  241. ParseGarbage(line, trackOn);
  242. //save discarded junk and place string[] with track it was found in
  243. break;
  244. }
  245. }
  246. }
  247. private void ParseComment(string line, int trackOn)
  248. {
  249. // "REM" already removed
  250. if (trackOn == -1)
  251. {
  252. if (line.Trim() != "")
  253. {
  254. m_Comments = (string[])ResizeArray(m_Comments, m_Comments.Length + 1);
  255. m_Comments[m_Comments.Length - 1] = line;
  256. }
  257. }
  258. else
  259. {
  260. m_Tracks[trackOn].AddComment(line);
  261. }
  262. }
  263. private AudioFile ParseFile(string line, int trackOn)
  264. {
  265. string fileType = line.Substring(line.LastIndexOf(' '), line.Length - line.LastIndexOf(' ')).Trim();
  266. line = line.Substring(0, line.LastIndexOf(' ')).Trim();
  267. //if quotes around it, remove them.
  268. if (line[0] == '"')
  269. {
  270. line = line.Substring(1, line.LastIndexOf('"') - 1);
  271. }
  272. return new AudioFile(line, fileType);
  273. }
  274. private void ParseFlags(string line, int trackOn)
  275. {
  276. string temp;
  277. if (trackOn != -1)
  278. {
  279. line = line.Trim();
  280. if (line != "")
  281. {
  282. var pos = line.IndexOf(' ');
  283. temp = (pos >= 0 ? line.Substring(0, pos) : line).ToUpper();
  284. switch (temp)
  285. {
  286. case "FLAGS":
  287. m_Tracks[trackOn].AddFlag(temp);
  288. break;
  289. case "DATA":
  290. m_Tracks[trackOn].AddFlag(temp);
  291. break;
  292. case "DCP":
  293. m_Tracks[trackOn].AddFlag(temp);
  294. break;
  295. case "4CH":
  296. m_Tracks[trackOn].AddFlag(temp);
  297. break;
  298. case "PRE":
  299. m_Tracks[trackOn].AddFlag(temp);
  300. break;
  301. case "SCMS":
  302. m_Tracks[trackOn].AddFlag(temp);
  303. break;
  304. default:
  305. break;
  306. }
  307. temp = pos >= 0 ? line.Substring(pos, line.Length - pos) : line;
  308. //if the flag hasn't already been processed
  309. if (temp.ToUpper().Trim() != line.ToUpper().Trim())
  310. {
  311. ParseFlags(temp, trackOn);
  312. }
  313. }
  314. }
  315. }
  316. private void ParseGarbage(string line, int trackOn)
  317. {
  318. if (trackOn == -1)
  319. {
  320. if (line.Trim() != "")
  321. {
  322. m_Garbage = (string[])ResizeArray(m_Garbage, m_Garbage.Length + 1);
  323. m_Garbage[m_Garbage.Length - 1] = line;
  324. }
  325. }
  326. else
  327. {
  328. m_Tracks[trackOn].AddGarbage(line);
  329. }
  330. }
  331. private void ParseIndex(string indexType, string value, int trackOn)
  332. {
  333. string tempString;
  334. int number = 0;
  335. int minutes;
  336. int seconds;
  337. int frames;
  338. tempString = value;
  339. if (indexType == "INDEX")
  340. {
  341. //read the index number
  342. number = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(' ')));
  343. tempString = tempString.Substring(tempString.IndexOf(' '), tempString.Length - tempString.IndexOf(' ')).Trim();
  344. }
  345. //extract the minutes, seconds, and frames
  346. minutes = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(':')));
  347. seconds = Convert.ToInt32(tempString.Substring(tempString.IndexOf(':') + 1, tempString.LastIndexOf(':') - tempString.IndexOf(':') - 1));
  348. frames = Convert.ToInt32(tempString.Substring(tempString.LastIndexOf(':') + 1, tempString.Length - tempString.LastIndexOf(':') - 1));
  349. if (indexType == "INDEX")
  350. {
  351. m_Tracks[trackOn].AddIndex(number, minutes, seconds, frames);
  352. }
  353. else if (indexType == "PREGAP")
  354. {
  355. m_Tracks[trackOn].PreGap = new Index(0, minutes, seconds, frames);
  356. }
  357. else if (indexType == "POSTGAP")
  358. {
  359. m_Tracks[trackOn].PostGap = new Index(0, minutes, seconds, frames);
  360. }
  361. }
  362. private void ParseString(string category, string line, int trackOn)
  363. {
  364. //get rid of the quotes
  365. if (line[0] == '"')
  366. {
  367. line = line.Substring(1, line.LastIndexOf('"') - 1);
  368. }
  369. switch (category)
  370. {
  371. case "CATALOG":
  372. if (trackOn == -1)
  373. {
  374. this.m_Catalog = line;
  375. }
  376. break;
  377. case "CDTEXTFILE":
  378. if (trackOn == -1)
  379. {
  380. this.m_CDTextFile = line;
  381. }
  382. break;
  383. case "ISRC":
  384. if (trackOn != -1)
  385. {
  386. m_Tracks[trackOn].ISRC = line;
  387. }
  388. break;
  389. case "PERFORMER":
  390. if (trackOn == -1)
  391. {
  392. this.m_Performer = line;
  393. }
  394. else
  395. {
  396. m_Tracks[trackOn].Performer = line;
  397. }
  398. break;
  399. case "SONGWRITER":
  400. if (trackOn == -1)
  401. {
  402. this.m_Songwriter = line;
  403. }
  404. else
  405. {
  406. m_Tracks[trackOn].Songwriter = line;
  407. }
  408. break;
  409. case "TITLE":
  410. if (trackOn == -1)
  411. {
  412. this.m_Title = line;
  413. }
  414. else
  415. {
  416. m_Tracks[trackOn].Title = line;
  417. }
  418. break;
  419. default:
  420. break;
  421. }
  422. }
  423. /// <summary>
  424. /// Parses the TRACK command.
  425. /// </summary>
  426. /// <param name="value">The line in the cue file that contains the TRACK command with TRACK part removed.</param>
  427. /// <param name="trackOn">The track currently processing.</param>
  428. private void ParseTrack(string value, int trackOn)
  429. {
  430. string tempString;
  431. int trackNumber;
  432. tempString = value;
  433. try
  434. {
  435. trackNumber = Convert.ToInt32(tempString.Substring(0, tempString.IndexOf(' ')));
  436. }
  437. catch (Exception)
  438. { throw; }
  439. //find the data type.
  440. tempString = tempString.Substring(tempString.IndexOf(' '), tempString.Length - tempString.IndexOf(' ')).Trim();
  441. AddTrack(trackNumber, tempString);
  442. }
  443. /// <summary>
  444. /// Reallocates an array with a new size, and copies the contents
  445. /// of the old array to the new array.
  446. /// </summary>
  447. /// <param name="oldArray">The old array, to be reallocated.</param>
  448. /// <param name="newSize">The new array size.</param>
  449. /// <returns>A new array with the same contents.</returns>
  450. /// <remarks >Useage: int[] a = {1,2,3}; a = (int[])ResizeArray(a,5);</remarks>
  451. public static System.Array ResizeArray(System.Array oldArray, int newSize)
  452. {
  453. int oldSize = oldArray.Length;
  454. System.Type elementType = oldArray.GetType().GetElementType();
  455. System.Array newArray = System.Array.CreateInstance(elementType, newSize);
  456. int preserveLength = System.Math.Min(oldSize, newSize);
  457. if (preserveLength > 0)
  458. System.Array.Copy(oldArray, newArray, preserveLength);
  459. return newArray;
  460. }
  461. /// <summary>
  462. /// Add a track to the current cuesheet.
  463. /// </summary>
  464. /// <param name="tracknumber">The number of the said track.</param>
  465. /// <param name="datatype">The datatype of the track.</param>
  466. private void AddTrack(int tracknumber, string datatype)
  467. {
  468. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1);
  469. m_Tracks[m_Tracks.Length - 1] = new Track(tracknumber, datatype);
  470. }
  471. /// <summary>
  472. /// Add a track to the current cuesheet
  473. /// </summary>
  474. /// <param name="title">The title of the track.</param>
  475. /// <param name="performer">The performer of this track.</param>
  476. public void AddTrack(string title, string performer)
  477. {
  478. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1);
  479. m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, "");
  480. m_Tracks[m_Tracks.Length - 1].Performer = performer;
  481. m_Tracks[m_Tracks.Length - 1].Title = title;
  482. }
  483. public void AddTrack(string title, string performer, string filename, FileType fType)
  484. {
  485. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1);
  486. m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, "");
  487. m_Tracks[m_Tracks.Length - 1].Performer = performer;
  488. m_Tracks[m_Tracks.Length - 1].Title = title;
  489. m_Tracks[m_Tracks.Length - 1].DataFile = new AudioFile(filename, fType);
  490. }
  491. /// <summary>
  492. /// Add a track to the current cuesheet
  493. /// </summary>
  494. /// <param name="title">The title of the track.</param>
  495. /// <param name="performer">The performer of this track.</param>
  496. /// <param name="datatype">The datatype for the track (typically DataType.Audio)</param>
  497. public void AddTrack(string title, string performer, DataType datatype)
  498. {
  499. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1);
  500. m_Tracks[m_Tracks.Length - 1] = new Track(m_Tracks.Length, datatype);
  501. m_Tracks[m_Tracks.Length - 1].Performer = performer;
  502. m_Tracks[m_Tracks.Length - 1].Title = title;
  503. }
  504. /// <summary>
  505. /// Add a track to the current cuesheet
  506. /// </summary>
  507. /// <param name="track">Track object to add to the cuesheet.</param>
  508. public void AddTrack(Track track)
  509. {
  510. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length + 1);
  511. m_Tracks[m_Tracks.Length - 1] = track;
  512. }
  513. /// <summary>
  514. /// Remove a track from the cuesheet.
  515. /// </summary>
  516. /// <param name="trackIndex">The index of the track you wish to remove.</param>
  517. public void RemoveTrack(int trackIndex)
  518. {
  519. for (int i = trackIndex; i < m_Tracks.Length - 1; i++)
  520. {
  521. m_Tracks[i] = m_Tracks[i + 1];
  522. }
  523. m_Tracks = (Track[])ResizeArray(m_Tracks, m_Tracks.Length - 1);
  524. }
  525. /// <summary>
  526. /// Add index information to an existing track.
  527. /// </summary>
  528. /// <param name="trackIndex">The array index number of track to be modified</param>
  529. /// <param name="indexNum">The index number of the new index</param>
  530. /// <param name="minutes">The minute value of the new index</param>
  531. /// <param name="seconds">The seconds value of the new index</param>
  532. /// <param name="frames">The frames value of the new index</param>
  533. public void AddIndex(int trackIndex, int indexNum, int minutes, int seconds, int frames)
  534. {
  535. m_Tracks[trackIndex].AddIndex(indexNum, minutes, seconds, frames);
  536. }
  537. /// <summary>
  538. /// Remove an index from a track.
  539. /// </summary>
  540. /// <param name="trackIndex">The array-index of the track.</param>
  541. /// <param name="indexIndex">The index of the Index you wish to remove.</param>
  542. public void RemoveIndex(int trackIndex, int indexIndex)
  543. {
  544. //Note it is the index of the Index you want to delete,
  545. //which may or may not correspond to the number of the index.
  546. m_Tracks[trackIndex].RemoveIndex(indexIndex);
  547. }
  548. /// <summary>
  549. /// Save the cue sheet file to specified location.
  550. /// </summary>
  551. /// <param name="filename">Filename of destination cue sheet file.</param>
  552. public void SaveCue(string filename)
  553. {
  554. SaveCue(filename, Encoding.Default);
  555. }
  556. /// <summary>
  557. /// Save the cue sheet file to specified location.
  558. /// </summary>
  559. /// <param name="filename">Filename of destination cue sheet file.</param>
  560. /// <param name="encoding">The encoding used to save the file.</param>
  561. public void SaveCue(string filename, Encoding encoding)
  562. {
  563. using (TextWriter tw = new StreamWriter(filename, false, encoding))
  564. tw.WriteLine(this.ToString());
  565. }
  566. /// <summary>
  567. /// Method to output the cuesheet into a single formatted string.
  568. /// </summary>
  569. /// <returns>The entire cuesheet formatted to specification.</returns>
  570. public override string ToString()
  571. {
  572. StringBuilder output = new StringBuilder();
  573. foreach (string comment in m_Comments)
  574. {
  575. output.Append("REM " + comment + Environment.NewLine);
  576. }
  577. if (m_Catalog.Trim() != "")
  578. {
  579. output.Append("CATALOG " + m_Catalog + Environment.NewLine);
  580. }
  581. if (m_Performer.Trim() != "")
  582. {
  583. output.Append("PERFORMER \"" + m_Performer + "\"" + Environment.NewLine);
  584. }
  585. if (m_Songwriter.Trim() != "")
  586. {
  587. output.Append("SONGWRITER \"" + m_Songwriter + "\"" + Environment.NewLine);
  588. }
  589. if (m_Title.Trim() != "")
  590. {
  591. output.Append("TITLE \"" + m_Title + "\"" + Environment.NewLine);
  592. }
  593. if (m_CDTextFile.Trim() != "")
  594. {
  595. output.Append("CDTEXTFILE \"" + m_CDTextFile.Trim() + "\"" + Environment.NewLine);
  596. }
  597. for (int i = 0; i < m_Tracks.Length; i++)
  598. {
  599. output.Append(m_Tracks[i].ToString());
  600. if (i != m_Tracks.Length - 1)
  601. {
  602. //add line break for each track except last
  603. output.Append(Environment.NewLine);
  604. }
  605. }
  606. return output.ToString();
  607. }
  608. #endregion Methods
  609. // split input line at the first space into two parts: UPPERCASE keyword and content,
  610. // when there are no spaces entire line is a keyword
  611. private static KeyValuePair<string, string> SplitLine(string line)
  612. {
  613. var pos = line.IndexOf(' ');
  614. // lines have been trimmed, so no leading/trailing spaces are expected here
  615. if (pos > 0)
  616. return new KeyValuePair<string, string>(line.Substring(0, pos).ToUpper(), line.Substring(pos + 1));
  617. else
  618. return new KeyValuePair<string, string>(line.ToUpper(), string.Empty);
  619. }
  620. }
  621. /// <summary>
  622. /// DCP - Digital copy permitted
  623. /// 4CH - Four channel audio
  624. /// PRE - Pre-emphasis enabled (audio tracks only)
  625. /// SCMS - Serial copy management system (not supported by all recorders)
  626. /// 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.
  627. /// </summary>
  628. public enum Flags
  629. {
  630. DCP, CH4, PRE, SCMS, DATA, NONE
  631. }
  632. /// <summary>
  633. /// BINARY - Intel binary file (least significant byte first)
  634. /// MOTOROLA - Motorola binary file (most significant byte first)
  635. /// AIFF - Audio AIFF file
  636. /// WAVE - Audio WAVE file
  637. /// MP3 - Audio MP3 file
  638. /// </summary>
  639. public enum FileType
  640. {
  641. BINARY, MOTOROLA, AIFF, WAVE, MP3
  642. }
  643. /// <summary>
  644. /// <list>
  645. /// <item>AUDIO - Audio/Music (2352)</item>
  646. /// <item>CDG - Karaoke CD+G (2448)</item>
  647. /// <item>MODE1/2048 - CDROM Mode1 Data (cooked)</item>
  648. /// <item>MODE1/2352 - CDROM Mode1 Data (raw)</item>
  649. /// <item>MODE2/2336 - CDROM-XA Mode2 Data</item>
  650. /// <item>MODE2/2352 - CDROM-XA Mode2 Data</item>
  651. /// <item>CDI/2336 - CDI Mode2 Data</item>
  652. /// <item>CDI/2352 - CDI Mode2 Data</item>
  653. /// </list>
  654. /// </summary>
  655. public enum DataType
  656. {
  657. AUDIO, CDG, MODE1_2048, MODE1_2352, MODE2_2336, MODE2_2352, CDI_2336, CDI_2352
  658. }
  659. /// <summary>
  660. /// This command is used to specify indexes (or subindexes) within a track.
  661. /// Syntax:
  662. /// INDEX [number] [mm:ss:ff]
  663. /// </summary>
  664. public struct Index
  665. {
  666. //0-99
  667. private int m_number;
  668. private int m_minutes;
  669. private int m_seconds;
  670. private int m_frames;
  671. /// <summary>
  672. /// Index number (0-99)
  673. /// </summary>
  674. public int Number
  675. {
  676. get { return m_number; }
  677. set
  678. {
  679. if (value > 99)
  680. {
  681. m_number = 99;
  682. }
  683. else if (value < 0)
  684. {
  685. m_number = 0;
  686. }
  687. else
  688. {
  689. m_number = value;
  690. }
  691. }
  692. }
  693. /// <summary>
  694. /// Possible values: 0-99
  695. /// </summary>
  696. public int Minutes
  697. {
  698. get { return m_minutes; }
  699. set
  700. {
  701. if (value > 99)
  702. {
  703. m_minutes = 99;
  704. }
  705. else if (value < 0)
  706. {
  707. m_minutes = 0;
  708. }
  709. else
  710. {
  711. m_minutes = value;
  712. }
  713. }
  714. }
  715. /// <summary>
  716. /// Possible values: 0-59
  717. /// There are 60 seconds/minute
  718. /// </summary>
  719. public int Seconds
  720. {
  721. get { return m_seconds; }
  722. set
  723. {
  724. if (value >= 60)
  725. {
  726. m_seconds = 59;
  727. }
  728. else if (value < 0)
  729. {
  730. m_seconds = 0;
  731. }
  732. else
  733. {
  734. m_seconds = value;
  735. }
  736. }
  737. }
  738. /// <summary>
  739. /// Possible values: 0-74
  740. /// There are 75 frames/second
  741. /// </summary>
  742. public int Frames
  743. {
  744. get { return m_frames; }
  745. set
  746. {
  747. if (value >= 75)
  748. {
  749. m_frames = 74;
  750. }
  751. else if (value < 0)
  752. {
  753. m_frames = 0;
  754. }
  755. else
  756. {
  757. m_frames = value;
  758. }
  759. }
  760. }
  761. /// <summary>
  762. /// The Index of a track.
  763. /// </summary>
  764. /// <param name="number">Index number 0-99</param>
  765. /// <param name="minutes">Minutes (0-99)</param>
  766. /// <param name="seconds">Seconds (0-59)</param>
  767. /// <param name="frames">Frames (0-74)</param>
  768. public Index(int number, int minutes, int seconds, int frames)
  769. {
  770. m_number = number;
  771. m_minutes = minutes;
  772. m_seconds = seconds;
  773. m_frames = frames;
  774. }
  775. }
  776. /// <summary>
  777. /// This command is used to specify a data/audio file that will be written to the recorder.
  778. /// </summary>
  779. public struct AudioFile
  780. {
  781. private string m_Filename;
  782. private FileType m_Filetype;
  783. public string Filename
  784. {
  785. get { return m_Filename; }
  786. set { m_Filename = value; }
  787. }
  788. /// <summary>
  789. /// BINARY - Intel binary file (least significant byte first)
  790. /// MOTOROLA - Motorola binary file (most significant byte first)
  791. /// AIFF - Audio AIFF file
  792. /// WAVE - Audio WAVE file
  793. /// MP3 - Audio MP3 file
  794. /// </summary>
  795. public FileType Filetype
  796. {
  797. get { return m_Filetype; }
  798. set { m_Filetype = value; }
  799. }
  800. public AudioFile(string filename, string filetype)
  801. {
  802. m_Filename = filename;
  803. switch (filetype.Trim().ToUpper())
  804. {
  805. case "BINARY":
  806. m_Filetype = FileType.BINARY;
  807. break;
  808. case "MOTOROLA":
  809. m_Filetype = FileType.MOTOROLA;
  810. break;
  811. case "AIFF":
  812. m_Filetype = FileType.AIFF;
  813. break;
  814. case "WAVE":
  815. m_Filetype = FileType.WAVE;
  816. break;
  817. case "MP3":
  818. m_Filetype = FileType.MP3;
  819. break;
  820. default:
  821. m_Filetype = FileType.BINARY;
  822. break;
  823. }
  824. }
  825. public AudioFile(string filename, FileType filetype)
  826. {
  827. m_Filename = filename;
  828. m_Filetype = filetype;
  829. }
  830. }
  831. /// <summary>
  832. /// Track that contains either data or audio. It can contain Indices and comment information.
  833. /// </summary>
  834. public class Track
  835. {
  836. #region Private Variables
  837. private string[] m_Comments;
  838. // strings that don't belong or were mistyped in the global part of the cue
  839. private AudioFile m_DataFile;
  840. private string[] m_Garbage;
  841. private Index[] m_Indices;
  842. private string m_ISRC;
  843. private string m_Performer;
  844. private Index m_PostGap;
  845. private Index m_PreGap;
  846. private string m_Songwriter;
  847. private string m_Title;
  848. private Flags[] m_TrackFlags;
  849. private DataType m_TrackDataType;
  850. private int m_TrackNumber;
  851. #endregion Private Variables
  852. #region Properties
  853. /// <summary>
  854. /// Returns/Sets Index in this track.
  855. /// </summary>
  856. /// <param name="indexnumber">Index in the track.</param>
  857. /// <returns>Index at indexnumber.</returns>
  858. public Index this[int indexnumber]
  859. {
  860. get
  861. {
  862. return m_Indices[indexnumber];
  863. }
  864. set
  865. {
  866. m_Indices[indexnumber] = value;
  867. }
  868. }
  869. public string[] Comments
  870. {
  871. get { return m_Comments; }
  872. set { m_Comments = value; }
  873. }
  874. public AudioFile DataFile
  875. {
  876. get { return m_DataFile; }
  877. set { m_DataFile = value; }
  878. }
  879. /// <summary>
  880. /// Lines in the cue file that don't belong or have other general syntax errors.
  881. /// </summary>
  882. public string[] Garbage
  883. {
  884. get { return m_Garbage; }
  885. set { m_Garbage = value; }
  886. }
  887. public Index[] Indices
  888. {
  889. get { return m_Indices; }
  890. set { m_Indices = value; }
  891. }
  892. public string ISRC
  893. {
  894. get { return m_ISRC; }
  895. set { m_ISRC = value; }
  896. }
  897. public string Performer
  898. {
  899. get { return m_Performer; }
  900. set { m_Performer = value; }
  901. }
  902. public Index PostGap
  903. {
  904. get { return m_PostGap; }
  905. set { m_PostGap = value; }
  906. }
  907. public Index PreGap
  908. {
  909. get { return m_PreGap; }
  910. set { m_PreGap = value; }
  911. }
  912. public string Songwriter
  913. {
  914. get { return m_Songwriter; }
  915. set { m_Songwriter = value; }
  916. }
  917. /// <summary>
  918. /// If the TITLE command appears before any TRACK commands, then the string will be encoded as the title of the entire disc.
  919. /// </summary>
  920. public string Title
  921. {
  922. get { return m_Title; }
  923. set { m_Title = value; }
  924. }
  925. public DataType TrackDataType
  926. {
  927. get { return m_TrackDataType; }
  928. set { m_TrackDataType = value; }
  929. }
  930. public Flags[] TrackFlags
  931. {
  932. get { return m_TrackFlags; }
  933. set { m_TrackFlags = value; }
  934. }
  935. public int TrackNumber
  936. {
  937. get { return m_TrackNumber; }
  938. set { m_TrackNumber = value; }
  939. }
  940. #endregion Properties
  941. #region Contructors
  942. public Track(int tracknumber, string datatype)
  943. {
  944. m_TrackNumber = tracknumber;
  945. switch (datatype.Trim().ToUpper())
  946. {
  947. case "AUDIO":
  948. m_TrackDataType = DataType.AUDIO;
  949. break;
  950. case "CDG":
  951. m_TrackDataType = DataType.CDG;
  952. break;
  953. case "MODE1/2048":
  954. m_TrackDataType = DataType.MODE1_2048;
  955. break;
  956. case "MODE1/2352":
  957. m_TrackDataType = DataType.MODE1_2352;
  958. break;
  959. case "MODE2/2336":
  960. m_TrackDataType = DataType.MODE2_2336;
  961. break;
  962. case "MODE2/2352":
  963. m_TrackDataType = DataType.MODE2_2352;
  964. break;
  965. case "CDI/2336":
  966. m_TrackDataType = DataType.CDI_2336;
  967. break;
  968. case "CDI/2352":
  969. m_TrackDataType = DataType.CDI_2352;
  970. break;
  971. default:
  972. m_TrackDataType = DataType.AUDIO;
  973. break;
  974. }
  975. m_TrackFlags = new Flags[0];
  976. m_Songwriter = "";
  977. m_Title = "";
  978. m_ISRC = "";
  979. m_Performer = "";
  980. m_Indices = new Index[0];
  981. m_Garbage = new string[0];
  982. m_Comments = new string[0];
  983. m_PreGap = new Index(-1, 0, 0, 0);
  984. m_PostGap = new Index(-1, 0, 0, 0);
  985. m_DataFile = new AudioFile();
  986. }
  987. public Track(int tracknumber, DataType datatype)
  988. {
  989. m_TrackNumber = tracknumber;
  990. m_TrackDataType = datatype;
  991. m_TrackFlags = new Flags[0];
  992. m_Songwriter = "";
  993. m_Title = "";
  994. m_ISRC = "";
  995. m_Performer = "";
  996. m_Indices = new Index[0];
  997. m_Garbage = new string[0];
  998. m_Comments = new string[0];
  999. m_PreGap = new Index(-1, 0, 0, 0);
  1000. m_PostGap = new Index(-1, 0, 0, 0);
  1001. m_DataFile = new AudioFile();
  1002. }
  1003. #endregion Contructors
  1004. #region Methods
  1005. public void AddFlag(Flags flag)
  1006. {
  1007. //if it's not a none tag
  1008. //and if the tags hasn't already been added
  1009. if (flag != Flags.NONE && NewFlag(flag) == true)
  1010. {
  1011. m_TrackFlags = (Flags[])CueSheet.ResizeArray(m_TrackFlags, m_TrackFlags.Length + 1);
  1012. m_TrackFlags[m_TrackFlags.Length - 1] = flag;
  1013. }
  1014. }
  1015. public void AddFlag(string flag)
  1016. {
  1017. switch (flag.Trim().ToUpper())
  1018. {
  1019. case "DATA":
  1020. AddFlag(Flags.DATA);
  1021. break;
  1022. case "DCP":
  1023. AddFlag(Flags.DCP);
  1024. break;
  1025. case "4CH":
  1026. AddFlag(Flags.CH4);
  1027. break;
  1028. case "PRE":
  1029. AddFlag(Flags.PRE);
  1030. break;
  1031. case "SCMS":
  1032. AddFlag(Flags.SCMS);
  1033. break;
  1034. default:
  1035. return;
  1036. }
  1037. }
  1038. public void AddGarbage(string garbage)
  1039. {
  1040. if (garbage.Trim() != "")
  1041. {
  1042. m_Garbage = (string[])CueSheet.ResizeArray(m_Garbage, m_Garbage.Length + 1);
  1043. m_Garbage[m_Garbage.Length - 1] = garbage;
  1044. }
  1045. }
  1046. public void AddComment(string comment)
  1047. {
  1048. if (comment.Trim() != "")
  1049. {
  1050. m_Comments = (string[])CueSheet.ResizeArray(m_Comments, m_Comments.Length + 1);
  1051. m_Comments[m_Comments.Length - 1] = comment;
  1052. }
  1053. }
  1054. public void AddIndex(int number, int minutes, int seconds, int frames)
  1055. {
  1056. m_Indices = (Index[])CueSheet.ResizeArray(m_Indices, m_Indices.Length + 1);
  1057. m_Indices[m_Indices.Length - 1] = new Index(number, minutes, seconds, frames);
  1058. }
  1059. public void RemoveIndex(int indexIndex)
  1060. {
  1061. for (int i = indexIndex; i < m_Indices.Length - 1; i++)
  1062. {
  1063. m_Indices[i] = m_Indices[i + 1];
  1064. }
  1065. m_Indices = (Index[])CueSheet.ResizeArray(m_Indices, m_Indices.Length - 1);
  1066. }
  1067. public TimeSpan Offset
  1068. {
  1069. get
  1070. {
  1071. if (m_Indices.Length == 0)
  1072. return TimeSpan.Zero;
  1073. var index = Indices.Length > 1 ? Indices[1] : Indices[0];
  1074. return new TimeSpan(0, 0, index.Minutes, index.Seconds, index.Frames * 1000 / 75);
  1075. }
  1076. }
  1077. /// <summary>
  1078. /// Checks if the flag is indeed new in this track.
  1079. /// </summary>
  1080. /// <param name="new_flag">The new flag to be added to the track.</param>
  1081. /// <returns>True if this flag doesn't already exist.</returns>
  1082. private bool NewFlag(Flags new_flag)
  1083. {
  1084. foreach (Flags flag in m_TrackFlags)
  1085. {
  1086. if (flag == new_flag)
  1087. {
  1088. return false;
  1089. }
  1090. }
  1091. return true;
  1092. }
  1093. /// <summary>
  1094. /// CueSheet string representation for debugging purposes
  1095. /// </summary>
  1096. /// <returns></returns>
  1097. public override string ToString()
  1098. {
  1099. StringBuilder output = new StringBuilder();
  1100. //write file
  1101. if (m_DataFile.Filename != null && m_DataFile.Filename.Trim() != "")
  1102. {
  1103. output.Append("FILE \"" + m_DataFile.Filename.Trim() + "\" " + m_DataFile.Filetype.ToString() + Environment.NewLine);
  1104. }
  1105. output.Append(" TRACK " + m_TrackNumber.ToString().PadLeft(2, '0') + " " + m_TrackDataType.ToString().Replace('_', '/'));
  1106. //write comments
  1107. foreach (string comment in m_Comments)
  1108. {
  1109. output.Append(Environment.NewLine + " REM " + comment);
  1110. }
  1111. if (m_Performer.Trim() != "")
  1112. {
  1113. output.Append(Environment.NewLine + " PERFORMER \"" + m_Performer + "\"");
  1114. }
  1115. if (m_Songwriter.Trim() != "")
  1116. {
  1117. output.Append(Environment.NewLine + " SONGWRITER \"" + m_Songwriter + "\"");
  1118. }
  1119. if (m_Title.Trim() != "")
  1120. {
  1121. output.Append(Environment.NewLine + " TITLE \"" + m_Title + "\"");
  1122. }
  1123. //write flags
  1124. if (m_TrackFlags.Length > 0)
  1125. {
  1126. output.Append(Environment.NewLine + " FLAGS");
  1127. }
  1128. foreach (Flags flag in m_TrackFlags)
  1129. {
  1130. output.Append(" " + flag.ToString().Replace("CH4", "4CH"));
  1131. }
  1132. //write isrc
  1133. if (m_ISRC.Trim() != "")
  1134. {
  1135. output.Append(Environment.NewLine + " ISRC " + m_ISRC.Trim());
  1136. }
  1137. //write pregap
  1138. if (m_PreGap.Number != -1)
  1139. {
  1140. 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'));
  1141. }
  1142. //write Indices
  1143. for (int j = 0; j < m_Indices.Length; j++)
  1144. {
  1145. 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'));
  1146. }
  1147. //write postgap
  1148. if (m_PostGap.Number != -1)
  1149. {
  1150. 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'));
  1151. }
  1152. return output.ToString();
  1153. }
  1154. #endregion Methods
  1155. }
  1156. }