LyricsReader.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
  2. import {VoiceEntry} from "../../VoiceData/VoiceEntry";
  3. import {IXmlElement} from "../../../Common/FileIO/Xml";
  4. import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
  5. import {ITextTranslation} from "../../Interfaces/ITextTranslation";
  6. import {MusicSheet} from "../../MusicSheet";
  7. export class LyricsReader {
  8. private openLyricWords: { [_: number]: LyricWord; } = {};
  9. private currentLyricWord: LyricWord;
  10. private musicSheet: MusicSheet;
  11. constructor(musicSheet: MusicSheet) {
  12. this.musicSheet = musicSheet;
  13. }
  14. /**
  15. * This method adds a single LyricEntry to a VoiceEntry
  16. * @param {IXmlElement[]} lyricNodeList
  17. * @param {VoiceEntry} currentVoiceEntry
  18. */
  19. public addLyricEntry(lyricNodeList: IXmlElement[], currentVoiceEntry: VoiceEntry): void {
  20. if (lyricNodeList) {
  21. const lyricNodeListArr: IXmlElement[] = lyricNodeList;
  22. for (let idx: number = 0, len: number = lyricNodeListArr.length; idx < len; ++idx) {
  23. const lyricNode: IXmlElement = lyricNodeListArr[idx];
  24. try {
  25. let syllabic: string = "single"; // Single as default
  26. if (lyricNode.element("text")) {
  27. let textNode: IXmlElement = lyricNode.element("text");
  28. if (lyricNode.element("syllabic")) {
  29. syllabic = lyricNode.element("syllabic").value;
  30. }
  31. if (textNode) {
  32. let text: string = "";
  33. const textAndElisionNodes: IXmlElement[] = lyricNode.elements();
  34. for (const node of textAndElisionNodes) {
  35. if (node.name === "text" || node.name === "elision") {
  36. text += node.value;
  37. }
  38. }
  39. text = text.replace(" ", " "); // filter multiple spaces from concatenating e.g. text "a " with elision " "
  40. // <elision> separates Multiple syllabels on a single LyricNote
  41. // "-" text indicating separated syllabel should be ignored
  42. // we calculate the Dash element much later
  43. if (lyricNode.element("elision") !== undefined && text === "-") {
  44. const lyricNodeChildren: IXmlElement[] = lyricNode.elements();
  45. let elisionIndex: number = 0;
  46. for (let i: number = 0; i < lyricNodeChildren.length; i++) {
  47. const child: IXmlElement = lyricNodeChildren[i];
  48. if (child.name === "elision") {
  49. elisionIndex = i;
  50. break;
  51. }
  52. }
  53. let nextText: IXmlElement = undefined;
  54. let nextSyllabic: IXmlElement = undefined;
  55. // read the next nodes
  56. if (elisionIndex > 0) {
  57. for (let i: number = elisionIndex; i < lyricNodeChildren.length; i++) {
  58. const child: IXmlElement = lyricNodeChildren[i];
  59. if (child.name === "text") {
  60. nextText = child;
  61. }
  62. if (child.name === "syllabic") {
  63. nextSyllabic = child;
  64. }
  65. }
  66. }
  67. if (nextText !== undefined && nextSyllabic) {
  68. textNode = nextText;
  69. syllabic = "middle";
  70. }
  71. }
  72. let currentLyricVerseNumber: number = 1;
  73. let errorNumberParse1: boolean = false;
  74. if (lyricNode.attributes() !== undefined && lyricNode.attribute("number")) {
  75. try {
  76. currentLyricVerseNumber = parseInt(lyricNode.attribute("number").value, 10); // usually doesn't throw error, but returns NaN
  77. } catch (err) {
  78. errorNumberParse1 = true;
  79. }
  80. errorNumberParse1 = errorNumberParse1 || isNaN(currentLyricVerseNumber);
  81. if (errorNumberParse1) {
  82. try { // Sibelius format: "part1verse1"
  83. const result: string[] = lyricNode.attribute("number").value.toLowerCase().split("verse");
  84. if (result.length > 1) {
  85. currentLyricVerseNumber = parseInt(result[1], 10);
  86. }
  87. } catch (err) {
  88. const errorMsg: string =
  89. ITextTranslation.translateText("ReaderErrorMessages/LyricVerseNumberError", "Invalid lyric verse number");
  90. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  91. continue;
  92. }
  93. }
  94. }
  95. let lyricsEntry: LyricsEntry = undefined;
  96. if (syllabic === "single" || syllabic === "end") {
  97. if (this.openLyricWords[currentLyricVerseNumber]) { // word end given or some word still open
  98. this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
  99. const syllableNumber: number = this.currentLyricWord.Syllables.length;
  100. lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry, syllableNumber);
  101. this.currentLyricWord.Syllables.push(lyricsEntry);
  102. delete this.openLyricWords[currentLyricVerseNumber];
  103. this.currentLyricWord = undefined;
  104. } else { // single syllable given or end given while no word has been started
  105. lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
  106. }
  107. lyricsEntry.extend = lyricNode.element("extend") !== undefined;
  108. } else if (syllabic === "begin") { // first finishing, if a word already is open (can only happen, when wrongly given)
  109. if (this.openLyricWords[currentLyricVerseNumber]) {
  110. delete this.openLyricWords[currentLyricVerseNumber];
  111. this.currentLyricWord = undefined;
  112. }
  113. this.currentLyricWord = new LyricWord();
  114. this.openLyricWords[currentLyricVerseNumber] = this.currentLyricWord;
  115. lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry, 0);
  116. this.currentLyricWord.Syllables.push(lyricsEntry);
  117. } else if (syllabic === "middle") {
  118. if (this.openLyricWords[currentLyricVerseNumber]) {
  119. this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
  120. const syllableNumber: number = this.currentLyricWord.Syllables.length;
  121. lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry, syllableNumber);
  122. this.currentLyricWord.Syllables.push(lyricsEntry);
  123. } else {
  124. // in case the wrong syllabel information is given, create a single Entry and add it to currentVoiceEntry
  125. lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
  126. }
  127. }
  128. // add each LyricEntry to currentVoiceEntry
  129. if (lyricsEntry) {
  130. // only add the lyric entry if not another entry has already been given:
  131. if (!currentVoiceEntry.LyricsEntries[currentLyricVerseNumber]) {
  132. currentVoiceEntry.LyricsEntries.setValue(currentLyricVerseNumber, lyricsEntry);
  133. if (currentVoiceEntry.ParentSourceStaffEntry?.VerticalContainerParent?.ParentMeasure) {
  134. currentVoiceEntry.ParentSourceStaffEntry.VerticalContainerParent.ParentMeasure.hasLyrics = true;
  135. }
  136. }
  137. // save in currentInstrument the verseNumber (only once)
  138. if (!currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers[currentLyricVerseNumber]) {
  139. currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.push(currentLyricVerseNumber);
  140. }
  141. }
  142. }
  143. }
  144. } catch (err) {
  145. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/LyricError", "Error while reading lyric entry.");
  146. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  147. continue;
  148. }
  149. }
  150. // Squash to unique numbers
  151. currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers =
  152. currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.filter((lvn, index, self) => self.indexOf(lvn) === index);
  153. }
  154. }
  155. }