InstrumentReader.ts 56 KB

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