InstrumentReader.ts 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  1. import {Instrument} from "../Instrument";
  2. import {MusicSheet} from "../MusicSheet";
  3. import {VoiceGenerator} from "./VoiceGenerator";
  4. import {Staff} from "../VoiceData/Staff";
  5. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  6. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  7. import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
  8. import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
  9. import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
  10. import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
  11. import {Fraction} from "../../Common/DataObjects/Fraction";
  12. import {IXmlElement} from "../../Common/FileIO/Xml";
  13. import {ITextTranslation} from "../Interfaces/ITextTranslation";
  14. import {MusicSheetReadingException} from "../Exceptions";
  15. import {ClefEnum} from "../VoiceData/Instructions/ClefInstruction";
  16. import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction";
  17. import {KeyEnum} from "../VoiceData/Instructions/KeyInstruction";
  18. import {IXmlAttribute} from "../../Common/FileIO/Xml";
  19. import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
  20. import log from "loglevel";
  21. import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
  22. import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
  23. import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
  24. import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
  25. import {SlurReader} from "./MusicSymbolModules/SlurReader";
  26. import {StemDirectionType} from "../VoiceData/VoiceEntry";
  27. import {NoteType, NoteTypeHandler} from "../VoiceData/NoteType";
  28. import { SystemLinesEnumHelper } from "../Graphical/SystemLinesEnum";
  29. // import {Dictionary} from "typescript-collections";
  30. // FIXME: The following classes are missing
  31. //type ChordSymbolContainer = any;
  32. //type SlurReader = any;
  33. //type RepetitionInstructionReader = any;
  34. //declare class MusicSymbolModuleFactory {
  35. // public static createSlurReader(x: any): any;
  36. //}
  37. //
  38. //class MetronomeReader {
  39. // public static addMetronomeSettings(xmlNode: IXmlElement, musicSheet: MusicSheet): void { }
  40. // public static readMetronomeInstructions(xmlNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { }
  41. // public static readTempoInstruction(soundNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { }
  42. //}
  43. //
  44. //class ChordSymbolReader {
  45. // public static readChordSymbol(xmlNode:IXmlElement, musicSheet:MusicSheet, activeKey:any): void {
  46. // }
  47. //}
  48. /**
  49. * An InstrumentReader is used during the reading phase to keep parsing new measures from the MusicXML file
  50. * with the readNextXmlMeasure method.
  51. */
  52. export class InstrumentReader {
  53. constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
  54. this.repetitionInstructionReader = repetitionInstructionReader;
  55. this.xmlMeasureList = xmlMeasureList;
  56. this.musicSheet = instrument.GetMusicSheet;
  57. this.instrument = instrument;
  58. this.activeClefs = new Array(instrument.Staves.length);
  59. this.activeClefsHaveBeenInitialized = new Array(instrument.Staves.length);
  60. for (let i: number = 0; i < instrument.Staves.length; i++) {
  61. this.activeClefsHaveBeenInitialized[i] = false;
  62. }
  63. this.createExpressionGenerators(instrument.Staves.length);
  64. this.slurReader = new SlurReader(this.musicSheet);
  65. }
  66. private repetitionInstructionReader: RepetitionInstructionReader;
  67. private xmlMeasureList: IXmlElement[];
  68. private musicSheet: MusicSheet;
  69. private slurReader: SlurReader;
  70. private instrument: Instrument;
  71. private voiceGeneratorsDict: { [n: number]: VoiceGenerator; } = {};
  72. private staffMainVoiceGeneratorDict: { [staffId: number]: VoiceGenerator } = {};
  73. private inSourceMeasureInstrumentIndex: number;
  74. private divisions: number = 0;
  75. private currentMeasure: SourceMeasure;
  76. private previousMeasure: SourceMeasure;
  77. private currentClefNumber: number = 1;
  78. private currentXmlMeasureIndex: number = 0;
  79. private currentStaff: Staff;
  80. private currentStaffEntry: SourceStaffEntry;
  81. private activeClefs: ClefInstruction[];
  82. private activeKey: KeyInstruction;
  83. private activeRhythm: RhythmInstruction;
  84. private activeClefsHaveBeenInitialized: boolean[];
  85. private activeKeyHasBeenInitialized: boolean = false;
  86. private abstractInstructions: [number, AbstractNotationInstruction][] = [];
  87. private openChordSymbolContainers: ChordSymbolContainer[] = [];
  88. private expressionReaders: ExpressionReader[];
  89. private currentVoiceGenerator: VoiceGenerator;
  90. //private openSlurDict: { [n: number]: Slur; } = {};
  91. private maxTieNoteFraction: Fraction;
  92. public get ActiveKey(): KeyInstruction {
  93. return this.activeKey;
  94. }
  95. public get MaxTieNoteFraction(): Fraction {
  96. return this.maxTieNoteFraction;
  97. }
  98. public get ActiveRhythm(): RhythmInstruction {
  99. return this.activeRhythm;
  100. }
  101. public set ActiveRhythm(value: RhythmInstruction) {
  102. this.activeRhythm = value;
  103. }
  104. /**
  105. * Main CreateSheet: read the next XML Measure and save all data to the given [[SourceMeasure]].
  106. * @param currentMeasure
  107. * @param measureStartAbsoluteTimestamp - Using this instead of currentMeasure.AbsoluteTimestamp as it isn't set yet
  108. * @param guitarPro
  109. * @returns {boolean}
  110. */
  111. public readNextXmlMeasure(currentMeasure: SourceMeasure, measureStartAbsoluteTimestamp: Fraction, guitarPro: boolean): boolean {
  112. if (this.currentXmlMeasureIndex >= this.xmlMeasureList.length) {
  113. return false;
  114. }
  115. this.currentMeasure = currentMeasure;
  116. this.inSourceMeasureInstrumentIndex = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument);
  117. if (this.repetitionInstructionReader) {
  118. this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
  119. }
  120. let currentFraction: Fraction = new Fraction(0, 1);
  121. let previousFraction: Fraction = new Fraction(0, 1);
  122. let divisionsException: boolean = false;
  123. this.maxTieNoteFraction = new Fraction(0, 1);
  124. let lastNoteWasGrace: boolean = false;
  125. try {
  126. const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[this.currentXmlMeasureIndex].elements();
  127. for (let xmlNodeIndex: number = 0; xmlNodeIndex < xmlMeasureListArr.length; xmlNodeIndex++) {
  128. const xmlNode: IXmlElement = xmlMeasureListArr[xmlNodeIndex];
  129. if (xmlNode.name === "print") {
  130. const newSystemAttr: IXmlAttribute = xmlNode.attribute("new-system");
  131. if (newSystemAttr?.value === "yes") {
  132. currentMeasure.printNewSystemXml = true;
  133. }
  134. const newPageAttr: IXmlAttribute = xmlNode.attribute("new-page");
  135. if (newPageAttr?.value === "yes") {
  136. currentMeasure.printNewPageXml = true;
  137. }
  138. } else if (xmlNode.name === "note") {
  139. let printObject: boolean = true;
  140. if (xmlNode.hasAttributes && xmlNode.attribute("print-object") &&
  141. xmlNode.attribute("print-object").value === "no") {
  142. printObject = false; // note will not be rendered, but still parsed for Playback etc.
  143. // if (xmlNode.attribute("print-spacing")) {
  144. // if (xmlNode.attribute("print-spacing").value === "yes" {
  145. // // TODO give spacing for invisible notes even when not displayed. might be hard with Vexflow formatting
  146. }
  147. let noteStaff: number = 1;
  148. if (this.instrument.Staves.length > 1) {
  149. if (xmlNode.element("staff")) {
  150. noteStaff = parseInt(xmlNode.element("staff").value, 10);
  151. if (isNaN(noteStaff)) {
  152. log.debug("InstrumentReader.readNextXmlMeasure.get staff number");
  153. noteStaff = 1;
  154. }
  155. }
  156. }
  157. this.currentStaff = this.instrument.Staves[noteStaff - 1];
  158. const isChord: boolean = xmlNode.element("chord") !== undefined;
  159. if (xmlNode.element("voice")) {
  160. const noteVoice: number = parseInt(xmlNode.element("voice").value, 10);
  161. this.currentVoiceGenerator = this.getOrCreateVoiceGenerator(noteVoice, noteStaff - 1);
  162. } else {
  163. if (!isChord || !this.currentVoiceGenerator) {
  164. this.currentVoiceGenerator = this.getOrCreateVoiceGenerator(1, noteStaff - 1);
  165. }
  166. }
  167. let noteDivisions: number = 0;
  168. let noteDuration: Fraction = new Fraction(0, 1);
  169. let normalNotes: number = 2;
  170. let typeDuration: Fraction = undefined;
  171. let isTuplet: boolean = false;
  172. if (xmlNode.element("duration")) {
  173. noteDivisions = parseInt(xmlNode.element("duration").value, 10);
  174. if (!isNaN(noteDivisions)) {
  175. noteDuration = new Fraction(noteDivisions, 4 * this.divisions);
  176. if (noteDivisions === 0) {
  177. noteDuration = this.getNoteDurationFromTypeNode(xmlNode);
  178. } else {
  179. typeDuration = this.getNoteDurationFromTypeNode(xmlNode);
  180. }
  181. if (xmlNode.element("time-modification")) {
  182. noteDuration = this.getNoteDurationForTuplet(xmlNode);
  183. const time: IXmlElement = xmlNode.element("time-modification");
  184. if (time) {
  185. if (time.element("normal-notes")) {
  186. normalNotes = parseInt(time.element("normal-notes").value, 10);
  187. }
  188. }
  189. isTuplet = true;
  190. }
  191. } else {
  192. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid Note Duration.");
  193. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  194. log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
  195. continue;
  196. }
  197. }
  198. const restNote: boolean = xmlNode.element("rest") !== undefined;
  199. //log.info("New note found!", noteDivisions, noteDuration.toString(), restNote);
  200. const notationsNode: IXmlElement = xmlNode.element("notations"); // used for multiple checks further on
  201. const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
  202. let graceNoteSlash: boolean = false;
  203. let graceSlur: boolean = false;
  204. if (isGraceNote) {
  205. const graceNode: IXmlElement = xmlNode.element("grace");
  206. if (graceNode && graceNode.attributes()) {
  207. if (graceNode.attribute("slash")) {
  208. const slash: string = graceNode.attribute("slash").value;
  209. if (slash === "yes") {
  210. graceNoteSlash = true;
  211. }
  212. }
  213. }
  214. noteDuration = this.getNoteDurationFromTypeNode(xmlNode);
  215. const notationNode: IXmlElement = xmlNode.element("notations");
  216. if (notationNode) {
  217. if (notationNode.element("slur")) {
  218. graceSlur = true;
  219. // grace slurs could be non-binary, but VexFlow.GraceNoteGroup modifier system is currently only boolean for slurs.
  220. }
  221. }
  222. }
  223. // check for cue note
  224. let isCueNote: boolean = false;
  225. const cueNode: IXmlElement = xmlNode.element("cue");
  226. if (cueNode) {
  227. isCueNote = true;
  228. }
  229. // alternative: check for <type size="cue">
  230. const typeNode: IXmlElement = xmlNode.element("type");
  231. let noteTypeXml: NoteType = NoteType.UNDEFINED;
  232. if (typeNode) {
  233. const sizeAttr: Attr = typeNode.attribute("size");
  234. if (sizeAttr !== undefined && sizeAttr !== null) {
  235. if (sizeAttr.value === "cue") {
  236. isCueNote = true;
  237. }
  238. }
  239. noteTypeXml = NoteTypeHandler.StringToNoteType(typeNode.value);
  240. }
  241. // check stem element
  242. let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
  243. let stemColorXml: string;
  244. const stemNode: IXmlElement = xmlNode.element("stem");
  245. if (stemNode) {
  246. switch (stemNode.value) {
  247. case "down":
  248. stemDirectionXml = StemDirectionType.Down;
  249. break;
  250. case "up":
  251. stemDirectionXml = StemDirectionType.Up;
  252. break;
  253. case "double":
  254. stemDirectionXml = StemDirectionType.Double;
  255. break;
  256. case "none":
  257. stemDirectionXml = StemDirectionType.None;
  258. break;
  259. default:
  260. stemDirectionXml = StemDirectionType.Undefined;
  261. }
  262. const stemColorAttr: Attr = stemNode.attribute("color");
  263. if (stemColorAttr) { // can be null, maybe also undefined
  264. stemColorXml = this.parseXmlColor(stemColorAttr.value);
  265. }
  266. }
  267. // check Tremolo
  268. let tremoloStrokes: number = 0;
  269. let vibratoStrokes: boolean = false;
  270. if (notationsNode) {
  271. const ornamentsNode: IXmlElement = notationsNode.element("ornaments");
  272. if (ornamentsNode) {
  273. const tremoloNode: IXmlElement = ornamentsNode.element("tremolo");
  274. if (tremoloNode) {
  275. const tremoloType: Attr = tremoloNode.attribute("type");
  276. if (tremoloType && tremoloType.value === "single") {
  277. const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10);
  278. if (tremoloStrokesGiven > 0) {
  279. tremoloStrokes = tremoloStrokesGiven;
  280. }
  281. }
  282. // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
  283. }
  284. const vibratoNode: IXmlElement = ornamentsNode.element("wavy-line");
  285. if (vibratoNode !== undefined) {
  286. const vibratoType: Attr = vibratoNode.attribute("type");
  287. if (vibratoType && vibratoType.value === "start") {
  288. vibratoStrokes = true;
  289. }
  290. }
  291. }
  292. }
  293. // check notehead/color
  294. let noteheadColorXml: string;
  295. const noteheadNode: IXmlElement = xmlNode.element("notehead");
  296. if (noteheadNode) {
  297. const colorAttr: Attr = noteheadNode.attribute("color");
  298. if (colorAttr) {
  299. noteheadColorXml = this.parseXmlColor(colorAttr.value);
  300. }
  301. }
  302. let noteColorXml: string;
  303. const noteColorAttr: Attr = xmlNode.attribute("color");
  304. if (noteColorAttr) { // can be undefined
  305. noteColorXml = this.parseXmlColor(noteColorAttr.value);
  306. if (!noteheadColorXml) {
  307. noteheadColorXml = noteColorXml;
  308. }
  309. if (!stemColorXml) {
  310. stemColorXml = noteColorXml;
  311. }
  312. }
  313. let musicTimestamp: Fraction = currentFraction.clone();
  314. if (isChord) {
  315. musicTimestamp = previousFraction.clone();
  316. }
  317. this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry(
  318. musicTimestamp,
  319. this.inSourceMeasureInstrumentIndex + noteStaff - 1,
  320. this.currentStaff
  321. ).staffEntry;
  322. //log.info("currentStaffEntry", this.currentStaffEntry, this.currentMeasure.VerticalSourceStaffEntryContainers.length);
  323. if (!this.currentVoiceGenerator.hasVoiceEntry()
  324. || (!isChord && !isGraceNote && !lastNoteWasGrace)
  325. || (isGraceNote && !lastNoteWasGrace)
  326. || (isGraceNote && !isChord)
  327. || (!isGraceNote && lastNoteWasGrace)
  328. ) {
  329. this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, !restNote && !isGraceNote,
  330. isGraceNote, graceNoteSlash, graceSlur);
  331. }
  332. if (!isGraceNote && !isChord) {
  333. previousFraction = currentFraction.clone();
  334. currentFraction.Add(noteDuration);
  335. }
  336. if (
  337. isChord &&
  338. this.currentStaffEntry !== undefined &&
  339. this.currentStaffEntry.ParentStaff !== this.currentStaff
  340. ) {
  341. this.currentStaffEntry = this.currentVoiceGenerator.checkForStaffEntryLink(
  342. this.inSourceMeasureInstrumentIndex + noteStaff - 1, this.currentStaff, this.currentStaffEntry, this.currentMeasure
  343. );
  344. }
  345. const beginOfMeasure: boolean = (
  346. this.currentStaffEntry !== undefined &&
  347. this.currentStaffEntry.Timestamp !== undefined &&
  348. this.currentStaffEntry.Timestamp.Equals(new Fraction(0, 1)) && !this.currentStaffEntry.hasNotes()
  349. );
  350. this.saveAbstractInstructionList(this.instrument.Staves.length, beginOfMeasure);
  351. if (this.openChordSymbolContainers.length !== 0) {
  352. this.currentStaffEntry.ChordContainers = this.openChordSymbolContainers;
  353. // TODO handle multiple chords on one note/staffentry
  354. this.openChordSymbolContainers = [];
  355. }
  356. if (this.activeRhythm) {
  357. // (*) this.musicSheet.SheetPlaybackSetting.Rhythm = this.activeRhythm.Rhythm;
  358. }
  359. if (!isTuplet && !isGraceNote) {
  360. noteDuration = new Fraction(noteDivisions, 4 * this.divisions);
  361. }
  362. this.currentVoiceGenerator.read(
  363. xmlNode, noteDuration, typeDuration, noteTypeXml, normalNotes, restNote,
  364. this.currentStaffEntry, this.currentMeasure,
  365. measureStartAbsoluteTimestamp,
  366. this.maxTieNoteFraction, isChord, guitarPro,
  367. printObject, isCueNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml,
  368. vibratoStrokes
  369. );
  370. // notationsNode created further up for multiple checks
  371. if (notationsNode !== undefined && notationsNode.element("dynamics")) {
  372. const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
  373. if (expressionReader) {
  374. expressionReader.readExpressionParameters(
  375. xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
  376. );
  377. expressionReader.read(
  378. xmlNode, this.currentMeasure, previousFraction
  379. );
  380. }
  381. }
  382. lastNoteWasGrace = isGraceNote;
  383. } else if (xmlNode.name === "attributes") {
  384. const divisionsNode: IXmlElement = xmlNode.element("divisions");
  385. if (divisionsNode) {
  386. this.divisions = parseInt(divisionsNode.value, 10);
  387. if (isNaN(this.divisions)) {
  388. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
  389. "Invalid divisions value at Instrument: ");
  390. log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
  391. this.divisions = this.readDivisionsFromNotes();
  392. if (this.divisions > 0) {
  393. this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
  394. } else {
  395. divisionsException = true;
  396. throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
  397. }
  398. }
  399. }
  400. if (
  401. !xmlNode.element("divisions") &&
  402. this.divisions === 0 &&
  403. this.currentXmlMeasureIndex === 0
  404. ) {
  405. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", "Invalid divisions value at Instrument: ");
  406. this.divisions = this.readDivisionsFromNotes();
  407. if (this.divisions > 0) {
  408. this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
  409. } else {
  410. divisionsException = true;
  411. throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
  412. }
  413. }
  414. this.addAbstractInstruction(xmlNode, guitarPro);
  415. if (currentFraction.Equals(new Fraction(0, 1)) &&
  416. this.isAttributesNodeAtBeginOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
  417. this.saveAbstractInstructionList(this.instrument.Staves.length, true);
  418. }
  419. if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
  420. this.saveClefInstructionAtEndOfMeasure();
  421. }
  422. const staffDetailsNode: IXmlElement = xmlNode.element("staff-details");
  423. if (staffDetailsNode) {
  424. const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
  425. if (staffLinesNode) {
  426. let staffNumber: number = 1;
  427. const staffNumberAttr: Attr = staffDetailsNode.attribute("number");
  428. if (staffNumberAttr) {
  429. staffNumber = parseInt(staffNumberAttr.value, 10);
  430. }
  431. this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10);
  432. }
  433. }
  434. // check multi measure rest
  435. const measureStyle: IXmlElement = xmlNode.element("measure-style");
  436. if (measureStyle) {
  437. const multipleRest: IXmlElement = measureStyle.element("multiple-rest");
  438. if (multipleRest) {
  439. // TODO: save multirest per staff info a dictionary, to display a partial multirest if multirest values across staffs differ.
  440. // this makes the code bulkier though, and for now we only draw multirests if the staffs have the same multirest lengths.
  441. // if (!currentMeasure.multipleRestMeasuresPerStaff) {
  442. // currentMeasure.multipleRestMeasuresPerStaff = new Dictionary<number, number>();
  443. // }
  444. const multipleRestValueXml: string = multipleRest.value;
  445. let multipleRestNumber: number = 0;
  446. try {
  447. multipleRestNumber = Number.parseInt(multipleRestValueXml, 10);
  448. if (currentMeasure.multipleRestMeasures !== undefined && multipleRestNumber !== currentMeasure.multipleRestMeasures) {
  449. // different multi-rest values in same measure for different staffs
  450. currentMeasure.multipleRestMeasures = 0; // for now, ignore multirest here. TODO: take minimum
  451. // currentMeasure.multipleRestMeasuresPerStaff.setValue(this.currentStaff?.Id, multipleRestNumber);
  452. // issue: currentStaff can be undefined for first measure
  453. } else {
  454. currentMeasure.multipleRestMeasures = multipleRestNumber;
  455. }
  456. } catch (e) {
  457. console.log("multirest parse error: " + e);
  458. }
  459. }
  460. }
  461. } else if (xmlNode.name === "forward") {
  462. const forFraction: number = parseInt(xmlNode.element("duration").value, 10);
  463. currentFraction.Add(new Fraction(forFraction, 4 * this.divisions));
  464. } else if (xmlNode.name === "backup") {
  465. const backFraction: number = parseInt(xmlNode.element("duration").value, 10);
  466. currentFraction.Sub(new Fraction(backFraction, 4 * this.divisions));
  467. if (currentFraction.IsNegative()) {
  468. currentFraction = new Fraction(0, 1);
  469. }
  470. previousFraction.Sub(new Fraction(backFraction, 4 * this.divisions));
  471. if (previousFraction.IsNegative()) {
  472. previousFraction = new Fraction(0, 1);
  473. }
  474. } else if (xmlNode.name === "direction") {
  475. const directionTypeNode: IXmlElement = xmlNode.element("direction-type");
  476. // (*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
  477. let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
  478. if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm) {
  479. relativePositionInMeasure /= this.activeRhythm.Rhythm.RealValue;
  480. }
  481. let handeled: boolean = false;
  482. if (this.repetitionInstructionReader) {
  483. handeled = this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols( directionTypeNode,
  484. relativePositionInMeasure);
  485. }
  486. if (!handeled) {
  487. let expressionReader: ExpressionReader = this.expressionReaders[0];
  488. const staffIndex: number = this.readExpressionStaffNumber(xmlNode) - 1;
  489. if (staffIndex < this.expressionReaders.length) {
  490. expressionReader = this.expressionReaders[staffIndex];
  491. }
  492. if (expressionReader) {
  493. if (directionTypeNode.element("octave-shift")) {
  494. expressionReader.readExpressionParameters(
  495. xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, true
  496. );
  497. expressionReader.addOctaveShift(xmlNode, this.currentMeasure, previousFraction.clone());
  498. }
  499. expressionReader.readExpressionParameters(
  500. xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
  501. );
  502. expressionReader.read(xmlNode, this.currentMeasure, currentFraction);
  503. }
  504. }
  505. } else if (xmlNode.name === "barline") {
  506. if (this.repetitionInstructionReader) {
  507. const measureEndsSystem: boolean = this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode);
  508. if (measureEndsSystem) {
  509. this.currentMeasure.HasEndLine = true;
  510. }
  511. }
  512. const location: IXmlAttribute = xmlNode.attribute("location");
  513. const isEndingBarline: boolean = (xmlNodeIndex === xmlMeasureListArr.length - 1);
  514. if (isEndingBarline || (location && location.value === "right")) {
  515. const stringValue: string = xmlNode.element("bar-style")?.value;
  516. // TODO apparently we didn't anticipate bar-style not existing (the ? above was missing). how to handle?
  517. if (stringValue) {
  518. this.currentMeasure.endingBarStyleXml = stringValue;
  519. this.currentMeasure.endingBarStyleEnum = SystemLinesEnumHelper.xmlBarlineStyleToSystemLinesEnum(stringValue);
  520. }
  521. }
  522. // TODO do we need to process bars with left location too?
  523. } else if (xmlNode.name === "sound") {
  524. // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
  525. } else if (xmlNode.name === "harmony") {
  526. // new chord, could be second chord on same staffentry/note
  527. this.openChordSymbolContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey));
  528. }
  529. }
  530. for (const j in this.voiceGeneratorsDict) {
  531. if (this.voiceGeneratorsDict.hasOwnProperty(j)) {
  532. const voiceGenerator: VoiceGenerator = this.voiceGeneratorsDict[j];
  533. voiceGenerator.checkForOpenBeam();
  534. }
  535. }
  536. if (this.currentXmlMeasureIndex === this.xmlMeasureList.length - 1) {
  537. for (let i: number = 0; i < this.instrument.Staves.length; i++) {
  538. if (!this.activeClefsHaveBeenInitialized[i]) {
  539. this.createDefaultClefInstruction(this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument) + i);
  540. }
  541. }
  542. if (!this.activeKeyHasBeenInitialized) {
  543. this.createDefaultKeyInstruction();
  544. }
  545. for (let i: number = 0; i < this.expressionReaders.length; i++) {
  546. const reader: ExpressionReader = this.expressionReaders[i];
  547. if (reader) {
  548. reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
  549. }
  550. }
  551. }
  552. // if this is the first measure and no BPM info found, we set it to 120
  553. // next measures will automatically inherit that value
  554. if (!this.musicSheet.HasBPMInfo) {
  555. this.currentMeasure.TempoInBPM = 120;
  556. } else if (currentMeasure.TempoInBPM === 0) {
  557. this.currentMeasure.TempoInBPM = this.previousMeasure.TempoInBPM;
  558. }
  559. } catch (e) {
  560. if (divisionsException) {
  561. throw new MusicSheetReadingException(e.Message);
  562. }
  563. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MeasureError", "Error while reading Measure.");
  564. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  565. log.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
  566. }
  567. this.previousMeasure = this.currentMeasure;
  568. this.currentXmlMeasureIndex += 1;
  569. return true;
  570. }
  571. /** Parse a color in XML format. Can be #ARGB or #RGB format, colors as byte hex values.
  572. * @return color in Vexflow format #[A]RGB or undefined for invalid xmlColorString
  573. */
  574. public parseXmlColor(xmlColorString: string): string {
  575. if (!xmlColorString) {
  576. return undefined;
  577. }
  578. if (xmlColorString.length === 7) { // #RGB
  579. return xmlColorString;
  580. } else if (xmlColorString.length === 9) { // #ARGB
  581. return "#" + xmlColorString.substr(3); // cut away alpha channel
  582. } else {
  583. return undefined; // invalid xml color
  584. }
  585. }
  586. public doCalculationsAfterDurationHasBeenSet(): void {
  587. for (const j in this.voiceGeneratorsDict) {
  588. if (this.voiceGeneratorsDict.hasOwnProperty(j)) {
  589. this.voiceGeneratorsDict[j].checkOpenTies();
  590. }
  591. }
  592. }
  593. /**
  594. * Get or create the passing [[VoiceGenerator]].
  595. * @param voiceId
  596. * @param staffId
  597. * @returns {VoiceGenerator}
  598. */
  599. private getOrCreateVoiceGenerator(voiceId: number, staffId: number): VoiceGenerator {
  600. const staff: Staff = this.instrument.Staves[staffId];
  601. let voiceGenerator: VoiceGenerator = this.voiceGeneratorsDict[voiceId];
  602. if (voiceGenerator) {
  603. if (staff.Voices.indexOf(voiceGenerator.GetVoice) === -1) {
  604. staff.Voices.push(voiceGenerator.GetVoice);
  605. }
  606. } else {
  607. const mainVoiceGenerator: VoiceGenerator = this.staffMainVoiceGeneratorDict[staffId];
  608. if (mainVoiceGenerator) {
  609. voiceGenerator = new VoiceGenerator(this.instrument, voiceId, this.slurReader, mainVoiceGenerator.GetVoice);
  610. staff.Voices.push(voiceGenerator.GetVoice);
  611. this.voiceGeneratorsDict[voiceId] = voiceGenerator;
  612. } else {
  613. voiceGenerator = new VoiceGenerator(this.instrument, voiceId, this.slurReader);
  614. staff.Voices.push(voiceGenerator.GetVoice);
  615. this.voiceGeneratorsDict[voiceId] = voiceGenerator;
  616. this.staffMainVoiceGeneratorDict[staffId] = voiceGenerator;
  617. }
  618. }
  619. return voiceGenerator;
  620. }
  621. private createExpressionGenerators(numberOfStaves: number): void {
  622. this.expressionReaders = new Array(numberOfStaves);
  623. for (let i: number = 0; i < numberOfStaves; i++) {
  624. this.expressionReaders[i] = new ExpressionReader(this.musicSheet, this.instrument, i + 1);
  625. }
  626. }
  627. /**
  628. * Create the default [[ClefInstruction]] for the given staff index.
  629. * @param staffIndex
  630. */
  631. private createDefaultClefInstruction(staffIndex: number): void {
  632. let first: SourceMeasure;
  633. if (this.musicSheet.SourceMeasures.length > 0) {
  634. first = this.musicSheet.SourceMeasures[0];
  635. } else {
  636. first = this.currentMeasure;
  637. }
  638. const clefInstruction: ClefInstruction = new ClefInstruction(ClefEnum.G, 0, 2);
  639. let firstStaffEntry: SourceStaffEntry;
  640. if (!first.FirstInstructionsStaffEntries[staffIndex]) {
  641. firstStaffEntry = new SourceStaffEntry(undefined, undefined);
  642. first.FirstInstructionsStaffEntries[staffIndex] = firstStaffEntry;
  643. } else {
  644. firstStaffEntry = first.FirstInstructionsStaffEntries[staffIndex];
  645. firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
  646. }
  647. clefInstruction.Parent = firstStaffEntry;
  648. firstStaffEntry.Instructions.splice(0, 0, clefInstruction);
  649. }
  650. /**
  651. * Create the default [[KeyInstruction]] in case no [[KeyInstruction]] is given in the whole [[Instrument]].
  652. */
  653. private createDefaultKeyInstruction(): void {
  654. let first: SourceMeasure;
  655. if (this.musicSheet.SourceMeasures.length > 0) {
  656. first = this.musicSheet.SourceMeasures[0];
  657. } else {
  658. first = this.currentMeasure;
  659. }
  660. const keyInstruction: KeyInstruction = new KeyInstruction(undefined, 0, KeyEnum.major);
  661. for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + this.instrument.Staves.length; j++) {
  662. if (!first.FirstInstructionsStaffEntries[j]) {
  663. const firstStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
  664. first.FirstInstructionsStaffEntries[j] = firstStaffEntry;
  665. keyInstruction.Parent = firstStaffEntry;
  666. firstStaffEntry.Instructions.push(keyInstruction);
  667. } else {
  668. const firstStaffEntry: SourceStaffEntry = first.FirstInstructionsStaffEntries[j];
  669. keyInstruction.Parent = firstStaffEntry;
  670. firstStaffEntry.removeFirstInstructionOfTypeKeyInstruction();
  671. if (firstStaffEntry.Instructions[0] instanceof ClefInstruction) {
  672. firstStaffEntry.Instructions.splice(1, 0, keyInstruction);
  673. } else {
  674. firstStaffEntry.Instructions.splice(0, 0, keyInstruction);
  675. }
  676. }
  677. }
  678. }
  679. /**
  680. * Check if the given attributesNode is at the begin of a XmlMeasure.
  681. * @param parentNode
  682. * @param attributesNode
  683. * @returns {boolean}
  684. */
  685. private isAttributesNodeAtBeginOfMeasure(parentNode: IXmlElement, attributesNode: IXmlElement): boolean {
  686. const children: IXmlElement[] = parentNode.elements();
  687. const attributesNodeIndex: number = children.indexOf(attributesNode); // FIXME | 0
  688. if (attributesNodeIndex > 0 && children[attributesNodeIndex - 1].name === "backup") {
  689. return true;
  690. }
  691. let firstNoteNodeIndex: number = -1;
  692. for (let i: number = 0; i < children.length; i++) {
  693. if (children[i].name === "note") {
  694. firstNoteNodeIndex = i;
  695. break;
  696. }
  697. }
  698. return (attributesNodeIndex < firstNoteNodeIndex && firstNoteNodeIndex > 0) || (firstNoteNodeIndex < 0);
  699. }
  700. /**
  701. * Check if the given attributesNode is at the end of a XmlMeasure.
  702. * @param parentNode
  703. * @param attributesNode
  704. * @returns {boolean}
  705. */
  706. private isAttributesNodeAtEndOfMeasure(parentNode: IXmlElement, attributesNode: IXmlElement): boolean {
  707. const childs: IXmlElement[] = parentNode.elements().slice(); // slice=arrayCopy
  708. let attributesNodeIndex: number = 0;
  709. for (let i: number = 0; i < childs.length; i++) {
  710. if (childs[i] === attributesNode) {
  711. attributesNodeIndex = i;
  712. break;
  713. }
  714. }
  715. let nextNoteNodeIndex: number = 0;
  716. for (let i: number = attributesNodeIndex; i < childs.length; i++) {
  717. if (childs[i].name === "note") {
  718. nextNoteNodeIndex = i;
  719. break;
  720. }
  721. }
  722. return attributesNodeIndex > nextNoteNodeIndex;
  723. }
  724. /**
  725. * Called only when no noteDuration is given in XML.
  726. * @param xmlNode
  727. * @returns {Fraction}
  728. */
  729. private getNoteDurationFromTypeNode(xmlNode: IXmlElement): Fraction {
  730. const typeNode: IXmlElement = xmlNode.element("type");
  731. if (typeNode) {
  732. const type: string = typeNode.value;
  733. return NoteTypeHandler.getNoteDurationFromType(type);
  734. }
  735. return new Fraction(0, 4 * this.divisions);
  736. }
  737. /**
  738. * Add (the three basic) Notation Instructions to a list
  739. * @param node
  740. * @param guitarPro
  741. */
  742. private addAbstractInstruction(node: IXmlElement, guitarPro: boolean): void {
  743. if (node.element("divisions")) {
  744. if (node.elements().length === 1) {
  745. return;
  746. }
  747. }
  748. const transposeNode: IXmlElement = node.element("transpose");
  749. if (transposeNode) {
  750. const chromaticNode: IXmlElement = transposeNode.element("chromatic");
  751. if (chromaticNode) {
  752. this.instrument.PlaybackTranspose = parseInt(chromaticNode.value, 10);
  753. }
  754. }
  755. const clefList: IXmlElement[] = node.elements("clef");
  756. let errorMsg: string;
  757. if (clefList.length > 0) {
  758. for (let idx: number = 0, len: number = clefList.length; idx < len; ++idx) {
  759. const nodeList: IXmlElement = clefList[idx];
  760. let clefEnum: ClefEnum = ClefEnum.G;
  761. let line: number = 2;
  762. let staffNumber: number = 1;
  763. let clefOctaveOffset: number = 0;
  764. const lineNode: IXmlElement = nodeList.element("line");
  765. if (lineNode) {
  766. try {
  767. line = parseInt(lineNode.value, 10);
  768. } catch (ex) {
  769. errorMsg = ITextTranslation.translateText(
  770. "ReaderErrorMessages/ClefLineError",
  771. "Invalid clef line given -> using default clef line."
  772. );
  773. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  774. line = 2;
  775. log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
  776. }
  777. }
  778. const signNode: IXmlElement = nodeList.element("sign");
  779. if (signNode) {
  780. try {
  781. clefEnum = ClefEnum[signNode.value];
  782. if (!ClefInstruction.isSupportedClef(clefEnum)) {
  783. errorMsg = ITextTranslation.translateText(
  784. "ReaderErrorMessages/ClefError",
  785. "Unsupported clef found -> using default clef."
  786. );
  787. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  788. clefEnum = ClefEnum.G;
  789. line = 2;
  790. }
  791. if (clefEnum === ClefEnum.TAB) {
  792. clefOctaveOffset = -1;
  793. }
  794. } catch (e) {
  795. errorMsg = ITextTranslation.translateText(
  796. "ReaderErrorMessages/ClefError",
  797. "Invalid clef found -> using default clef."
  798. );
  799. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  800. clefEnum = ClefEnum.G;
  801. line = 2;
  802. log.debug("InstrumentReader.addAbstractInstruction", errorMsg, e);
  803. }
  804. }
  805. const clefOctaveNode: IXmlElement = nodeList.element("clef-octave-change");
  806. if (clefOctaveNode) {
  807. try {
  808. clefOctaveOffset = parseInt(clefOctaveNode.value, 10);
  809. } catch (e) {
  810. errorMsg = ITextTranslation.translateText(
  811. "ReaderErrorMessages/ClefOctaveError",
  812. "Invalid clef octave found -> using default clef octave."
  813. );
  814. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  815. clefOctaveOffset = 0;
  816. }
  817. }
  818. if (nodeList.hasAttributes && nodeList.attributes()[0].name === "number") {
  819. try {
  820. staffNumber = parseInt(nodeList.attributes()[0].value, 10);
  821. if (staffNumber > this.currentClefNumber) {
  822. staffNumber = this.currentClefNumber;
  823. }
  824. this.currentClefNumber = staffNumber + 1;
  825. } catch (err) {
  826. errorMsg = ITextTranslation.translateText(
  827. "ReaderErrorMessages/ClefError",
  828. "Invalid clef found -> using default clef."
  829. );
  830. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  831. staffNumber = 1;
  832. this.currentClefNumber = staffNumber + 1;
  833. }
  834. }
  835. const clefInstruction: ClefInstruction = new ClefInstruction(clefEnum, clefOctaveOffset, line);
  836. this.abstractInstructions.push([staffNumber, clefInstruction]);
  837. }
  838. }
  839. if (node.element("key") !== undefined && this.instrument.MidiInstrumentId !== MidiInstrument.Percussion) {
  840. let key: number = 0;
  841. const keyNode: IXmlElement = node.element("key").element("fifths");
  842. if (keyNode) {
  843. try {
  844. key = parseInt(keyNode.value, 10);
  845. } catch (ex) {
  846. errorMsg = ITextTranslation.translateText(
  847. "ReaderErrorMessages/KeyError",
  848. "Invalid key found -> set to default."
  849. );
  850. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  851. key = 0;
  852. log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
  853. }
  854. }
  855. let keyEnum: KeyEnum = KeyEnum.none;
  856. let modeNode: IXmlElement = node.element("key");
  857. if (modeNode) {
  858. modeNode = modeNode.element("mode");
  859. }
  860. if (modeNode) {
  861. try {
  862. keyEnum = KeyEnum[modeNode.value];
  863. } catch (ex) {
  864. errorMsg = ITextTranslation.translateText(
  865. "ReaderErrorMessages/KeyError",
  866. "Invalid key found -> set to default."
  867. );
  868. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  869. keyEnum = KeyEnum.major;
  870. log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
  871. }
  872. }
  873. const keyInstruction: KeyInstruction = new KeyInstruction(undefined, key, keyEnum);
  874. this.abstractInstructions.push([1, keyInstruction]);
  875. }
  876. if (node.element("time")) {
  877. const timeNode: IXmlElement = node.element("time");
  878. let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
  879. let timePrintObject: boolean = true;
  880. if (timeNode !== undefined && timeNode.hasAttributes) {
  881. const symbolAttribute: IXmlAttribute = timeNode.attribute("symbol");
  882. if (symbolAttribute) {
  883. if (symbolAttribute.value === "common") {
  884. symbolEnum = RhythmSymbolEnum.COMMON;
  885. } else if (symbolAttribute.value === "cut") {
  886. symbolEnum = RhythmSymbolEnum.CUT;
  887. }
  888. }
  889. const printObjectAttribute: IXmlAttribute = timeNode.attribute("print-object");
  890. if (printObjectAttribute) {
  891. if (printObjectAttribute.value === "no") {
  892. timePrintObject = false;
  893. }
  894. }
  895. }
  896. let num: number = 0;
  897. let denom: number = 0;
  898. const senzaMisura: boolean = (timeNode && timeNode.element("senza-misura") !== undefined);
  899. const timeList: IXmlElement[] = node.elements("time");
  900. const beatsList: IXmlElement[] = [];
  901. const typeList: IXmlElement[] = [];
  902. for (let idx: number = 0, len: number = timeList.length; idx < len; ++idx) {
  903. const xmlNode: IXmlElement = timeList[idx];
  904. beatsList.push.apply(beatsList, xmlNode.elements("beats"));
  905. typeList.push.apply(typeList, xmlNode.elements("beat-type"));
  906. }
  907. if (!senzaMisura) {
  908. try {
  909. if (beatsList !== undefined && beatsList.length > 0 && typeList !== undefined && beatsList.length === typeList.length) {
  910. const length: number = beatsList.length;
  911. const fractions: Fraction[] = new Array(length);
  912. let maxDenom: number = 0;
  913. for (let i: number = 0; i < length; i++) {
  914. const s: string = beatsList[i].value;
  915. let n: number = 0;
  916. let d: number = 0;
  917. if (s.indexOf("+") !== -1) {
  918. const numbers: string[] = s.split("+");
  919. for (let idx: number = 0, len: number = numbers.length; idx < len; ++idx) {
  920. n += parseInt(numbers[idx], 10);
  921. }
  922. } else {
  923. n = parseInt(s, 10);
  924. }
  925. d = parseInt(typeList[i].value, 10);
  926. maxDenom = Math.max(maxDenom, d);
  927. fractions[i] = new Fraction(n, d, 0, false);
  928. }
  929. for (let i: number = 0; i < length; i++) {
  930. if (fractions[i].Denominator === maxDenom) {
  931. num += fractions[i].Numerator;
  932. } else {
  933. num += (maxDenom / fractions[i].Denominator) * fractions[i].Numerator;
  934. }
  935. }
  936. denom = maxDenom;
  937. } else {
  938. num = parseInt(node.element("time").element("beats").value, 10);
  939. denom = parseInt(node.element("time").element("beat-type").value, 10);
  940. }
  941. } catch (ex) {
  942. errorMsg = ITextTranslation.translateText("ReaderErrorMessages/RhythmError", "Invalid rhythm found -> set to default.");
  943. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  944. num = 4;
  945. denom = 4;
  946. log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
  947. }
  948. const newRhythmInstruction: RhythmInstruction = new RhythmInstruction(
  949. new Fraction(num, denom, 0, false), symbolEnum
  950. );
  951. newRhythmInstruction.PrintObject = timePrintObject;
  952. this.abstractInstructions.push([1, newRhythmInstruction]);
  953. } else {
  954. this.abstractInstructions.push([1, new RhythmInstruction(new Fraction(4, 4, 0, false), RhythmSymbolEnum.NONE)]);
  955. }
  956. }
  957. }
  958. /**
  959. * Save the current AbstractInstructions to the corresponding [[StaffEntry]]s.
  960. * @param numberOfStaves
  961. * @param beginOfMeasure
  962. */
  963. private saveAbstractInstructionList(numberOfStaves: number, beginOfMeasure: boolean): void {
  964. for (let i: number = this.abstractInstructions.length - 1; i >= 0; i--) {
  965. const pair: [number, AbstractNotationInstruction] = this.abstractInstructions[i];
  966. const key: number = pair[0];
  967. const value: AbstractNotationInstruction = pair[1];
  968. if (value instanceof ClefInstruction) {
  969. const clefInstruction: ClefInstruction = <ClefInstruction>value;
  970. if (this.currentXmlMeasureIndex === 0 || (key <= this.activeClefs.length && clefInstruction !== this.activeClefs[key - 1])) {
  971. if (!beginOfMeasure && this.currentStaffEntry !== undefined && !this.currentStaffEntry.hasNotes() && key - 1
  972. === this.instrument.Staves.indexOf(this.currentStaffEntry.ParentStaff)) {
  973. const newClefInstruction: ClefInstruction = clefInstruction;
  974. newClefInstruction.Parent = this.currentStaffEntry;
  975. this.currentStaffEntry.removeFirstInstructionOfTypeClefInstruction();
  976. this.currentStaffEntry.Instructions.push(newClefInstruction);
  977. this.activeClefs[key - 1] = clefInstruction;
  978. this.abstractInstructions.splice(i, 1);
  979. } else if (beginOfMeasure) {
  980. let firstStaffEntry: SourceStaffEntry;
  981. if (this.currentMeasure) {
  982. const newClefInstruction: ClefInstruction = clefInstruction;
  983. const sseIndex: number = this.inSourceMeasureInstrumentIndex + key - 1;
  984. const firstSse: SourceStaffEntry = this.currentMeasure.FirstInstructionsStaffEntries[sseIndex];
  985. if (this.currentXmlMeasureIndex === 0) {
  986. if (!firstSse) {
  987. firstStaffEntry = new SourceStaffEntry(undefined, undefined);
  988. this.currentMeasure.FirstInstructionsStaffEntries[sseIndex] = firstStaffEntry;
  989. newClefInstruction.Parent = firstStaffEntry;
  990. firstStaffEntry.Instructions.push(newClefInstruction);
  991. this.activeClefsHaveBeenInitialized[key - 1] = true;
  992. } else if (this.currentMeasure.FirstInstructionsStaffEntries[sseIndex]
  993. !==
  994. undefined && !(firstSse.Instructions[0] instanceof ClefInstruction)) {
  995. firstStaffEntry = firstSse;
  996. newClefInstruction.Parent = firstStaffEntry;
  997. firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
  998. firstStaffEntry.Instructions.splice(0, 0, newClefInstruction);
  999. this.activeClefsHaveBeenInitialized[key - 1] = true;
  1000. } else {
  1001. const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
  1002. this.currentMeasure.LastInstructionsStaffEntries[sseIndex] = lastStaffEntry;
  1003. newClefInstruction.Parent = lastStaffEntry;
  1004. lastStaffEntry.Instructions.push(newClefInstruction);
  1005. }
  1006. } else if (!this.activeClefsHaveBeenInitialized[key - 1]) {
  1007. const first: SourceMeasure = this.musicSheet.SourceMeasures[0];
  1008. if (!first.FirstInstructionsStaffEntries[sseIndex]) {
  1009. firstStaffEntry = new SourceStaffEntry(undefined, undefined);
  1010. } else {
  1011. firstStaffEntry = first.FirstInstructionsStaffEntries[sseIndex];
  1012. firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
  1013. }
  1014. newClefInstruction.Parent = firstStaffEntry;
  1015. firstStaffEntry.Instructions.splice(0, 0, newClefInstruction);
  1016. this.activeClefsHaveBeenInitialized[key - 1] = true;
  1017. } else {
  1018. const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
  1019. this.previousMeasure.LastInstructionsStaffEntries[sseIndex] = lastStaffEntry;
  1020. newClefInstruction.Parent = lastStaffEntry;
  1021. lastStaffEntry.Instructions.push(newClefInstruction);
  1022. }
  1023. this.activeClefs[key - 1] = clefInstruction;
  1024. this.abstractInstructions.splice(i, 1);
  1025. }
  1026. }
  1027. } else if (key <= this.activeClefs.length && clefInstruction === this.activeClefs[key - 1]) {
  1028. this.abstractInstructions.splice(i, 1);
  1029. }
  1030. }
  1031. if (value instanceof KeyInstruction) {
  1032. const keyInstruction: KeyInstruction = <KeyInstruction>value;
  1033. if (!this.activeKey || this.activeKey.Key !== keyInstruction.Key) {
  1034. this.activeKey = keyInstruction;
  1035. this.abstractInstructions.splice(i, 1);
  1036. let sourceMeasure: SourceMeasure;
  1037. if (!this.activeKeyHasBeenInitialized) {
  1038. this.activeKeyHasBeenInitialized = true;
  1039. if (this.currentXmlMeasureIndex > 0) {
  1040. sourceMeasure = this.musicSheet.SourceMeasures[0];
  1041. } else {
  1042. sourceMeasure = this.currentMeasure;
  1043. }
  1044. } else {
  1045. sourceMeasure = this.currentMeasure;
  1046. }
  1047. if (sourceMeasure) {
  1048. for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + numberOfStaves; j++) {
  1049. const newKeyInstruction: KeyInstruction = keyInstruction;
  1050. if (!sourceMeasure.FirstInstructionsStaffEntries[j]) {
  1051. const firstStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
  1052. sourceMeasure.FirstInstructionsStaffEntries[j] = firstStaffEntry;
  1053. newKeyInstruction.Parent = firstStaffEntry;
  1054. firstStaffEntry.Instructions.push(newKeyInstruction);
  1055. } else {
  1056. const firstStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[j];
  1057. newKeyInstruction.Parent = firstStaffEntry;
  1058. firstStaffEntry.removeFirstInstructionOfTypeKeyInstruction();
  1059. if (firstStaffEntry.Instructions.length === 0) {
  1060. firstStaffEntry.Instructions.push(newKeyInstruction);
  1061. } else {
  1062. if (firstStaffEntry.Instructions[0] instanceof ClefInstruction) {
  1063. firstStaffEntry.Instructions.splice(1, 0, newKeyInstruction);
  1064. } else {
  1065. firstStaffEntry.Instructions.splice(0, 0, newKeyInstruction);
  1066. }
  1067. }
  1068. }
  1069. }
  1070. }
  1071. } else {
  1072. this.abstractInstructions.splice(i, 1);
  1073. }
  1074. }
  1075. if (value instanceof RhythmInstruction) {
  1076. const rhythmInstruction: RhythmInstruction = <RhythmInstruction>value;
  1077. if (!this.activeRhythm || this.activeRhythm !== rhythmInstruction) {
  1078. this.activeRhythm = rhythmInstruction;
  1079. this.abstractInstructions.splice(i, 1);
  1080. if (this.currentMeasure) {
  1081. for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + numberOfStaves; j++) {
  1082. const newRhythmInstruction: RhythmInstruction = rhythmInstruction;
  1083. let firstStaffEntry: SourceStaffEntry;
  1084. if (!this.currentMeasure.FirstInstructionsStaffEntries[j]) {
  1085. firstStaffEntry = new SourceStaffEntry(undefined, undefined);
  1086. this.currentMeasure.FirstInstructionsStaffEntries[j] = firstStaffEntry;
  1087. } else {
  1088. firstStaffEntry = this.currentMeasure.FirstInstructionsStaffEntries[j];
  1089. firstStaffEntry.removeFirstInstructionOfTypeRhythmInstruction();
  1090. }
  1091. newRhythmInstruction.Parent = firstStaffEntry;
  1092. firstStaffEntry.Instructions.push(newRhythmInstruction);
  1093. }
  1094. }
  1095. } else {
  1096. this.abstractInstructions.splice(i, 1);
  1097. }
  1098. }
  1099. }
  1100. }
  1101. /**
  1102. * Save any ClefInstruction given - exceptionally - at the end of the currentMeasure.
  1103. */
  1104. private saveClefInstructionAtEndOfMeasure(): void {
  1105. for (let i: number = this.abstractInstructions.length - 1; i >= 0; i--) {
  1106. const key: number = this.abstractInstructions[i][0];
  1107. const value: AbstractNotationInstruction = this.abstractInstructions[i][1];
  1108. if (value instanceof ClefInstruction) {
  1109. const clefInstruction: ClefInstruction = <ClefInstruction>value;
  1110. if (
  1111. (!this.activeClefs[key - 1]) ||
  1112. (clefInstruction.ClefType !== this.activeClefs[key - 1].ClefType || (
  1113. clefInstruction.ClefType === this.activeClefs[key - 1].ClefType &&
  1114. clefInstruction.Line !== this.activeClefs[key - 1].Line
  1115. ))) {
  1116. const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
  1117. this.currentMeasure.LastInstructionsStaffEntries[this.inSourceMeasureInstrumentIndex + key - 1] = lastStaffEntry;
  1118. const newClefInstruction: ClefInstruction = clefInstruction;
  1119. newClefInstruction.Parent = lastStaffEntry;
  1120. lastStaffEntry.Instructions.push(newClefInstruction);
  1121. this.activeClefs[key - 1] = clefInstruction;
  1122. this.abstractInstructions.splice(i, 1);
  1123. }
  1124. }
  1125. }
  1126. }
  1127. /**
  1128. * In case of a [[Tuplet]], read NoteDuration from type.
  1129. * @param xmlNode
  1130. * @returns {Fraction}
  1131. */
  1132. private getNoteDurationForTuplet(xmlNode: IXmlElement): Fraction {
  1133. let duration: Fraction = new Fraction(0, 1);
  1134. const typeDuration: Fraction = this.getNoteDurationFromTypeNode(xmlNode);
  1135. if (xmlNode.element("time-modification")) {
  1136. const time: IXmlElement = xmlNode.element("time-modification");
  1137. if (time) {
  1138. if (time.element("actual-notes") !== undefined && time.element("normal-notes")) {
  1139. const actualNotes: IXmlElement = time.element("actual-notes");
  1140. const normalNotes: IXmlElement = time.element("normal-notes");
  1141. if (actualNotes !== undefined && normalNotes) {
  1142. const actual: number = parseInt(actualNotes.value, 10);
  1143. const normal: number = parseInt(normalNotes.value, 10);
  1144. duration = new Fraction(normal * typeDuration.Numerator, actual * typeDuration.Denominator);
  1145. }
  1146. }
  1147. }
  1148. }
  1149. return duration;
  1150. }
  1151. private readExpressionStaffNumber(xmlNode: IXmlElement): number {
  1152. let directionStaffNumber: number = 1;
  1153. if (xmlNode.element("staff")) {
  1154. const staffNode: IXmlElement = xmlNode.element("staff");
  1155. if (staffNode) {
  1156. try {
  1157. directionStaffNumber = parseInt(staffNode.value, 10);
  1158. } catch (ex) {
  1159. const errorMsg: string = ITextTranslation.translateText(
  1160. "ReaderErrorMessages/ExpressionStaffError", "Invalid Expression staff number -> set to default."
  1161. );
  1162. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  1163. directionStaffNumber = 1;
  1164. log.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
  1165. }
  1166. }
  1167. }
  1168. return directionStaffNumber;
  1169. }
  1170. /**
  1171. * Calculate the divisions value from the type and duration of the first MeasureNote that makes sense
  1172. * (meaning itself hasn't any errors and it doesn't belong to a [[Tuplet]]).
  1173. *
  1174. * If all the MeasureNotes belong to a [[Tuplet]], then we read the next XmlMeasure (and so on...).
  1175. * If we have reached the end of the [[Instrument]] and still the divisions aren't set, we throw an exception
  1176. * @returns {number}
  1177. */
  1178. private readDivisionsFromNotes(): number {
  1179. let divisionsFromNote: number = 0;
  1180. let xmlMeasureIndex: number = this.currentXmlMeasureIndex;
  1181. let read: boolean = false;
  1182. while (!read) {
  1183. const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[xmlMeasureIndex].elements();
  1184. for (let idx: number = 0, len: number = xmlMeasureListArr.length; idx < len; ++idx) {
  1185. const xmlNode: IXmlElement = xmlMeasureListArr[idx];
  1186. if (xmlNode.name === "note" && !xmlNode.element("time-modification")) {
  1187. const durationNode: IXmlElement = xmlNode.element("duration");
  1188. const typeNode: IXmlElement = xmlNode.element("type");
  1189. if (durationNode !== undefined && typeNode) {
  1190. const type: string = typeNode.value;
  1191. let noteDuration: number = 0;
  1192. try {
  1193. noteDuration = parseInt(durationNode.value, 10);
  1194. } catch (ex) {
  1195. log.debug("InstrumentReader.readDivisionsFromNotes", ex);
  1196. continue;
  1197. }
  1198. switch (type) {
  1199. case "1024th":
  1200. divisionsFromNote = (noteDuration / 4) * 1024;
  1201. break;
  1202. case "512th":
  1203. divisionsFromNote = (noteDuration / 4) * 512;
  1204. break;
  1205. case "256th":
  1206. divisionsFromNote = (noteDuration / 4) * 256;
  1207. break;
  1208. case "128th":
  1209. divisionsFromNote = (noteDuration / 4) * 128;
  1210. break;
  1211. case "64th":
  1212. divisionsFromNote = (noteDuration / 4) * 64;
  1213. break;
  1214. case "32nd":
  1215. divisionsFromNote = (noteDuration / 4) * 32;
  1216. break;
  1217. case "16th":
  1218. divisionsFromNote = (noteDuration / 4) * 16;
  1219. break;
  1220. case "eighth":
  1221. divisionsFromNote = (noteDuration / 4) * 8;
  1222. break;
  1223. case "quarter":
  1224. divisionsFromNote = (noteDuration / 4) * 4;
  1225. break;
  1226. case "half":
  1227. divisionsFromNote = (noteDuration / 4) * 2;
  1228. break;
  1229. case "whole":
  1230. divisionsFromNote = (noteDuration / 4);
  1231. break;
  1232. case "breve":
  1233. divisionsFromNote = (noteDuration / 4) / 2;
  1234. break;
  1235. case "long":
  1236. divisionsFromNote = (noteDuration / 4) / 4;
  1237. break;
  1238. case "maxima":
  1239. divisionsFromNote = (noteDuration / 4) / 8;
  1240. break;
  1241. default:
  1242. break;
  1243. }
  1244. }
  1245. }
  1246. if (divisionsFromNote > 0) {
  1247. read = true;
  1248. break;
  1249. }
  1250. }
  1251. if (divisionsFromNote === 0) {
  1252. xmlMeasureIndex++;
  1253. if (xmlMeasureIndex === this.xmlMeasureList.length) {
  1254. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMEssages/DivisionsError", "Invalid divisions value at Instrument: ");
  1255. throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
  1256. }
  1257. }
  1258. }
  1259. return divisionsFromNote;
  1260. }
  1261. }