InstrumentReader.ts 61 KB

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