InstrumentReader.ts 60 KB

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