1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345 |
- import {Instrument} from "../Instrument";
- import {MusicSheet} from "../MusicSheet";
- import {VoiceGenerator} from "./VoiceGenerator";
- import {Staff} from "../VoiceData/Staff";
- import {SourceMeasure} from "../VoiceData/SourceMeasure";
- import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
- import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
- import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
- import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
- import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
- import {Fraction} from "../../Common/DataObjects/Fraction";
- import {IXmlElement} from "../../Common/FileIO/Xml";
- import {ITextTranslation} from "../Interfaces/ITextTranslation";
- import {MusicSheetReadingException} from "../Exceptions";
- 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";
- import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
- import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
- import {SlurReader} from "./MusicSymbolModules/SlurReader";
- import {StemDirectionType} from "../VoiceData/VoiceEntry";
- import {NoteType, NoteTypeHandler} from "../VoiceData/NoteType";
- import { SystemLinesEnumHelper } from "../Graphical/SystemLinesEnum";
- // import {Dictionary} from "typescript-collections";
- // FIXME: The following classes are missing
- //type ChordSymbolContainer = any;
- //type SlurReader = any;
- //type RepetitionInstructionReader = any;
- //declare class MusicSymbolModuleFactory {
- // public static createSlurReader(x: any): any;
- //}
- //
- //class MetronomeReader {
- // public static addMetronomeSettings(xmlNode: IXmlElement, musicSheet: MusicSheet): void { }
- // 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 {
- // }
- //}
- /**
- * An InstrumentReader is used during the reading phase to keep parsing new measures from the MusicXML file
- * with the readNextXmlMeasure method.
- */
- export class InstrumentReader {
- constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
- this.repetitionInstructionReader = repetitionInstructionReader;
- this.xmlMeasureList = xmlMeasureList;
- this.musicSheet = instrument.GetMusicSheet;
- this.instrument = instrument;
- this.activeClefs = new Array(instrument.Staves.length);
- this.activeClefsHaveBeenInitialized = new Array(instrument.Staves.length);
- for (let i: number = 0; i < instrument.Staves.length; i++) {
- this.activeClefsHaveBeenInitialized[i] = false;
- }
- this.createExpressionGenerators(instrument.Staves.length);
- this.slurReader = new SlurReader(this.musicSheet);
- }
- private repetitionInstructionReader: RepetitionInstructionReader;
- private xmlMeasureList: IXmlElement[];
- private musicSheet: MusicSheet;
- private slurReader: SlurReader;
- private instrument: Instrument;
- private voiceGeneratorsDict: { [n: number]: VoiceGenerator } = {};
- private staffMainVoiceGeneratorDict: { [staffId: number]: VoiceGenerator } = {};
- private inSourceMeasureInstrumentIndex: number;
- private divisions: number = 0;
- private currentMeasure: SourceMeasure;
- private previousMeasure: SourceMeasure;
- private currentClefNumber: number = 1;
- private currentXmlMeasureIndex: number = 0;
- private currentStaff: Staff;
- private currentStaffEntry: SourceStaffEntry;
- private activeClefs: ClefInstruction[];
- private activeKey: KeyInstruction;
- private activeRhythm: RhythmInstruction;
- private activeClefsHaveBeenInitialized: boolean[];
- private activeKeyHasBeenInitialized: boolean = false;
- private abstractInstructions: [number, AbstractNotationInstruction][] = [];
- private openChordSymbolContainers: ChordSymbolContainer[] = [];
- private expressionReaders: ExpressionReader[];
- private currentVoiceGenerator: VoiceGenerator;
- //private openSlurDict: { [n: number]: Slur; } = {};
- private maxTieNoteFraction: Fraction;
- public get ActiveKey(): KeyInstruction {
- return this.activeKey;
- }
- public get MaxTieNoteFraction(): Fraction {
- return this.maxTieNoteFraction;
- }
- public get ActiveRhythm(): RhythmInstruction {
- return this.activeRhythm;
- }
- public set ActiveRhythm(value: RhythmInstruction) {
- this.activeRhythm = value;
- }
- /**
- * Main CreateSheet: read the next XML Measure and save all data to the given [[SourceMeasure]].
- * @param currentMeasure
- * @param measureStartAbsoluteTimestamp - Using this instead of currentMeasure.AbsoluteTimestamp as it isn't set yet
- * @param octavePlusOne Software like Guitar Pro gives one octave too low, so we need to add one
- * @returns {boolean}
- */
- public readNextXmlMeasure(currentMeasure: SourceMeasure, measureStartAbsoluteTimestamp: Fraction, octavePlusOne: boolean): boolean {
- if (this.currentXmlMeasureIndex >= this.xmlMeasureList.length) {
- return false;
- }
- this.currentMeasure = currentMeasure;
- this.inSourceMeasureInstrumentIndex = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument);
- if (this.repetitionInstructionReader) {
- this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
- }
- let currentFraction: Fraction = new Fraction(0, 1);
- let previousFraction: Fraction = new Fraction(0, 1);
- let divisionsException: boolean = false;
- this.maxTieNoteFraction = new Fraction(0, 1);
- let lastNoteWasGrace: boolean = false;
- try {
- const measureNode: IXmlElement = this.xmlMeasureList[this.currentXmlMeasureIndex];
- const xmlMeasureListArr: IXmlElement[] = measureNode.elements();
- if (currentMeasure.Rules.UseXMLMeasureNumbers && !Number.isInteger(currentMeasure.MeasureNumberXML)) {
- const measureNumberXml: number = parseInt(measureNode.attribute("number")?.value, 10);
- if (Number.isInteger(measureNumberXml)) {
- currentMeasure.MeasureNumberXML = measureNumberXml;
- }
- }
- let previousNode: IXmlElement; // needs a null check when accessed because of node index 0!
- for (let xmlNodeIndex: number = 0; xmlNodeIndex < xmlMeasureListArr.length; xmlNodeIndex++) {
- const xmlNode: IXmlElement = xmlMeasureListArr[xmlNodeIndex];
- if (xmlNodeIndex > 0) {
- previousNode = xmlMeasureListArr[xmlNodeIndex - 1];
- }
- if (xmlNode.name === "print") {
- const newSystemAttr: IXmlAttribute = xmlNode.attribute("new-system");
- if (newSystemAttr?.value === "yes") {
- currentMeasure.printNewSystemXml = true;
- }
- const newPageAttr: IXmlAttribute = xmlNode.attribute("new-page");
- if (newPageAttr?.value === "yes") {
- currentMeasure.printNewPageXml = true;
- }
- } else if (xmlNode.name === "note") {
- let printObject: boolean = true;
- if (xmlNode.attribute("print-object")?.value === "no") {
- printObject = false; // note will not be rendered, but still parsed for Playback etc.
- // if (xmlNode.attribute("print-spacing")) {
- // 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;
- }
- }
- }
- this.currentStaff = this.instrument.Staves[noteStaff - 1];
- const isChord: boolean = xmlNode.element("chord") !== undefined;
- if (xmlNode.element("voice")) {
- const noteVoice: number = parseInt(xmlNode.element("voice").value, 10);
- this.currentVoiceGenerator = this.getOrCreateVoiceGenerator(noteVoice, noteStaff - 1);
- } else {
- if (!isChord || !this.currentVoiceGenerator) {
- this.currentVoiceGenerator = this.getOrCreateVoiceGenerator(1, noteStaff - 1);
- }
- }
- let noteDivisions: number = 0;
- let noteDuration: Fraction = new Fraction(0, 1);
- let normalNotes: number = 2;
- let typeDuration: Fraction = undefined;
- let isTuplet: boolean = false;
- if (xmlNode.element("duration")) {
- noteDivisions = parseInt(xmlNode.element("duration").value, 10);
- if (!isNaN(noteDivisions)) {
- noteDuration = new Fraction(noteDivisions, 4 * this.divisions);
- if (noteDivisions === 0) {
- noteDuration = this.getNoteDurationFromTypeNode(xmlNode);
- } else {
- typeDuration = this.getNoteDurationFromTypeNode(xmlNode);
- }
- if (xmlNode.element("time-modification")) {
- noteDuration = this.getNoteDurationForTuplet(xmlNode);
- const time: IXmlElement = xmlNode.element("time-modification");
- if (time?.element("normal-notes")) {
- normalNotes = parseInt(time.element("normal-notes").value, 10);
- }
- isTuplet = true;
- }
- } else {
- const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid Note Duration.");
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
- continue;
- }
- }
- 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 isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
- let graceNoteSlash: boolean = false;
- let graceSlur: boolean = false;
- if (isGraceNote) {
- const graceNode: IXmlElement = xmlNode.element("grace");
- if (graceNode && graceNode.attributes()) {
- if (graceNode.attribute("slash")) {
- const slash: string = graceNode.attribute("slash").value;
- if (slash === "yes") {
- graceNoteSlash = true;
- }
- }
- }
- 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.
- }
- }
- }
- // 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);
- }
- // 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);
- }
- }
- // check Tremolo
- let tremoloStrokes: number = 0;
- let vibratoStrokes: boolean = false;
- 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 vibratoNode: IXmlElement = ornamentsNode.element("wavy-line");
- if (vibratoNode !== undefined) {
- const vibratoType: Attr = vibratoNode.attribute("type");
- if (vibratoType && vibratoType.value === "start") {
- vibratoStrokes = true;
- }
- }
- }
- }
- // 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);
- }
- }
- 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();
- }
- this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry(
- musicTimestamp,
- this.inSourceMeasureInstrumentIndex + noteStaff - 1,
- this.currentStaff
- ).staffEntry;
- //log.info("currentStaffEntry", this.currentStaffEntry, this.currentMeasure.VerticalSourceStaffEntryContainers.length);
- if (!this.currentVoiceGenerator.hasVoiceEntry()
- || (!isChord && !isGraceNote && !lastNoteWasGrace)
- || (isGraceNote && !lastNoteWasGrace)
- || (isGraceNote && !isChord)
- || (!isGraceNote && lastNoteWasGrace)
- ) {
- this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, !restNote && !isGraceNote,
- isGraceNote, graceNoteSlash, graceSlur);
- }
- if (!isGraceNote && !isChord) {
- previousFraction = currentFraction.clone();
- currentFraction.Add(noteDuration);
- }
- if (
- isChord &&
- this.currentStaffEntry !== undefined &&
- this.currentStaffEntry.ParentStaff !== this.currentStaff
- ) {
- this.currentStaffEntry = this.currentVoiceGenerator.checkForStaffEntryLink(
- this.inSourceMeasureInstrumentIndex + noteStaff - 1, this.currentStaff, this.currentStaffEntry, this.currentMeasure
- );
- }
- const beginOfMeasure: boolean = (
- this.currentStaffEntry !== undefined &&
- this.currentStaffEntry.Timestamp !== undefined &&
- 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 = [];
- }
- if (this.activeRhythm) {
- // (*) this.musicSheet.SheetPlaybackSetting.Rhythm = this.activeRhythm.Rhythm;
- }
- if (!isTuplet && !isGraceNote) {
- noteDuration = new Fraction(noteDivisions, 4 * this.divisions);
- }
- this.currentVoiceGenerator.read(
- xmlNode, noteDuration, typeDuration, noteTypeXml, normalNotes, restNote,
- this.currentStaffEntry, this.currentMeasure,
- measureStartAbsoluteTimestamp,
- this.maxTieNoteFraction, isChord, octavePlusOne,
- printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml,
- vibratoStrokes
- );
- // notationsNode created further up for multiple checks
- if (notationsNode !== undefined && notationsNode.element("dynamics")) {
- const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
- if (expressionReader) {
- expressionReader.readExpressionParameters(
- xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
- );
- expressionReader.read(
- xmlNode, this.currentMeasure, previousFraction
- );
- }
- }
- 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);
- 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)) {
- this.saveClefInstructionAtEndOfMeasure();
- }
- const staffDetailsNode: IXmlElement = xmlNode.element("staff-details");
- if (staffDetailsNode) {
- 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;
- }
- } 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));
- } else if (xmlNode.name === "backup") {
- const backFraction: number = parseInt(xmlNode.element("duration").value, 10);
- currentFraction.Sub(new Fraction(backFraction, 4 * this.divisions));
- if (currentFraction.IsNegative()) {
- currentFraction = new Fraction(0, 1);
- }
- previousFraction.Sub(new Fraction(backFraction, 4 * this.divisions));
- if (previousFraction.IsNegative()) {
- previousFraction = new Fraction(0, 1);
- }
- } else if (xmlNode.name === "direction") {
- const directionTypeNode: IXmlElement = xmlNode.element("direction-type");
- // (*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
- let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
- if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm) {
- relativePositionInMeasure /= this.activeRhythm.Rhythm.RealValue;
- }
- let handeled: boolean = false;
- if (this.repetitionInstructionReader) {
- handeled = this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols( directionTypeNode,
- relativePositionInMeasure);
- }
- if (!handeled) {
- let expressionReader: ExpressionReader = this.expressionReaders[0];
- const staffIndex: number = this.readExpressionStaffNumber(xmlNode) - 1;
- if (staffIndex < this.expressionReaders.length) {
- expressionReader = this.expressionReaders[staffIndex];
- }
- if (expressionReader) {
- if (directionTypeNode.element("octave-shift")) {
- expressionReader.readExpressionParameters(
- xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, true
- );
- expressionReader.addOctaveShift(xmlNode, this.currentMeasure, previousFraction.clone());
- }
- expressionReader.readExpressionParameters(
- xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
- );
- expressionReader.read(xmlNode, this.currentMeasure, currentFraction);
- }
- }
- } else if (xmlNode.name === "barline") {
- if (this.repetitionInstructionReader) {
- const measureEndsSystem: boolean = this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode);
- if (measureEndsSystem) {
- this.currentMeasure.HasEndLine = true;
- }
- }
- const location: IXmlAttribute = xmlNode.attribute("location");
- const isEndingBarline: boolean = (xmlNodeIndex === xmlMeasureListArr.length - 1);
- if (isEndingBarline || (location && location.value === "right")) {
- const stringValue: string = xmlNode.element("bar-style")?.value;
- // TODO apparently we didn't anticipate bar-style not existing (the ? above was missing). how to handle?
- if (stringValue) {
- this.currentMeasure.endingBarStyleXml = stringValue;
- this.currentMeasure.endingBarStyleEnum = SystemLinesEnumHelper.xmlBarlineStyleToSystemLinesEnum(stringValue);
- }
- }
- // TODO do we need to process bars with left location too?
- } else if (xmlNode.name === "sound") {
- // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
- try {
- if (xmlNode.attribute("tempo")) { // can be null, not just undefined!
- const tempo: number = parseFloat(xmlNode.attribute("tempo").value);
- // should set the PlaybackSettings only at first Measure
- if (this.currentXmlMeasureIndex === 0) {
- this.musicSheet.DefaultStartTempoInBpm = tempo;
- this.musicSheet.HasBPMInfo = true;
- }
- }
- } catch (e) {
- log.debug("InstrumentReader.readTempoInstruction", e);
- }
- } else if (xmlNode.name === "harmony") {
- // new chord, could be second chord on same staffentry/note
- this.openChordSymbolContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey));
- }
- }
- for (const j in this.voiceGeneratorsDict) {
- if (this.voiceGeneratorsDict.hasOwnProperty(j)) {
- const voiceGenerator: VoiceGenerator = this.voiceGeneratorsDict[j];
- voiceGenerator.checkForOpenBeam();
- }
- }
- if (this.currentXmlMeasureIndex === this.xmlMeasureList.length - 1) {
- for (let i: number = 0; i < this.instrument.Staves.length; i++) {
- if (!this.activeClefsHaveBeenInitialized[i]) {
- this.createDefaultClefInstruction(this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument) + i);
- }
- }
- if (!this.activeKeyHasBeenInitialized) {
- this.createDefaultKeyInstruction();
- }
- for (let i: number = 0; i < this.expressionReaders.length; i++) {
- const reader: ExpressionReader = this.expressionReaders[i];
- if (reader) {
- reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
- }
- }
- }
- // if this is the first measure and no BPM info found, we set it to 120
- // next measures will automatically inherit that value
- if (!this.musicSheet.HasBPMInfo) {
- this.currentMeasure.TempoInBPM = 120;
- } else if (currentMeasure.TempoInBPM === 0 && this.previousMeasure) {
- this.currentMeasure.TempoInBPM = this.previousMeasure.TempoInBPM;
- }
- } catch (e) {
- if (divisionsException) {
- throw new MusicSheetReadingException(e.Message);
- }
- const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MeasureError", "Error while reading Measure.");
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- log.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
- }
- this.previousMeasure = this.currentMeasure;
- this.currentXmlMeasureIndex += 1;
- return true;
- }
- /** 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
- */
- public parseXmlColor(xmlColorString: string): string {
- if (!xmlColorString) {
- return undefined;
- }
- if (xmlColorString.length === 7) { // #RGB
- return xmlColorString;
- } else if (xmlColorString.length === 9) { // #ARGB
- return "#" + xmlColorString.substr(3); // cut away alpha channel
- } else {
- return undefined; // invalid xml color
- }
- }
- public doCalculationsAfterDurationHasBeenSet(): void {
- for (const j in this.voiceGeneratorsDict) {
- if (this.voiceGeneratorsDict.hasOwnProperty(j)) {
- this.voiceGeneratorsDict[j].checkOpenTies();
- }
- }
- }
- /**
- * Get or create the passing [[VoiceGenerator]].
- * @param voiceId
- * @param staffId
- * @returns {VoiceGenerator}
- */
- private getOrCreateVoiceGenerator(voiceId: number, staffId: number): VoiceGenerator {
- const staff: Staff = this.instrument.Staves[staffId];
- let voiceGenerator: VoiceGenerator = this.voiceGeneratorsDict[voiceId];
- if (voiceGenerator) {
- if (staff.Voices.indexOf(voiceGenerator.GetVoice) === -1) {
- staff.Voices.push(voiceGenerator.GetVoice);
- }
- } else {
- const mainVoiceGenerator: VoiceGenerator = this.staffMainVoiceGeneratorDict[staffId];
- if (mainVoiceGenerator) {
- voiceGenerator = new VoiceGenerator(this.instrument, voiceId, this.slurReader, mainVoiceGenerator.GetVoice);
- staff.Voices.push(voiceGenerator.GetVoice);
- this.voiceGeneratorsDict[voiceId] = voiceGenerator;
- } else {
- voiceGenerator = new VoiceGenerator(this.instrument, voiceId, this.slurReader);
- staff.Voices.push(voiceGenerator.GetVoice);
- this.voiceGeneratorsDict[voiceId] = voiceGenerator;
- this.staffMainVoiceGeneratorDict[staffId] = voiceGenerator;
- }
- }
- return voiceGenerator;
- }
- private createExpressionGenerators(numberOfStaves: number): void {
- this.expressionReaders = new Array(numberOfStaves);
- for (let i: number = 0; i < numberOfStaves; i++) {
- this.expressionReaders[i] = new ExpressionReader(this.musicSheet, this.instrument, i + 1);
- }
- }
- /**
- * Create the default [[ClefInstruction]] for the given staff index.
- * @param staffIndex
- */
- private createDefaultClefInstruction(staffIndex: number): void {
- let first: SourceMeasure;
- if (this.musicSheet.SourceMeasures.length > 0) {
- first = this.musicSheet.SourceMeasures[0];
- } else {
- first = this.currentMeasure;
- }
- const clefInstruction: ClefInstruction = new ClefInstruction(ClefEnum.G, 0, 2);
- let firstStaffEntry: SourceStaffEntry;
- if (!first.FirstInstructionsStaffEntries[staffIndex]) {
- firstStaffEntry = new SourceStaffEntry(undefined, undefined);
- first.FirstInstructionsStaffEntries[staffIndex] = firstStaffEntry;
- } else {
- firstStaffEntry = first.FirstInstructionsStaffEntries[staffIndex];
- firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
- }
- clefInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.splice(0, 0, clefInstruction);
- }
- /**
- * Create the default [[KeyInstruction]] in case no [[KeyInstruction]] is given in the whole [[Instrument]].
- */
- private createDefaultKeyInstruction(): void {
- let first: SourceMeasure;
- if (this.musicSheet.SourceMeasures.length > 0) {
- first = this.musicSheet.SourceMeasures[0];
- } else {
- first = this.currentMeasure;
- }
- const keyInstruction: KeyInstruction = new KeyInstruction(undefined, 0, KeyEnum.major);
- for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + this.instrument.Staves.length; j++) {
- if (!first.FirstInstructionsStaffEntries[j]) {
- const firstStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
- first.FirstInstructionsStaffEntries[j] = firstStaffEntry;
- keyInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.push(keyInstruction);
- } else {
- const firstStaffEntry: SourceStaffEntry = first.FirstInstructionsStaffEntries[j];
- keyInstruction.Parent = firstStaffEntry;
- firstStaffEntry.removeFirstInstructionOfTypeKeyInstruction();
- if (firstStaffEntry.Instructions[0] instanceof ClefInstruction) {
- firstStaffEntry.Instructions.splice(1, 0, keyInstruction);
- } else {
- firstStaffEntry.Instructions.splice(0, 0, keyInstruction);
- }
- }
- }
- }
- /**
- * Check if the given attributesNode is at the begin of a XmlMeasure.
- * @param parentNode
- * @param attributesNode
- * @returns {boolean}
- */
- private isAttributesNodeAtBeginOfMeasure(parentNode: IXmlElement, attributesNode: IXmlElement): boolean {
- const children: IXmlElement[] = parentNode.elements();
- const attributesNodeIndex: number = children.indexOf(attributesNode); // FIXME | 0
- if (attributesNodeIndex > 0 && children[attributesNodeIndex - 1].name === "backup") {
- return true;
- }
- let firstNoteNodeIndex: number = -1;
- for (let i: number = 0; i < children.length; i++) {
- if (children[i].name === "note") {
- firstNoteNodeIndex = i;
- break;
- }
- }
- return (attributesNodeIndex < firstNoteNodeIndex && firstNoteNodeIndex > 0) || (firstNoteNodeIndex < 0);
- }
- /**
- * Check if the given attributesNode is at the end of a XmlMeasure.
- * @param parentNode
- * @param attributesNode
- * @returns {boolean}
- */
- private isAttributesNodeAtEndOfMeasure(parentNode: IXmlElement, attributesNode: IXmlElement): boolean {
- const childs: IXmlElement[] = parentNode.elements().slice(); // slice=arrayCopy
- let attributesNodeIndex: number = 0;
- for (let i: number = 0; i < childs.length; i++) {
- if (childs[i] === attributesNode) {
- attributesNodeIndex = i;
- break;
- }
- }
- let nextNoteNodeIndex: number = 0;
- for (let i: number = attributesNodeIndex; i < childs.length; i++) {
- if (childs[i].name === "note") {
- nextNoteNodeIndex = i;
- break;
- }
- }
- return attributesNodeIndex > nextNoteNodeIndex;
- }
- /**
- * Called only when no noteDuration is given in XML.
- * @param xmlNode
- * @returns {Fraction}
- */
- private getNoteDurationFromTypeNode(xmlNode: IXmlElement): Fraction {
- const typeNode: IXmlElement = xmlNode.element("type");
- if (typeNode) {
- const type: string = typeNode.value;
- return NoteTypeHandler.getNoteDurationFromType(type);
- }
- return new Fraction(0, 4 * this.divisions);
- }
- /**
- * Add (the three basic) Notation Instructions to a list
- * @param attrNode
- * @param guitarPro
- */
- private addAbstractInstruction(attrNode: IXmlElement, guitarPro: boolean, previousNode: IXmlElement): void {
- if (attrNode.element("divisions")) {
- if (attrNode.elements().length === 1) {
- return;
- }
- }
- const transposeNode: IXmlElement = attrNode.element("transpose");
- if (transposeNode) {
- const chromaticNode: IXmlElement = transposeNode.element("chromatic");
- if (chromaticNode) {
- this.instrument.PlaybackTranspose = parseInt(chromaticNode.value, 10);
- }
- }
- const clefList: IXmlElement[] = attrNode.elements("clef");
- let errorMsg: string;
- if (clefList.length > 0) {
- for (let idx: number = 0, len: number = clefList.length; idx < len; ++idx) {
- const nodeList: IXmlElement = clefList[idx];
- let clefEnum: ClefEnum = ClefEnum.G;
- let line: number = 2;
- let staffNumber: number = 1;
- let clefOctaveOffset: number = 0;
- const lineNode: IXmlElement = nodeList.element("line");
- if (lineNode) {
- try {
- line = parseInt(lineNode.value, 10);
- } catch (ex) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/ClefLineError",
- "Invalid clef line given -> using default clef line."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- line = 2;
- log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
- }
- }
- const signNode: IXmlElement = nodeList.element("sign");
- if (signNode) {
- try {
- clefEnum = ClefEnum[signNode.value];
- if (!ClefInstruction.isSupportedClef(clefEnum)) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/ClefError",
- "Unsupported clef found -> using default clef."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- clefEnum = ClefEnum.G;
- line = 2;
- }
- if (clefEnum === ClefEnum.TAB) {
- clefOctaveOffset = -1;
- }
- } catch (e) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/ClefError",
- "Invalid clef found -> using default clef."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- clefEnum = ClefEnum.G;
- line = 2;
- log.debug("InstrumentReader.addAbstractInstruction", errorMsg, e);
- }
- }
- const clefOctaveNode: IXmlElement = nodeList.element("clef-octave-change");
- if (clefOctaveNode) {
- try {
- clefOctaveOffset = parseInt(clefOctaveNode.value, 10);
- } catch (e) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/ClefOctaveError",
- "Invalid clef octave found -> using default clef octave."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- clefOctaveOffset = 0;
- }
- }
- if (nodeList.hasAttributes && nodeList.attributes()[0].name === "number") {
- try {
- staffNumber = parseInt(nodeList.attributes()[0].value, 10);
- if (staffNumber > this.currentClefNumber) {
- staffNumber = this.currentClefNumber;
- }
- this.currentClefNumber = staffNumber + 1;
- } catch (err) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/ClefError",
- "Invalid clef found -> using default clef."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- staffNumber = 1;
- this.currentClefNumber = staffNumber + 1;
- }
- }
- // TODO problem: in saveAbstractInstructionList, this is always saved in this.currentStaffEntry.
- // so when there's a <forward> or <backup> instruction in <attributes> (which is unfortunate encoding), this gets misplaced.
- // so for now we skip it.
- const skipClefInstruction: boolean = previousNode?.name === "forward";
- // || previousNode?.name === "backup") && // necessary for clef at beginning of measure/system,
- // see sample test_staverepetitions_coda_etc.musicxml, where the bass clef was placed over a previous treble clef
- if (!skipClefInstruction) {
- const clefInstruction: ClefInstruction = new ClefInstruction(clefEnum, clefOctaveOffset, line);
- this.abstractInstructions.push([staffNumber, clefInstruction]);
- }
- }
- }
- if (attrNode.element("key") !== undefined && this.instrument.MidiInstrumentId !== MidiInstrument.Percussion) {
- let key: number = 0;
- const keyNode: IXmlElement = attrNode.element("key").element("fifths");
- if (keyNode) {
- try {
- key = parseInt(keyNode.value, 10);
- } catch (ex) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/KeyError",
- "Invalid key found -> set to default."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- key = 0;
- log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
- }
- }
- let keyEnum: KeyEnum = KeyEnum.none;
- let modeNode: IXmlElement = attrNode.element("key");
- if (modeNode) {
- modeNode = modeNode.element("mode");
- }
- if (modeNode) {
- try {
- keyEnum = KeyEnum[modeNode.value];
- } catch (ex) {
- errorMsg = ITextTranslation.translateText(
- "ReaderErrorMessages/KeyError",
- "Invalid key found -> set to default."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- keyEnum = KeyEnum.major;
- log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
- }
- }
- const keyInstruction: KeyInstruction = new KeyInstruction(undefined, key, keyEnum);
- this.abstractInstructions.push([1, keyInstruction]);
- }
- if (attrNode.element("time")) {
- const timeNode: IXmlElement = attrNode.element("time");
- let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
- let timePrintObject: boolean = true;
- if (timeNode !== undefined && timeNode.hasAttributes) {
- const symbolAttribute: IXmlAttribute = timeNode.attribute("symbol");
- if (symbolAttribute) {
- if (symbolAttribute.value === "common") {
- symbolEnum = RhythmSymbolEnum.COMMON;
- } else if (symbolAttribute.value === "cut") {
- symbolEnum = RhythmSymbolEnum.CUT;
- }
- }
- const printObjectAttribute: IXmlAttribute = timeNode.attribute("print-object");
- if (printObjectAttribute) {
- if (printObjectAttribute.value === "no") {
- timePrintObject = false;
- }
- }
- }
- let num: number = 0;
- let denom: number = 0;
- const senzaMisura: boolean = (timeNode && timeNode.element("senza-misura") !== undefined);
- const timeList: IXmlElement[] = attrNode.elements("time");
- const beatsList: IXmlElement[] = [];
- const typeList: IXmlElement[] = [];
- for (let idx: number = 0, len: number = timeList.length; idx < len; ++idx) {
- const xmlNode: IXmlElement = timeList[idx];
- beatsList.push.apply(beatsList, xmlNode.elements("beats"));
- typeList.push.apply(typeList, xmlNode.elements("beat-type"));
- }
- if (!senzaMisura) {
- try {
- if (beatsList !== undefined && beatsList.length > 0 && typeList !== undefined && beatsList.length === typeList.length) {
- const length: number = beatsList.length;
- const fractions: Fraction[] = new Array(length);
- let maxDenom: number = 0;
- for (let i: number = 0; i < length; i++) {
- const s: string = beatsList[i].value;
- let n: number = 0;
- let d: number = 0;
- if (s.indexOf("+") !== -1) {
- const numbers: string[] = s.split("+");
- for (let idx: number = 0, len: number = numbers.length; idx < len; ++idx) {
- n += parseInt(numbers[idx], 10);
- }
- } else {
- n = parseInt(s, 10);
- }
- d = parseInt(typeList[i].value, 10);
- maxDenom = Math.max(maxDenom, d);
- fractions[i] = new Fraction(n, d, 0, false);
- }
- for (let i: number = 0; i < length; i++) {
- if (fractions[i].Denominator === maxDenom) {
- num += fractions[i].Numerator;
- } else {
- num += (maxDenom / fractions[i].Denominator) * fractions[i].Numerator;
- }
- }
- denom = maxDenom;
- } else {
- num = parseInt(attrNode.element("time").element("beats").value, 10);
- denom = parseInt(attrNode.element("time").element("beat-type").value, 10);
- }
- } catch (ex) {
- errorMsg = ITextTranslation.translateText("ReaderErrorMessages/RhythmError", "Invalid rhythm found -> set to default.");
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- num = 4;
- denom = 4;
- log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
- }
- const newRhythmInstruction: RhythmInstruction = new RhythmInstruction(
- new Fraction(num, denom, 0, false), symbolEnum
- );
- newRhythmInstruction.PrintObject = timePrintObject;
- this.abstractInstructions.push([1, newRhythmInstruction]);
- } else {
- this.abstractInstructions.push([1, new RhythmInstruction(new Fraction(4, 4, 0, false), RhythmSymbolEnum.NONE)]);
- }
- }
- }
- /**
- * Save the current AbstractInstructions to the corresponding [[StaffEntry]]s.
- * @param numberOfStaves
- * @param beginOfMeasure
- */
- private saveAbstractInstructionList(numberOfStaves: number, beginOfMeasure: boolean): void {
- for (let i: number = this.abstractInstructions.length - 1; i >= 0; i--) {
- const pair: [number, AbstractNotationInstruction] = this.abstractInstructions[i];
- const key: number = pair[0];
- const value: AbstractNotationInstruction = pair[1];
- if (value instanceof ClefInstruction) {
- const clefInstruction: ClefInstruction = <ClefInstruction>value;
- if (this.currentXmlMeasureIndex === 0 || (key <= this.activeClefs.length && clefInstruction !== this.activeClefs[key - 1])) {
- if (!beginOfMeasure && this.currentStaffEntry !== undefined && !this.currentStaffEntry.hasNotes() && key - 1
- === this.instrument.Staves.indexOf(this.currentStaffEntry.ParentStaff)) {
- const newClefInstruction: ClefInstruction = clefInstruction;
- newClefInstruction.Parent = this.currentStaffEntry;
- this.currentStaffEntry.removeFirstInstructionOfTypeClefInstruction();
- this.currentStaffEntry.Instructions.push(newClefInstruction);
- this.activeClefs[key - 1] = clefInstruction;
- this.abstractInstructions.splice(i, 1);
- } else if (beginOfMeasure) {
- let firstStaffEntry: SourceStaffEntry;
- if (this.currentMeasure) {
- const newClefInstruction: ClefInstruction = clefInstruction;
- const sseIndex: number = this.inSourceMeasureInstrumentIndex + key - 1;
- const firstSse: SourceStaffEntry = this.currentMeasure.FirstInstructionsStaffEntries[sseIndex];
- if (this.currentXmlMeasureIndex === 0) {
- if (!firstSse) {
- firstStaffEntry = new SourceStaffEntry(undefined, undefined);
- this.currentMeasure.FirstInstructionsStaffEntries[sseIndex] = firstStaffEntry;
- newClefInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.push(newClefInstruction);
- this.activeClefsHaveBeenInitialized[key - 1] = true;
- } else if (this.currentMeasure.FirstInstructionsStaffEntries[sseIndex]
- !==
- undefined && !(firstSse.Instructions[0] instanceof ClefInstruction)) {
- firstStaffEntry = firstSse;
- newClefInstruction.Parent = firstStaffEntry;
- firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
- firstStaffEntry.Instructions.splice(0, 0, newClefInstruction);
- this.activeClefsHaveBeenInitialized[key - 1] = true;
- } else {
- const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
- this.currentMeasure.LastInstructionsStaffEntries[sseIndex] = lastStaffEntry;
- newClefInstruction.Parent = lastStaffEntry;
- lastStaffEntry.Instructions.push(newClefInstruction);
- }
- } else if (!this.activeClefsHaveBeenInitialized[key - 1]) {
- const first: SourceMeasure = this.musicSheet.SourceMeasures[0];
- if (!first.FirstInstructionsStaffEntries[sseIndex]) {
- firstStaffEntry = new SourceStaffEntry(undefined, undefined);
- } else {
- firstStaffEntry = first.FirstInstructionsStaffEntries[sseIndex];
- firstStaffEntry.removeFirstInstructionOfTypeClefInstruction();
- }
- newClefInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.splice(0, 0, newClefInstruction);
- this.activeClefsHaveBeenInitialized[key - 1] = true;
- } else {
- const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
- this.previousMeasure.LastInstructionsStaffEntries[sseIndex] = lastStaffEntry;
- newClefInstruction.Parent = lastStaffEntry;
- lastStaffEntry.Instructions.push(newClefInstruction);
- }
- this.activeClefs[key - 1] = clefInstruction;
- this.abstractInstructions.splice(i, 1);
- }
- }
- } else if (key <= this.activeClefs.length && clefInstruction === this.activeClefs[key - 1]) {
- this.abstractInstructions.splice(i, 1);
- }
- }
- if (value instanceof KeyInstruction) {
- const keyInstruction: KeyInstruction = <KeyInstruction>value;
- if (!this.activeKey || this.activeKey.Key !== keyInstruction.Key) {
- this.activeKey = keyInstruction;
- this.abstractInstructions.splice(i, 1);
- let sourceMeasure: SourceMeasure;
- if (!this.activeKeyHasBeenInitialized) {
- this.activeKeyHasBeenInitialized = true;
- if (this.currentXmlMeasureIndex > 0) {
- sourceMeasure = this.musicSheet.SourceMeasures[0];
- } else {
- sourceMeasure = this.currentMeasure;
- }
- } else {
- sourceMeasure = this.currentMeasure;
- }
- if (sourceMeasure) {
- for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + numberOfStaves; j++) {
- const newKeyInstruction: KeyInstruction = keyInstruction;
- if (!sourceMeasure.FirstInstructionsStaffEntries[j]) {
- const firstStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
- sourceMeasure.FirstInstructionsStaffEntries[j] = firstStaffEntry;
- newKeyInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.push(newKeyInstruction);
- } else {
- const firstStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[j];
- newKeyInstruction.Parent = firstStaffEntry;
- firstStaffEntry.removeFirstInstructionOfTypeKeyInstruction();
- if (firstStaffEntry.Instructions.length === 0) {
- firstStaffEntry.Instructions.push(newKeyInstruction);
- } else {
- if (firstStaffEntry.Instructions[0] instanceof ClefInstruction) {
- firstStaffEntry.Instructions.splice(1, 0, newKeyInstruction);
- } else {
- firstStaffEntry.Instructions.splice(0, 0, newKeyInstruction);
- }
- }
- }
- }
- }
- } else {
- this.abstractInstructions.splice(i, 1);
- }
- }
- if (value instanceof RhythmInstruction) {
- const rhythmInstruction: RhythmInstruction = <RhythmInstruction>value;
- if (!this.activeRhythm || this.activeRhythm !== rhythmInstruction) {
- this.activeRhythm = rhythmInstruction;
- this.abstractInstructions.splice(i, 1);
- if (this.currentMeasure) {
- for (let j: number = this.inSourceMeasureInstrumentIndex; j < this.inSourceMeasureInstrumentIndex + numberOfStaves; j++) {
- const newRhythmInstruction: RhythmInstruction = rhythmInstruction;
- let firstStaffEntry: SourceStaffEntry;
- if (!this.currentMeasure.FirstInstructionsStaffEntries[j]) {
- firstStaffEntry = new SourceStaffEntry(undefined, undefined);
- this.currentMeasure.FirstInstructionsStaffEntries[j] = firstStaffEntry;
- } else {
- firstStaffEntry = this.currentMeasure.FirstInstructionsStaffEntries[j];
- firstStaffEntry.removeFirstInstructionOfTypeRhythmInstruction();
- }
- newRhythmInstruction.Parent = firstStaffEntry;
- firstStaffEntry.Instructions.push(newRhythmInstruction);
- }
- }
- } else {
- this.abstractInstructions.splice(i, 1);
- }
- }
- }
- }
- /**
- * Save any ClefInstruction given - exceptionally - at the end of the currentMeasure.
- */
- private saveClefInstructionAtEndOfMeasure(): void {
- for (let i: number = this.abstractInstructions.length - 1; i >= 0; i--) {
- const key: number = this.abstractInstructions[i][0];
- const value: AbstractNotationInstruction = this.abstractInstructions[i][1];
- if (value instanceof ClefInstruction) {
- const clefInstruction: ClefInstruction = <ClefInstruction>value;
- if (
- (!this.activeClefs[key - 1]) ||
- (clefInstruction.ClefType !== this.activeClefs[key - 1].ClefType || (
- clefInstruction.ClefType === this.activeClefs[key - 1].ClefType &&
- clefInstruction.Line !== this.activeClefs[key - 1].Line
- ))) {
- const lastStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, undefined);
- this.currentMeasure.LastInstructionsStaffEntries[this.inSourceMeasureInstrumentIndex + key - 1] = lastStaffEntry;
- const newClefInstruction: ClefInstruction = clefInstruction;
- newClefInstruction.Parent = lastStaffEntry;
- lastStaffEntry.Instructions.push(newClefInstruction);
- this.activeClefs[key - 1] = clefInstruction;
- this.abstractInstructions.splice(i, 1);
- }
- }
- }
- }
- /**
- * In case of a [[Tuplet]], read NoteDuration from type.
- * @param xmlNode
- * @returns {Fraction}
- */
- private getNoteDurationForTuplet(xmlNode: IXmlElement): Fraction {
- let duration: Fraction = new Fraction(0, 1);
- const typeDuration: Fraction = this.getNoteDurationFromTypeNode(xmlNode);
- if (xmlNode.element("time-modification")) {
- const time: IXmlElement = xmlNode.element("time-modification");
- if (time) {
- if (time.element("actual-notes") !== undefined && time.element("normal-notes")) {
- const actualNotes: IXmlElement = time.element("actual-notes");
- const normalNotes: IXmlElement = time.element("normal-notes");
- if (actualNotes !== undefined && normalNotes) {
- const actual: number = parseInt(actualNotes.value, 10);
- const normal: number = parseInt(normalNotes.value, 10);
- duration = new Fraction(normal * typeDuration.Numerator, actual * typeDuration.Denominator);
- }
- }
- }
- }
- return duration;
- }
- private readExpressionStaffNumber(xmlNode: IXmlElement): number {
- let directionStaffNumber: number = 1;
- if (xmlNode.element("staff")) {
- const staffNode: IXmlElement = xmlNode.element("staff");
- if (staffNode) {
- try {
- directionStaffNumber = parseInt(staffNode.value, 10);
- } catch (ex) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/ExpressionStaffError", "Invalid Expression staff number -> set to default."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- directionStaffNumber = 1;
- log.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
- }
- }
- }
- return directionStaffNumber;
- }
- /**
- * Calculate the divisions value from the type and duration of the first MeasureNote that makes sense
- * (meaning itself hasn't any errors and it doesn't belong to a [[Tuplet]]).
- *
- * If all the MeasureNotes belong to a [[Tuplet]], then we read the next XmlMeasure (and so on...).
- * If we have reached the end of the [[Instrument]] and still the divisions aren't set, we throw an exception
- * @returns {number}
- */
- private readDivisionsFromNotes(): number {
- let divisionsFromNote: number = 0;
- let xmlMeasureIndex: number = this.currentXmlMeasureIndex;
- let read: boolean = false;
- while (!read) {
- const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[xmlMeasureIndex].elements();
- for (let idx: number = 0, len: number = xmlMeasureListArr.length; idx < len; ++idx) {
- const xmlNode: IXmlElement = xmlMeasureListArr[idx];
- if (xmlNode.name === "note" && !xmlNode.element("time-modification")) {
- const durationNode: IXmlElement = xmlNode.element("duration");
- const typeNode: IXmlElement = xmlNode.element("type");
- if (durationNode !== undefined && typeNode) {
- const type: string = typeNode.value;
- let noteDuration: number = 0;
- try {
- noteDuration = parseInt(durationNode.value, 10);
- } catch (ex) {
- log.debug("InstrumentReader.readDivisionsFromNotes", ex);
- continue;
- }
- switch (type) {
- case "1024th":
- divisionsFromNote = (noteDuration / 4) * 1024;
- break;
- case "512th":
- divisionsFromNote = (noteDuration / 4) * 512;
- break;
- case "256th":
- divisionsFromNote = (noteDuration / 4) * 256;
- break;
- case "128th":
- divisionsFromNote = (noteDuration / 4) * 128;
- break;
- case "64th":
- divisionsFromNote = (noteDuration / 4) * 64;
- break;
- case "32nd":
- divisionsFromNote = (noteDuration / 4) * 32;
- break;
- case "16th":
- divisionsFromNote = (noteDuration / 4) * 16;
- break;
- case "eighth":
- divisionsFromNote = (noteDuration / 4) * 8;
- break;
- case "quarter":
- divisionsFromNote = (noteDuration / 4) * 4;
- break;
- case "half":
- divisionsFromNote = (noteDuration / 4) * 2;
- break;
- case "whole":
- divisionsFromNote = (noteDuration / 4);
- break;
- case "breve":
- divisionsFromNote = (noteDuration / 4) / 2;
- break;
- case "long":
- divisionsFromNote = (noteDuration / 4) / 4;
- break;
- case "maxima":
- divisionsFromNote = (noteDuration / 4) / 8;
- break;
- default:
- break;
- }
- }
- }
- if (divisionsFromNote > 0) {
- read = true;
- break;
- }
- }
- if (divisionsFromNote === 0) {
- xmlMeasureIndex++;
- if (xmlMeasureIndex === this.xmlMeasureList.length) {
- const errorMsg: string = ITextTranslation.translateText("ReaderErrorMEssages/DivisionsError", "Invalid divisions value at Instrument: ");
- throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
- }
- }
- }
- return divisionsFromNote;
- }
- }
|