| 
					
				 | 
			
			
				@@ -16,7 +16,6 @@ import {ClefEnum} from "../VoiceData/Instructions/ClefInstruction"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import {KeyEnum} from "../VoiceData/Instructions/KeyInstruction"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import {IXmlAttribute} from "../../Common/FileIO/Xml"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import log from "loglevel"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader"; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -42,11 +41,6 @@ import { ReaderPluginManager } from "./ReaderPluginManager"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 //  public static readMetronomeInstructions(xmlNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 //  public static readTempoInstruction(soundNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 //} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-// 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//class ChordSymbolReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//  public static readChordSymbol(xmlNode:IXmlElement, musicSheet:MusicSheet, activeKey:any): void { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-//} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 /** 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -93,7 +87,8 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private activeClefsHaveBeenInitialized: boolean[]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private activeKeyHasBeenInitialized: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private abstractInstructions: [number, AbstractNotationInstruction, Fraction][] = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  private openChordSymbolContainers: ChordSymbolContainer[] = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  //TODO: remove line below if it is not needed anymore? 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  //private openChordSymbolContainers: ChordSymbolContainer[] = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private expressionReaders: ExpressionReader[]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private currentVoiceGenerator: VoiceGenerator; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   //private openSlurDict: { [n: number]: Slur; } = {}; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -163,6 +158,86 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           if (newPageAttr?.value === "yes") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             currentMeasure.printNewPageXml = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } else if (xmlNode.name === "attributes") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const divisionsNode: IXmlElement = xmlNode.element("divisions"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          if (divisionsNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.divisions = parseInt(divisionsNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (isNaN(this.divisions)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  "Invalid divisions value at Instrument: "); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.divisions = this.readDivisionsFromNotes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (this.divisions > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                divisionsException = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                throw new MusicSheetReadingException(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              !xmlNode.element("divisions") && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.divisions === 0 && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.currentXmlMeasureIndex === 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", "Invalid divisions value at Instrument: "); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.divisions = this.readDivisionsFromNotes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (this.divisions > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              divisionsException = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              throw new MusicSheetReadingException(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.addAbstractInstruction(xmlNode, octavePlusOne, previousNode, currentFraction.clone()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          if (currentFraction.Equals(new Fraction(0, 1)) && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.isAttributesNodeAtBeginOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.saveAbstractInstructionList(this.instrument.Staves.length, true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode, currentFraction)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.saveClefInstructionAtEndOfMeasure(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const staffDetailsNodes: IXmlElement[] = xmlNode.elements("staff-details"); // there can be multiple, even if redundant. see #1041 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          for (const staffDetailsNode of staffDetailsNodes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (staffLinesNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              let staffNumber: number = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              const staffNumberAttr: Attr = staffDetailsNode.attribute("number"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (staffNumberAttr) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                staffNumber = parseInt(staffNumberAttr.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // check multi measure rest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const measureStyle: IXmlElement = xmlNode.element("measure-style"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          if (measureStyle) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            const multipleRest: IXmlElement = measureStyle.element("multiple-rest"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (multipleRest) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // TODO: save multirest per staff info a dictionary, to display a partial multirest if multirest values across staffs differ. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              //   this makes the code bulkier though, and for now we only draw multirests if the staffs have the same multirest lengths. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // if (!currentMeasure.multipleRestMeasuresPerStaff) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              //   currentMeasure.multipleRestMeasuresPerStaff = new Dictionary<number, number>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              const multipleRestValueXml: string = multipleRest.value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              let multipleRestNumber: number = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                multipleRestNumber = Number.parseInt(multipleRestValueXml, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (currentMeasure.multipleRestMeasures !== undefined && multipleRestNumber !== currentMeasure.multipleRestMeasures) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  // different multi-rest values in same measure for different staffs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  currentMeasure.multipleRestMeasures = 0; // for now, ignore multirest here. TODO: take minimum 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  // currentMeasure.multipleRestMeasuresPerStaff.setValue(this.currentStaff?.Id, multipleRestNumber); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  //   issue: currentStaff can be undefined for first measure 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  currentMeasure.multipleRestMeasures = multipleRestNumber; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  this.currentMultirestStartMeasure = currentMeasure; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  this.followingMultirestMeasures = multipleRestNumber + 1; // will be decremented at the start of the loop 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } catch (e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                console.log("multirest parse error: " + e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } else if (xmlNode.name === "note") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           let printObject: boolean = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           if (xmlNode.attribute("print-object")?.value === "no") { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -171,16 +246,7 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               //   if (xmlNode.attribute("print-spacing").value === "yes" { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               //     // TODO give spacing for invisible notes even when not displayed. might be hard with Vexflow formatting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let noteStaff: number = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (this.instrument.Staves.length > 1) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (xmlNode.element("staff")) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              noteStaff = parseInt(xmlNode.element("staff").value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (isNaN(noteStaff)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                log.debug("InstrumentReader.readNextXmlMeasure.get staff number"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                noteStaff = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const noteStaff: number = this.getNoteStaff(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.currentStaff = this.instrument.Staves[noteStaff - 1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           const isChord: boolean = xmlNode.element("chord") !== undefined; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -225,7 +291,7 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           const restNote: boolean = xmlNode.element("rest") !== undefined; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           //log.info("New note found!", noteDivisions, noteDuration.toString(), restNote); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const notationsNode: IXmlElement = xmlNode.element("notations"); // used for multiple checks further on 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const notationsNode: IXmlElement = xmlNode.combinedElement("notations"); // select all notation nodes 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           let graceNoteSlash: boolean = false; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -243,59 +309,17 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             noteDuration = this.getNoteDurationFromTypeNode(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const notationNode: IXmlElement = xmlNode.element("notations"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (notationNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (notationNode.element("slur")) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                graceSlur = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // grace slurs could be non-binary, but VexFlow.GraceNoteGroup modifier system is currently only boolean for slurs. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (notationsNode && notationsNode.element("slur")) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              graceSlur = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // grace slurs could be non-binary, but VexFlow.GraceNoteGroup modifier system is currently only boolean for slurs. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           // check for cue note 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let isCueNote: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const cueNode: IXmlElement = xmlNode.element("cue"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (cueNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            isCueNote = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          // alternative: check for <type size="cue"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const typeNode: IXmlElement = xmlNode.element("type"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let noteTypeXml: NoteType = NoteType.UNDEFINED; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (typeNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const sizeAttr: Attr = typeNode.attribute("size"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (sizeAttr?.value === "cue") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              isCueNote = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            noteTypeXml = NoteTypeHandler.StringToNoteType(typeNode.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const [isCueNote, noteTypeXml] = this.getCueNoteAndNoteTypeXml(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           // check stem element 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let stemColorXml: string; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const stemNode: IXmlElement = xmlNode.element("stem"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (stemNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            switch (stemNode.value) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              case "down": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stemDirectionXml = StemDirectionType.Down; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              case "up": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stemDirectionXml = StemDirectionType.Up; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              case "double": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stemDirectionXml = StemDirectionType.Double; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              case "none": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stemDirectionXml = StemDirectionType.None; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stemDirectionXml = StemDirectionType.Undefined; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const stemColorAttr: Attr = stemNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (stemColorAttr) { // can be null, maybe also undefined 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              stemColorXml = this.parseXmlColor(stemColorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const [stemDirectionXml, stemColorXml, noteheadColorXml] = this.getStemDirectionAndColors(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           // check Tremolo 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           let tremoloStrokes: number = 0; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -303,64 +327,12 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           if (notationsNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             const ornamentsNode: IXmlElement = notationsNode.element("ornaments"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if (ornamentsNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const tremoloNode: IXmlElement = ornamentsNode.element("tremolo"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (tremoloNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                const tremoloType: Attr = tremoloNode.attribute("type"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (tremoloType && tremoloType.value === "single") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  if (tremoloStrokesGiven > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    tremoloStrokes = tremoloStrokesGiven; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const wavyLineNodes: IXmlElement[] = ornamentsNode.elements("wavy-line"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (wavyLineNodes !== undefined) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                /* As mentioned elsewhere, the wavy-line is technically an ornament element, but is specified and behaves 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                   very much like a continuous expression, so makes more sense to interpret as an expression in our model. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               for (const wavyLineNode of wavyLineNodes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  if (expressionReader) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    //Read placement from the wavy line node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  expressionReader.readExpressionParameters( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    wavyLineNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  expressionReader.addWavyLine( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    wavyLineNode, this.currentMeasure, currentFraction, previousFraction 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          // check notehead/color 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let noteheadColorXml: string; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const noteheadNode: IXmlElement = xmlNode.element("notehead"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (noteheadNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const colorAttr: Attr = noteheadNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (colorAttr) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              noteheadColorXml = this.parseXmlColor(colorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              tremoloStrokes = this.getTremoloStrokes(ornamentsNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.getWavyLines(ornamentsNode, xmlNode, currentFraction, previousFraction); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let noteColorXml: string; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const noteColorAttr: Attr = xmlNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (noteColorAttr) { // can be undefined 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            noteColorXml = this.parseXmlColor(noteColorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (!noteheadColorXml) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              noteheadColorXml = noteColorXml; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (!stemColorXml) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              stemColorXml = noteColorXml; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          let musicTimestamp: Fraction = currentFraction.clone(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (isChord) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            musicTimestamp = previousFraction.clone(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const musicTimestamp: Fraction = isChord ? previousFraction.clone() : currentFraction.clone(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             musicTimestamp, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             this.inSourceMeasureInstrumentIndex + noteStaff - 1, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -375,7 +347,7 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             || (!isGraceNote && lastNoteWasGrace) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, this.activeKey, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                                        this.ActiveRhythm, isGraceNote, graceNoteSlash, graceSlur); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.ActiveRhythm, isGraceNote, graceNoteSlash, graceSlur); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           if (!isGraceNote && !isChord) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             previousFraction = currentFraction.clone(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -396,11 +368,15 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             this.currentStaffEntry.Timestamp.Equals(new Fraction(0, 1)) && !this.currentStaffEntry.hasNotes() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.saveAbstractInstructionList(this.instrument.Staves.length, beginOfMeasure); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (this.openChordSymbolContainers.length !== 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.currentStaffEntry.ChordContainers = this.openChordSymbolContainers; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // TODO handle multiple chords on one note/staffentry 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.openChordSymbolContainers = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // this if block handles harmony/chords on the next note/staffentry element, so it assumes that a 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   harmony is given before the staff entry, but when a harmony is given after a staff entry element with a backup node, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   it is put on the next note/staffentry and the last chord item is never parsed at all. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   see PR #1342 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // if (this.openChordSymbolContainers.length !== 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   this.currentStaffEntry.ChordContainers = this.openChordSymbolContainers; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   // TODO handle multiple chords on one note/staffentry 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          //   this.openChordSymbolContainers = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           if (this.activeRhythm) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             // (*) this.musicSheet.SheetPlaybackSetting.Rhythm = this.activeRhythm.Rhythm; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -430,87 +406,6 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           lastNoteWasGrace = isGraceNote; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } else if (xmlNode.name === "attributes") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const divisionsNode: IXmlElement = xmlNode.element("divisions"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (divisionsNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.divisions = parseInt(divisionsNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (isNaN(this.divisions)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                                                      "Invalid divisions value at Instrument: "); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              this.divisions = this.readDivisionsFromNotes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (this.divisions > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                divisionsException = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                throw new MusicSheetReadingException(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            !xmlNode.element("divisions") && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.divisions === 0 && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.currentXmlMeasureIndex === 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", "Invalid divisions value at Instrument: "); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.divisions = this.readDivisionsFromNotes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (this.divisions > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              divisionsException = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              throw new MusicSheetReadingException(errorMsg + this.instrument.Name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          this.addAbstractInstruction(xmlNode, octavePlusOne, previousNode, currentFraction.clone()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (currentFraction.Equals(new Fraction(0, 1)) && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.isAttributesNodeAtBeginOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.saveAbstractInstructionList(this.instrument.Staves.length, true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode, currentFraction)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.saveClefInstructionAtEndOfMeasure(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const staffDetailsNodes: IXmlElement[] = xmlNode.elements("staff-details"); // there can be multiple, even if redundant. see #1041 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          for (const staffDetailsNode of staffDetailsNodes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (staffLinesNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              let staffNumber: number = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const staffNumberAttr: Attr = staffDetailsNode.attribute("number"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (staffNumberAttr) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                staffNumber = parseInt(staffNumberAttr.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          // check multi measure rest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          const measureStyle: IXmlElement = xmlNode.element("measure-style"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if (measureStyle) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            const multipleRest: IXmlElement = measureStyle.element("multiple-rest"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            if (multipleRest) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // TODO: save multirest per staff info a dictionary, to display a partial multirest if multirest values across staffs differ. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              //   this makes the code bulkier though, and for now we only draw multirests if the staffs have the same multirest lengths. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // if (!currentMeasure.multipleRestMeasuresPerStaff) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              //   currentMeasure.multipleRestMeasuresPerStaff = new Dictionary<number, number>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const multipleRestValueXml: string = multipleRest.value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              let multipleRestNumber: number = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                multipleRestNumber = Number.parseInt(multipleRestValueXml, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (currentMeasure.multipleRestMeasures !== undefined && multipleRestNumber !== currentMeasure.multipleRestMeasures) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  // different multi-rest values in same measure for different staffs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  currentMeasure.multipleRestMeasures = 0; // for now, ignore multirest here. TODO: take minimum 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  // currentMeasure.multipleRestMeasuresPerStaff.setValue(this.currentStaff?.Id, multipleRestNumber); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  //   issue: currentStaff can be undefined for first measure 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  currentMeasure.multipleRestMeasures = multipleRestNumber; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  this.currentMultirestStartMeasure = currentMeasure; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  this.followingMultirestMeasures = multipleRestNumber + 1; // will be decremented at the start of the loop 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } catch (e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                console.log("multirest parse error: " + e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } else if (xmlNode.name === "forward") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           const forFraction: number = parseInt(xmlNode.element("duration").value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           currentFraction.Add(new Fraction(forFraction, 4 * this.divisions)); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -597,8 +492,13 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             log.debug("InstrumentReader.readTempoInstruction", e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } else if (xmlNode.name === "harmony") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const noteStaff: number = this.getNoteStaff(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.currentStaff = this.instrument.Staves[noteStaff - 1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           // new chord, could be second chord on same staffentry/note 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          this.openChordSymbolContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          const musicTimestamp: Fraction = currentFraction.clone(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              musicTimestamp, this.inSourceMeasureInstrumentIndex + noteStaff - 1, this.currentStaff).staffEntry; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.currentStaffEntry.ChordContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       for (const j in this.voiceGeneratorsDict) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -652,6 +552,32 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     return true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getStemDirectionAndColors(xmlNode: IXmlElement): [StemDirectionType, string, string] { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let stemColorXml: string; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const stemNode: IXmlElement = xmlNode.element("stem"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (stemNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      stemDirectionXml = this.getStemDirectionType(stemNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const stemColorAttr: Attr = stemNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (stemColorAttr) { // can be null, maybe also undefined 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        stemColorXml = this.parseXmlColor(stemColorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // check notehead/color 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let noteheadColorXml: string = this.getNoteHeadColorXml(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const noteColorXml: string = this.getNoteColorXml(xmlNode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (noteColorXml && !noteheadColorXml) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      noteheadColorXml = noteColorXml; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (noteColorXml && !stemColorXml) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      stemColorXml = noteColorXml; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return [stemDirectionXml, stemColorXml, noteheadColorXml]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   /** Parse a color in XML format. Can be #ARGB or #RGB format, colors as byte hex values. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    *  @return color in Vexflow format #[A]RGB or undefined for invalid xmlColorString 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    */ 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1442,4 +1368,108 @@ export class InstrumentReader { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     return divisionsFromNote; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getCueNoteAndNoteTypeXml(xmlNode: IXmlElement): [boolean, NoteType] { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const cueNode: IXmlElement = xmlNode.element("cue"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let isCueNote: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (cueNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      isCueNote = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const typeNode: IXmlElement = xmlNode.element("type"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let noteTypeXml: NoteType = NoteType.UNDEFINED; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (typeNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const sizeAttr: Attr = typeNode.attribute("size"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (sizeAttr?.value === "cue") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        isCueNote = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      noteTypeXml = NoteTypeHandler.StringToNoteType(typeNode.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return [isCueNote, noteTypeXml]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getStemDirectionType(stemNode: IXmlElement): StemDirectionType { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    switch (stemNode.value) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      case "down": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return StemDirectionType.Down; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      case "up": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return StemDirectionType.Up; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      case "double": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return StemDirectionType.Double; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      case "none": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return StemDirectionType.None; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      default: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return StemDirectionType.Undefined; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getNoteHeadColorXml(xmlNode: IXmlElement): string | null { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const noteheadNode: IXmlElement = xmlNode.element("notehead"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (noteheadNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const colorAttr: Attr = noteheadNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (colorAttr) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return this.parseXmlColor(colorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getNoteColorXml(xmlNode: IXmlElement): string | null { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const noteColorAttr: Attr = xmlNode.attribute("color"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (noteColorAttr) { // can be undefined 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return this.parseXmlColor(noteColorAttr.value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getTremoloStrokes(ornamentsNode: IXmlElement): number { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const tremoloNode: IXmlElement = ornamentsNode.element("tremolo"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (tremoloNode) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const tremoloType: Attr = tremoloNode.attribute("type"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (tremoloType && tremoloType.value === "single") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (tremoloStrokesGiven > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          return tremoloStrokesGiven; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getWavyLines(ornamentsNode: IXmlElement, xmlNode: IXmlElement, currentFraction: Fraction, previousFraction: Fraction): void { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const wavyLineNodes: IXmlElement[] = ornamentsNode.elements("wavy-line"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (!wavyLineNodes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /* As mentioned elsewhere, the wavy-line is technically an ornament element, but is specified and behaves 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        very much like a continuous expression, so makes more sense to interpret as an expression in our model. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for (const wavyLineNode of wavyLineNodes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (expressionReader) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        //Read placement from the wavy line node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        expressionReader.readExpressionParameters( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          wavyLineNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        expressionReader.addWavyLine( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          wavyLineNode, this.currentMeasure, currentFraction, previousFraction 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private getNoteStaff(xmlNode: IXmlElement): number { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let noteStaff: number = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (this.instrument.Staves.length > 1) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (xmlNode.element("staff")) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        noteStaff = parseInt(xmlNode.element("staff").value, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (isNaN(noteStaff)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          log.debug("InstrumentReader.readNextXmlMeasure.get staff number"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          noteStaff = 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return noteStaff; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |