InstrumentReader.ts 67 KB

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