InstrumentReader.ts 56 KB

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