RepetitionInstructionReader.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import {MusicSheet} from "../../MusicSheet";
  2. import {IXmlElement} from "../../../Common/FileIO/Xml";
  3. import {SourceMeasure} from "../../VoiceData/SourceMeasure";
  4. import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
  5. import {RepetitionInstructionComparer} from "../../VoiceData/Instructions/RepetitionInstruction";
  6. import {StringUtil} from "../../../Common/Strings/StringUtil";
  7. export class RepetitionInstructionReader {
  8. /**
  9. * A global list of all repetition instructions in the musicsheet.
  10. */
  11. public repetitionInstructions: RepetitionInstruction[];
  12. public xmlMeasureList: IXmlElement[][];
  13. private musicSheet: MusicSheet;
  14. private currentMeasureIndex: number;
  15. public set MusicSheet(value: MusicSheet) {
  16. this.musicSheet = value;
  17. this.xmlMeasureList = new Array(this.musicSheet.Instruments.length);
  18. this.repetitionInstructions = [];
  19. }
  20. /**
  21. * is called when starting reading an xml measure
  22. * @param measure
  23. * @param currentMeasureIndex
  24. */
  25. public prepareReadingMeasure(measure: SourceMeasure, currentMeasureIndex: number): void {
  26. this.currentMeasureIndex = currentMeasureIndex;
  27. }
  28. public handleLineRepetitionInstructions(barlineNode: IXmlElement, pieceEndingDetected: boolean): void {
  29. pieceEndingDetected = false;
  30. if (barlineNode.elements().length > 0) {
  31. let location: string = "";
  32. let hasRepeat: boolean = false;
  33. let direction: string = "";
  34. let type: string = "";
  35. let style: string = "";
  36. const endingIndices: number[] = [];
  37. // read barline style
  38. const styleNode: IXmlElement = barlineNode.element("bar-style");
  39. // if location is ommited in Xml, right is implied (from documentation)
  40. if (styleNode !== undefined) {
  41. style = styleNode.value;
  42. }
  43. if (barlineNode.attributes().length > 0 && barlineNode.attribute("location") !== undefined) {
  44. location = barlineNode.attribute("location").value;
  45. } else {
  46. location = "right";
  47. }
  48. const barlineNodeElements: IXmlElement[] = barlineNode.elements();
  49. // read repeat- or ending line information
  50. for (let idx: number = 0, len: number = barlineNodeElements.length; idx < len; ++idx) {
  51. const childNode: IXmlElement = barlineNodeElements[idx];
  52. if ("repeat" === childNode.name && childNode.hasAttributes) {
  53. hasRepeat = true;
  54. direction = childNode.attribute("direction").value;
  55. } else if ( "ending" === childNode.name && childNode.hasAttributes &&
  56. childNode.attribute("type") !== undefined && childNode.attribute("number") !== undefined) {
  57. type = childNode.attribute("type").value;
  58. const num: string = childNode.attribute("number").value;
  59. // Parse the given ending indices:
  60. // handle cases like: "1, 2" or "1 + 2" or even "1 - 3, 6"
  61. const separatedEndingIndices: string[] = num.split("[,+]");
  62. for (let idx2: number = 0, len2: number = separatedEndingIndices.length; idx2 < len2; ++idx2) {
  63. const separatedEndingIndex: string = separatedEndingIndices[idx2];
  64. const indices: string[] = separatedEndingIndex.match("[0-9]");
  65. // check if possibly something like "1-3" is given..
  66. if (separatedEndingIndex.search("-") !== -1 && indices.length === 2) {
  67. const startIndex: number = parseInt(indices[0], 10);
  68. const endIndex: number = parseInt(indices[1], 10);
  69. for (let index: number = startIndex; index <= endIndex; index++) {
  70. endingIndices.push(index);
  71. }
  72. } else {
  73. for (let idx3: number = 0, len3: number = indices.length; idx3 < len3; ++idx3) {
  74. const index: string = indices[idx3];
  75. endingIndices.push(parseInt(index, 10));
  76. }
  77. }
  78. }
  79. }
  80. }
  81. // reset measure counter if not lastMeasure
  82. if (style === "light-heavy" && endingIndices.length === 0 && !hasRepeat) {
  83. pieceEndingDetected = true;
  84. }
  85. if (hasRepeat || endingIndices.length > 0) {
  86. if (location === "left") {
  87. if (type === "start") {
  88. const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
  89. AlignmentType.Begin, undefined, endingIndices);
  90. this.addInstruction(this.repetitionInstructions, newInstruction);
  91. }
  92. if (direction === "forward") {
  93. // start new Repetition
  94. const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.StartLine);
  95. this.addInstruction(this.repetitionInstructions, newInstruction);
  96. }
  97. } else { // location right
  98. if (type === "stop") {
  99. const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
  100. AlignmentType.End, undefined, endingIndices);
  101. this.addInstruction(this.repetitionInstructions, newInstruction);
  102. }
  103. if (direction === "backward") {
  104. const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.BackJumpLine);
  105. this.addInstruction(this.repetitionInstructions, newInstruction);
  106. }
  107. }
  108. }
  109. }
  110. }
  111. public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean {
  112. const wordsNode: IXmlElement = directionTypeNode.element("words");
  113. if (wordsNode !== undefined) {
  114. const dsRegEx: string = "d\\s?\\.s\\."; // Input for new RegExp(). TS eliminates the first \
  115. // must Trim string and ToLower before compare
  116. const innerText: string = wordsNode.value.trim().toLowerCase();
  117. if (StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine")) {
  118. let measureIndex: number = this.currentMeasureIndex;
  119. if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
  120. measureIndex--;
  121. }
  122. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlFine);
  123. this.addInstruction(this.repetitionInstructions, newInstruction);
  124. return true;
  125. }
  126. const dcRegEx: string = "d\\.\\s?c\\.";
  127. if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda")) {
  128. let measureIndex: number = this.currentMeasureIndex;
  129. if (relativeMeasurePosition < 0.5) {
  130. measureIndex--;
  131. }
  132. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlCoda);
  133. this.addInstruction(this.repetitionInstructions, newInstruction);
  134. return true;
  135. }
  136. if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine")) {
  137. let measureIndex: number = this.currentMeasureIndex;
  138. if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
  139. measureIndex--;
  140. }
  141. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlFine);
  142. this.addInstruction(this.repetitionInstructions, newInstruction);
  143. return true;
  144. }
  145. if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda")) {
  146. let measureIndex: number = this.currentMeasureIndex;
  147. if (relativeMeasurePosition < 0.5) {
  148. measureIndex--;
  149. }
  150. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlCoda);
  151. this.addInstruction(this.repetitionInstructions, newInstruction);
  152. return true;
  153. }
  154. if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
  155. StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo")) {
  156. let measureIndex: number = this.currentMeasureIndex;
  157. if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
  158. measureIndex--;
  159. }
  160. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapo);
  161. this.addInstruction(this.repetitionInstructions, newInstruction);
  162. return true;
  163. }
  164. if (StringUtil.StringContainsSeparatedWord(innerText, dsRegEx) ||
  165. StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno")) {
  166. let measureIndex: number = this.currentMeasureIndex;
  167. if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
  168. measureIndex--;
  169. }
  170. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegno);
  171. this.addInstruction(this.repetitionInstructions, newInstruction);
  172. return true;
  173. }
  174. if (StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda") ||
  175. StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda")) {
  176. let measureIndex: number = this.currentMeasureIndex;
  177. if (relativeMeasurePosition < 0.5) {
  178. measureIndex--;
  179. }
  180. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.ToCoda);
  181. this.addInstruction(this.repetitionInstructions, newInstruction);
  182. return true;
  183. }
  184. if (StringUtil.StringContainsSeparatedWord(innerText, "fine")) {
  185. let measureIndex: number = this.currentMeasureIndex;
  186. if (relativeMeasurePosition < 0.5) {
  187. measureIndex--;
  188. }
  189. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Fine);
  190. this.addInstruction(this.repetitionInstructions, newInstruction);
  191. return true;
  192. }
  193. if (StringUtil.StringContainsSeparatedWord(innerText, "coda")) {
  194. let measureIndex: number = this.currentMeasureIndex;
  195. if (relativeMeasurePosition > 0.5) {
  196. measureIndex++;
  197. }
  198. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
  199. this.addInstruction(this.repetitionInstructions, newInstruction);
  200. return true;
  201. }
  202. if (StringUtil.StringContainsSeparatedWord(innerText, "segno")) {
  203. let measureIndex: number = this.currentMeasureIndex;
  204. if (relativeMeasurePosition > 0.5) {
  205. measureIndex++;
  206. }
  207. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
  208. this.addInstruction(this.repetitionInstructions, newInstruction);
  209. return true;
  210. }
  211. } else if (directionTypeNode.element("segno") !== undefined) {
  212. let measureIndex: number = this.currentMeasureIndex;
  213. if (relativeMeasurePosition > 0.5) {
  214. measureIndex++;
  215. }
  216. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
  217. this.addInstruction(this.repetitionInstructions, newInstruction);
  218. return true;
  219. } else if (directionTypeNode.element("coda") !== undefined) {
  220. let measureIndex: number = this.currentMeasureIndex;
  221. if (relativeMeasurePosition > 0.5) {
  222. measureIndex++;
  223. }
  224. const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
  225. this.addInstruction(this.repetitionInstructions, newInstruction);
  226. return true;
  227. }
  228. return false;
  229. }
  230. public removeRedundantInstructions(): void {
  231. let segnoCount: number = 0;
  232. let codaCount: number = 0;
  233. //const fineCount: number = 0;
  234. let toCodaCount: number = 0;
  235. let dalSegnaCount: number = 0;
  236. for (let index: number = 0; index < this.repetitionInstructions.length; index++) {
  237. const instruction: RepetitionInstruction = this.repetitionInstructions[index];
  238. switch (instruction.type) {
  239. case RepetitionInstructionEnum.Coda:
  240. if (toCodaCount > 0) {
  241. if (this.findInstructionInPreviousMeasure(index, instruction.measureIndex, RepetitionInstructionEnum.ToCoda)) {
  242. instruction.type = RepetitionInstructionEnum.None;
  243. }
  244. }
  245. if (codaCount === 0 && toCodaCount === 0) {
  246. instruction.type = RepetitionInstructionEnum.ToCoda;
  247. instruction.alignment = AlignmentType.End;
  248. instruction.measureIndex--;
  249. }
  250. break;
  251. case RepetitionInstructionEnum.Segno:
  252. if (segnoCount - dalSegnaCount > 0) { // two segnos in a row
  253. let foundInstruction: boolean = false;
  254. for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) {
  255. const instr: RepetitionInstruction = this.repetitionInstructions[idx];
  256. if (instruction.measureIndex - instr.measureIndex === 1) {
  257. switch (instr.type) {
  258. case RepetitionInstructionEnum.BackJumpLine:
  259. if (toCodaCount - codaCount > 0) { // open toCoda existing
  260. instr.type = RepetitionInstructionEnum.DalSegnoAlCoda;
  261. } else {
  262. instr.type = RepetitionInstructionEnum.DalSegno;
  263. }
  264. instruction.type = RepetitionInstructionEnum.None;
  265. foundInstruction = true;
  266. break;
  267. case RepetitionInstructionEnum.DalSegno:
  268. case RepetitionInstructionEnum.DalSegnoAlFine:
  269. case RepetitionInstructionEnum.DalSegnoAlCoda:
  270. instruction.type = RepetitionInstructionEnum.None;
  271. foundInstruction = true;
  272. break;
  273. default:
  274. break;
  275. }
  276. }
  277. if (foundInstruction) {
  278. break;
  279. }
  280. }
  281. if (foundInstruction) {
  282. break;
  283. }
  284. // convert to dal segno instruction:
  285. if (toCodaCount - codaCount > 0) { // open toCoda existing
  286. instruction.type = RepetitionInstructionEnum.DalSegnoAlCoda;
  287. } else {
  288. instruction.type = RepetitionInstructionEnum.DalSegno;
  289. }
  290. instruction.alignment = AlignmentType.End;
  291. instruction.measureIndex--;
  292. }
  293. break;
  294. default:
  295. break;
  296. }
  297. // check if this instruction already exists or is otherwise redundant:
  298. if (this.backwardSearchForPreviousIdenticalInstruction(index, instruction) || instruction.type === RepetitionInstructionEnum.None) {
  299. this.repetitionInstructions.splice(index, 1);
  300. index--;
  301. } else {
  302. switch (instruction.type) {
  303. case RepetitionInstructionEnum.Fine:
  304. //fineCount++;
  305. break;
  306. case RepetitionInstructionEnum.ToCoda:
  307. toCodaCount++;
  308. break;
  309. case RepetitionInstructionEnum.Coda:
  310. codaCount++;
  311. break;
  312. case RepetitionInstructionEnum.Segno:
  313. segnoCount++;
  314. break;
  315. case RepetitionInstructionEnum.DalSegnoAlFine:
  316. case RepetitionInstructionEnum.DalSegnoAlCoda:
  317. dalSegnaCount++;
  318. break;
  319. default:
  320. break;
  321. }
  322. }
  323. }
  324. this.repetitionInstructions.sort(RepetitionInstructionComparer.Compare);
  325. }
  326. private findInstructionInPreviousMeasure(currentInstructionIndex: number, currentMeasureIndex: number, searchedType: RepetitionInstructionEnum): boolean {
  327. for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
  328. const instruction: RepetitionInstruction = this.repetitionInstructions[index];
  329. if (currentMeasureIndex - instruction.measureIndex === 1 && instruction.type === searchedType) {
  330. return true;
  331. }
  332. }
  333. return false;
  334. }
  335. private backwardSearchForPreviousIdenticalInstruction(currentInstructionIndex: number, currentInstruction: RepetitionInstruction): boolean {
  336. for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
  337. const instruction: RepetitionInstruction = this.repetitionInstructions[index];
  338. if (instruction.equals(currentInstruction)) {
  339. return true;
  340. }
  341. }
  342. return false;
  343. }
  344. private addInstruction(currentRepetitionInstructions: RepetitionInstruction[], newInstruction: RepetitionInstruction): void {
  345. let addInstruction: boolean = true;
  346. for (let idx: number = 0, len: number = currentRepetitionInstructions.length; idx < len; ++idx) {
  347. const repetitionInstruction: RepetitionInstruction = currentRepetitionInstructions[idx];
  348. if (newInstruction.equals(repetitionInstruction)) {
  349. addInstruction = false;
  350. break;
  351. }
  352. }
  353. if (addInstruction) {
  354. currentRepetitionInstructions.push(newInstruction);
  355. }
  356. }
  357. }