1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093 |
- import { LinkedVoice } from "../VoiceData/LinkedVoice";
- import { Voice } from "../VoiceData/Voice";
- import { MusicSheet } from "../MusicSheet";
- import { VoiceEntry, StemDirectionType } from "../VoiceData/VoiceEntry";
- import { Note } from "../VoiceData/Note";
- import { SourceMeasure } from "../VoiceData/SourceMeasure";
- import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
- import { Beam } from "../VoiceData/Beam";
- import { Tie } from "../VoiceData/Tie";
- import { TieTypes } from "../../Common/Enums/";
- import { Tuplet } from "../VoiceData/Tuplet";
- import { Fraction } from "../../Common/DataObjects/Fraction";
- import { IXmlElement } from "../../Common/FileIO/Xml";
- import { ITextTranslation } from "../Interfaces/ITextTranslation";
- import { LyricsReader } from "../ScoreIO/MusicSymbolModules/LyricsReader";
- import { MusicSheetReadingException } from "../Exceptions";
- import { AccidentalEnum } from "../../Common/DataObjects/Pitch";
- import { NoteEnum } from "../../Common/DataObjects/Pitch";
- import { Staff } from "../VoiceData/Staff";
- import { StaffEntryLink } from "../VoiceData/StaffEntryLink";
- import { VerticalSourceStaffEntryContainer } from "../VoiceData/VerticalSourceStaffEntryContainer";
- import log from "loglevel";
- import { Pitch } from "../../Common/DataObjects/Pitch";
- import { IXmlAttribute } from "../../Common/FileIO/Xml";
- import { CollectionUtil } from "../../Util/CollectionUtil";
- import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader";
- import { SlurReader } from "./MusicSymbolModules/SlurReader";
- import { Notehead } from "../VoiceData/Notehead";
- import { Arpeggio, ArpeggioType } from "../VoiceData/Arpeggio";
- import { NoteType, NoteTypeHandler } from "../VoiceData/NoteType";
- import { TabNote } from "../VoiceData/TabNote";
- import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
- import { KeyInstruction, RhythmInstruction } from "../VoiceData/Instructions";
- import { ReaderPluginManager } from "./ReaderPluginManager";
- import { Instrument } from "../Instrument";
- export class VoiceGenerator {
- constructor(pluginManager: ReaderPluginManager, staff: Staff, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
- this.staff = staff;
- this.instrument = staff.ParentInstrument;
- this.musicSheet = this.instrument.GetMusicSheet;
- this.slurReader = slurReader;
- this.pluginManager = pluginManager;
- if (mainVoice) {
- this.voice = new LinkedVoice(this.instrument, voiceId, mainVoice);
- } else {
- this.voice = new Voice(this.instrument, voiceId);
- }
- this.instrument.Voices.push(this.voice); // apparently necessary for cursor.next(), for "cursor with hidden instrument" test
- this.staff.Voices.push(this.voice);
- this.lyricsReader = new LyricsReader(this.musicSheet);
- this.articulationReader = new ArticulationReader(this.musicSheet.Rules);
- }
- public pluginManager: ReaderPluginManager; // currently only used in audio player
- private slurReader: SlurReader;
- private lyricsReader: LyricsReader;
- private articulationReader: ArticulationReader;
- private musicSheet: MusicSheet;
- private voice: Voice;
- private currentVoiceEntry: VoiceEntry;
- private currentNormalVoiceEntry: VoiceEntry;
- private currentNote: Note;
- private activeKey: KeyInstruction;
- private activeRhythm: RhythmInstruction;
- private currentMeasure: SourceMeasure;
- private currentStaffEntry: SourceStaffEntry;
- private staff: Staff;
- private instrument: Instrument;
- // private lastBeamTag: string = "";
- private openBeams: Beam[] = []; // works like a stack, with push and pop
- private beamNumberOffset: number = 0;
- private openTieDict: { [_: number]: Tie } = {};
- private currentOctaveShift: number = 0;
- private tupletDict: { [_: number]: Tuplet } = {};
- private openTupletNumber: number = 0;
- private currMeasureVoiceEntries: VoiceEntry[] = [];
- private graceVoiceEntriesTempList: VoiceEntry[] = [];
- public get GetVoice(): Voice {
- return this.voice;
- }
- public get OctaveShift(): number {
- return this.currentOctaveShift;
- }
- public set OctaveShift(value: number) {
- this.currentOctaveShift = value;
- }
- /**
- * Create new [[VoiceEntry]], add it to given [[SourceStaffEntry]] and if given so, to [[Voice]].
- * @param musicTimestamp
- * @param parentStaffEntry
- * @param addToVoice
- * @param isGrace States whether the new VoiceEntry (only) has grace notes
- */
- public createVoiceEntry(musicTimestamp: Fraction, parentStaffEntry: SourceStaffEntry, activeKey: KeyInstruction, activeRhythm: RhythmInstruction,
- isGrace: boolean = false, hasGraceSlash: boolean = false, graceSlur: boolean = false): void {
- this.activeKey = activeKey;
- this.activeRhythm = activeRhythm;
- this.currentVoiceEntry = new VoiceEntry(Fraction.createFromFraction(musicTimestamp), this.voice, parentStaffEntry, true, isGrace, hasGraceSlash, graceSlur);
- if (isGrace) {
- // if grace voice entry, add to temp list
- this.graceVoiceEntriesTempList.push(this.currentVoiceEntry);
- } else {
- // remember new main VE -> needed for grace voice entries
- this.currentNormalVoiceEntry = this.currentVoiceEntry;
- // add ve to list of voice entries of this measure:
- this.currMeasureVoiceEntries.push(this.currentNormalVoiceEntry);
- // add grace VE temp list to normal voice entry:
- if (this.graceVoiceEntriesTempList.length > 0) {
- this.currentVoiceEntry.GraceVoiceEntriesBefore = this.graceVoiceEntriesTempList;
- this.graceVoiceEntriesTempList = [];
- }
- }
- }
- public finalizeReadingMeasure(): void {
- // store floating grace notes, if any:
- if (this.graceVoiceEntriesTempList.length > 0 &&
- this.currentNormalVoiceEntry !== undefined) {
- this.currentNormalVoiceEntry.GraceVoiceEntriesAfter.concat(this.graceVoiceEntriesTempList);
- }
- this.graceVoiceEntriesTempList = [];
- this.pluginManager.processVoiceMeasureReadPlugins(this.currMeasureVoiceEntries, this.activeKey, this.activeRhythm);
- this.currMeasureVoiceEntries.length = 0;
- // possibly (eventuell) close an already opened beam:
- if (this.openBeams.length > 1) {
- this.handleOpenBeam();
- }
- }
- /**
- * Create [[Note]]s and handle Lyrics, Articulations, Beams, Ties, Slurs, Tuplets.
- * @param noteNode
- * @param noteDuration
- * @param divisions
- * @param restNote
- * @param parentStaffEntry
- * @param parentMeasure
- * @param measureStartAbsoluteTimestamp
- * @param maxTieNoteFraction
- * @param chord
- * @param octavePlusOne Software like Guitar Pro gives one octave too low, so we need to add one
- * @param printObject whether the note should be rendered (true) or invisible (false)
- * @returns {Note}
- */
- public read(noteNode: IXmlElement, noteDuration: Fraction, typeDuration: Fraction, noteTypeXml: NoteType, normalNotes: number, restNote: boolean,
- parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
- measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, octavePlusOne: boolean,
- printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
- stemColorXml: string, noteheadColorXml: string): Note {
- this.currentStaffEntry = parentStaffEntry;
- this.currentMeasure = parentMeasure;
- //log.debug("read called:", restNote);
- try {
- this.currentNote = restNote
- ? this.addRestNote(noteNode.element("rest"), noteDuration, noteTypeXml, normalNotes, printObject, isCueNote, noteheadColorXml)
- : this.addSingleNote(noteNode, noteDuration, noteTypeXml, typeDuration, normalNotes, chord, octavePlusOne,
- printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml);
- // read lyrics
- const lyricElements: IXmlElement[] = noteNode.elements("lyric");
- if (this.lyricsReader !== undefined && lyricElements) {
- this.lyricsReader.addLyricEntry(lyricElements, this.currentVoiceEntry);
- this.voice.Parent.HasLyrics = true;
- }
- let hasTupletCommand: boolean = false;
- const notationNode: IXmlElement = noteNode.element("notations");
- if (notationNode) {
- // read articulations
- if (this.articulationReader) {
- this.readArticulations(notationNode, this.currentVoiceEntry, this.currentNote);
- }
- // read slurs
- const slurElements: IXmlElement[] = notationNode.elements("slur");
- if (this.slurReader !== undefined &&
- slurElements.length > 0 &&
- !this.currentNote.ParentVoiceEntry.IsGrace) {
- this.slurReader.addSlur(slurElements, this.currentNote);
- }
- // read Tuplets
- const tupletElements: IXmlElement[] = notationNode.elements("tuplet");
- if (tupletElements.length > 0) {
- this.openTupletNumber = this.addTuplet(noteNode, tupletElements);
- hasTupletCommand = true;
- }
- // check for Arpeggios
- const arpeggioNode: IXmlElement = notationNode.element("arpeggiate");
- if (arpeggioNode !== undefined) {
- let currentArpeggio: Arpeggio;
- if (this.currentVoiceEntry.Arpeggio) { // add note to existing Arpeggio
- currentArpeggio = this.currentVoiceEntry.Arpeggio;
- } else { // create new Arpeggio
- let arpeggioAlreadyExists: boolean = false;
- for (const voiceEntry of this.currentStaffEntry.VoiceEntries) {
- if (voiceEntry.Arpeggio) {
- arpeggioAlreadyExists = true;
- currentArpeggio = voiceEntry.Arpeggio;
- // TODO handle multiple arpeggios across multiple voices at same timestamp
- // this.currentVoiceEntry.Arpeggio = currentArpeggio; // register the arpeggio in the current voice entry as well?
- // but then we duplicate information, and may have to take care not to render it multiple times
- // we already have an arpeggio in another voice, at the current timestamp. add the notes there.
- break;
- }
- }
- if (!arpeggioAlreadyExists) {
- let arpeggioType: ArpeggioType = ArpeggioType.ARPEGGIO_DIRECTIONLESS;
- const directionAttr: Attr = arpeggioNode.attribute("direction");
- if (directionAttr) {
- switch (directionAttr.value) {
- case "up":
- arpeggioType = ArpeggioType.ROLL_UP;
- break;
- case "down":
- arpeggioType = ArpeggioType.ROLL_DOWN;
- break;
- default:
- arpeggioType = ArpeggioType.ARPEGGIO_DIRECTIONLESS;
- }
- }
- currentArpeggio = new Arpeggio(this.currentVoiceEntry, arpeggioType);
- this.currentVoiceEntry.Arpeggio = currentArpeggio;
- }
- }
- currentArpeggio.addNote(this.currentNote);
- }
- // check for Ties - must be the last check
- const tiedNodeList: IXmlElement[] = notationNode.elements("tied");
- if (tiedNodeList.length > 0) {
- this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.SIMPLE);
- }
- //check for slides, they are the same as Ties but with a different connection
- const slideNodeList: IXmlElement[] = notationNode.elements("slide");
- if (slideNodeList.length > 0) {
- this.addTie(slideNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.SLIDE);
- }
- //check for guitar specific symbols:
- const technicalNode: IXmlElement = notationNode.element("technical");
- if (technicalNode) {
- const hammerNodeList: IXmlElement[] = technicalNode.elements("hammer-on");
- if (hammerNodeList.length > 0) {
- this.addTie(hammerNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.HAMMERON);
- }
- const pulloffNodeList: IXmlElement[] = technicalNode.elements("pull-off");
- if (pulloffNodeList.length > 0) {
- this.addTie(pulloffNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction, TieTypes.PULLOFF);
- }
- }
- // remove open ties, if there is already a gap between the last tie note and now.
- const openTieDict: { [_: number]: Tie } = this.openTieDict;
- for (const key in openTieDict) {
- if (openTieDict.hasOwnProperty(key)) {
- const tie: Tie = openTieDict[key];
- if (Fraction.plus(tie.StartNote.ParentStaffEntry.Timestamp, tie.Duration).lt(this.currentStaffEntry.Timestamp)) {
- delete openTieDict[key];
- }
- }
- }
- }
- // time-modification yields tuplet in currentNote
- // mustn't execute method, if this is the Note where the Tuplet has been created
- if (noteNode.element("time-modification") !== undefined && !hasTupletCommand) {
- this.handleTimeModificationNode(noteNode);
- }
- } catch (err) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/NoteError", "Ignored erroneous Note."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- }
- return this.currentNote;
- }
- /**
- * Create a new [[StaffEntryLink]] and sets the currenstStaffEntry accordingly.
- * @param index
- * @param currentStaff
- * @param currentStaffEntry
- * @param currentMeasure
- * @returns {SourceStaffEntry}
- */
- public checkForStaffEntryLink(index: number, currentStaff: Staff, currentStaffEntry: SourceStaffEntry, currentMeasure: SourceMeasure): SourceStaffEntry {
- const staffEntryLink: StaffEntryLink = new StaffEntryLink(this.currentVoiceEntry);
- staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
- currentStaffEntry.Link = staffEntryLink;
- const linkMusicTimestamp: Fraction = this.currentVoiceEntry.Timestamp.clone();
- const verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = currentMeasure.getVerticalContainerByTimestamp(linkMusicTimestamp);
- currentStaffEntry = verticalSourceStaffEntryContainer.StaffEntries[index];
- if (!currentStaffEntry) {
- currentStaffEntry = new SourceStaffEntry(verticalSourceStaffEntryContainer, currentStaff);
- verticalSourceStaffEntryContainer.StaffEntries[index] = currentStaffEntry;
- }
- currentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
- staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
- currentStaffEntry.Link = staffEntryLink;
- return currentStaffEntry;
- }
- public checkForOpenBeam(): void {
- if (this.openBeams.length > 0 && this.currentNote) {
- this.handleOpenBeam();
- }
- }
- public checkOpenTies(): void {
- const openTieDict: { [key: number]: Tie } = this.openTieDict;
- for (const key in openTieDict) {
- if (openTieDict.hasOwnProperty(key)) {
- const tie: Tie = openTieDict[key];
- if (Fraction.plus(tie.StartNote.ParentStaffEntry.Timestamp, tie.Duration)
- .lt(tie.StartNote.SourceMeasure.Duration)) {
- delete openTieDict[key];
- }
- }
- }
- }
- public hasVoiceEntry(): boolean {
- return this.currentVoiceEntry !== undefined;
- }
- private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry, currentNote: Note): void {
- const articNode: IXmlElement = notationNode.element("articulations");
- if (articNode) {
- this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
- }
- const fermaNode: IXmlElement = notationNode.element("fermata");
- if (fermaNode) {
- this.articulationReader.addFermata(fermaNode, currentVoiceEntry);
- }
- const tecNode: IXmlElement = notationNode.element("technical");
- if (tecNode) {
- this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry, currentNote);
- }
- const ornaNode: IXmlElement = notationNode.element("ornaments");
- if (ornaNode) {
- this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
- // const tremoloNode: IXmlElement = ornaNode.element("tremolo");
- // tremolo should be and is added per note, not per VoiceEntry. see addSingleNote()
- }
- }
- /**
- * Create a new [[Note]] and adds it to the currentVoiceEntry
- * @param node
- * @param noteDuration
- * @param divisions
- * @param chord
- * @param octavePlusOne Software like Guitar Pro gives one octave too low, so we need to add one
- * @returns {Note}
- */
- private addSingleNote(node: IXmlElement, noteDuration: Fraction, noteTypeXml: NoteType, typeDuration: Fraction,
- normalNotes: number, chord: boolean, octavePlusOne: boolean,
- printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
- stemColorXml: string, noteheadColorXml: string): Note {
- //log.debug("addSingleNote called");
- let noteAlter: number = 0;
- let noteAccidental: AccidentalEnum = AccidentalEnum.NONE;
- let noteStep: NoteEnum = NoteEnum.C;
- let displayStepUnpitched: NoteEnum = NoteEnum.C;
- let noteOctave: number = 0;
- let displayOctaveUnpitched: number = 0;
- let playbackInstrumentId: string = undefined;
- let noteheadShapeXml: string = undefined;
- let noteheadFilledXml: boolean = undefined; // if undefined, the final filled parameter will be calculated from duration
- const xmlnodeElementsArr: IXmlElement[] = node.elements();
- for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
- const noteElement: IXmlElement = xmlnodeElementsArr[idx];
- try {
- if (noteElement.name === "pitch") {
- const noteElementsArr: IXmlElement[] = noteElement.elements();
- for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
- const pitchElement: IXmlElement = noteElementsArr[idx2];
- noteheadShapeXml = undefined; // reinitialize for each pitch
- noteheadFilledXml = undefined;
- try {
- if (pitchElement.name === "step") {
- noteStep = NoteEnum[pitchElement.value];
- if (noteStep === undefined) { // don't replace undefined check
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/NotePitchError",
- "Invalid pitch while reading note."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException(errorMsg, undefined);
- }
- } else if (pitchElement.name === "alter") {
- noteAlter = parseFloat(pitchElement.value);
- if (isNaN(noteAlter)) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/NoteAlterationError", "Invalid alteration while reading note."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException(errorMsg, undefined);
- }
- noteAccidental = Pitch.AccidentalFromHalfTones(noteAlter); // potentially overwritten by "accidental" noteElement
- } else if (pitchElement.name === "octave") {
- noteOctave = parseInt(pitchElement.value, 10);
- if (isNaN(noteOctave)) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/NoteOctaveError", "Invalid octave value while reading note."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException(errorMsg, undefined);
- }
- }
- } catch (ex) {
- log.info("VoiceGenerator.addSingleNote read Step: ", ex.message);
- }
- }
- } else if (noteElement.name === "accidental") {
- const accidentalValue: string = noteElement.value;
- if (accidentalValue === "natural") {
- noteAccidental = AccidentalEnum.NATURAL;
- } else if (accidentalValue === "slash-flat") {
- noteAccidental = AccidentalEnum.SLASHFLAT;
- }
- } else if (noteElement.name === "unpitched") {
- const displayStepElement: IXmlElement = noteElement.element("display-step");
- const octave: IXmlElement = noteElement.element("display-octave");
- if (octave) {
- noteOctave = parseInt(octave.value, 10);
- displayOctaveUnpitched = noteOctave - 3;
- if (octavePlusOne) {
- noteOctave += 1;
- }
- if (this.instrument.Staves[0].StafflineCount === 1) {
- displayOctaveUnpitched += 1;
- }
- }
- if (displayStepElement) {
- noteStep = NoteEnum[displayStepElement.value.toUpperCase()];
- let octaveShift: number = 0;
- let noteValueShift: number = this.musicSheet.Rules.PercussionXMLDisplayStepNoteValueShift;
- if (this.instrument.Staves[0].StafflineCount === 1) {
- noteValueShift -= 3; // for percussion one line scores, we need to set the notes 3 lines lower
- }
- [displayStepUnpitched, octaveShift] = Pitch.lineShiftFromNoteEnum(noteStep, noteValueShift);
- displayOctaveUnpitched += octaveShift;
- }
- } else if (noteElement.name === "instrument") {
- if (noteElement.firstAttribute) {
- playbackInstrumentId = noteElement.firstAttribute.value;
- }
- } else if (noteElement.name === "notehead") {
- noteheadShapeXml = noteElement.value;
- if (noteElement.attribute("filled")) {
- noteheadFilledXml = noteElement.attribute("filled").value === "yes";
- }
- }
- } catch (ex) {
- log.info("VoiceGenerator.addSingleNote: ", ex);
- }
- }
- noteOctave -= Pitch.OctaveXmlDifference;
- const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAccidental);
- const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
- let note: Note = undefined;
- let stringNumber: number = -1;
- let fretNumber: number = -1;
- const bends: {bendalter: number, direction: string}[] = [];
- // check for guitar tabs:
- const notationNode: IXmlElement = node.element("notations");
- if (notationNode) {
- const technicalNode: IXmlElement = notationNode.element("technical");
- if (technicalNode) {
- const stringNode: IXmlElement = technicalNode.element("string");
- if (stringNode) {
- stringNumber = parseInt(stringNode.value, 10);
- }
- const fretNode: IXmlElement = technicalNode.element("fret");
- if (fretNode) {
- fretNumber = parseInt(fretNode.value, 10);
- }
- const bendElementsArr: IXmlElement[] = technicalNode.elements("bend");
- bendElementsArr.forEach(function (bend: IXmlElement): void {
- const bendalterNote: IXmlElement = bend.element("bend-alter");
- const releaseNode: IXmlElement = bend.element("release");
- if (releaseNode !== undefined) {
- bends.push({bendalter: parseInt (bendalterNote.value, 10), direction: "down"});
- } else {
- bends.push({bendalter: parseInt (bendalterNote.value, 10), direction: "up"});
- }
- });
- }
- }
- if (stringNumber < 0 || fretNumber < 0) {
- // create normal Note
- note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch, this.currentMeasure);
- } else {
- // create TabNote
- note = new TabNote(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch, this.currentMeasure,
- stringNumber, fretNumber, bends);
- }
- this.addNoteInfo(note, noteTypeXml, printObject, isCueNote, normalNotes,
- displayStepUnpitched, displayOctaveUnpitched,
- noteheadColorXml, noteheadColorXml);
- note.TypeLength = typeDuration;
- note.IsGraceNote = isGraceNote;
- note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
- note.TremoloStrokes = tremoloStrokes; // could be a Tremolo object in future if we have more data to manage like two-note tremolo
- note.PlaybackInstrumentId = playbackInstrumentId;
- if ((noteheadShapeXml !== undefined && noteheadShapeXml !== "normal") || noteheadFilledXml !== undefined) {
- note.Notehead = new Notehead(note, noteheadShapeXml, noteheadFilledXml);
- } // if normal, leave note head undefined to save processing/runtime
- note.NoteheadColorXml = noteheadColorXml; // color set in Xml, shouldn't be changed.
- note.NoteheadColor = noteheadColorXml; // color currently used
- note.PlaybackInstrumentId = playbackInstrumentId;
- this.currentVoiceEntry.addNote(note);
- if (stemDirectionXml === StemDirectionType.None) {
- stemColorXml = "#00000000"; // just setting this to transparent for now
- }
- this.currentVoiceEntry.StemDirectionXml = stemDirectionXml;
- if (stemColorXml) {
- this.currentVoiceEntry.StemColorXml = stemColorXml;
- this.currentVoiceEntry.StemColor = stemColorXml;
- note.StemColorXml = stemColorXml;
- }
- if (node.elements("beam") && !chord) {
- this.createBeam(node, note);
- }
- return note;
- }
- /**
- * Create a new rest note and add it to the currentVoiceEntry.
- * @param noteDuration
- * @param divisions
- * @returns {Note}
- */
- private addRestNote(node: IXmlElement, noteDuration: Fraction, noteTypeXml: NoteType,
- normalNotes: number, printObject: boolean, isCueNote: boolean, noteheadColorXml: string): Note {
- const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
- const displayStepElement: IXmlElement = node.element("display-step");
- const octaveElement: IXmlElement = node.element("display-octave");
- let displayStep: NoteEnum;
- let displayOctave: number;
- let pitch: Pitch = undefined;
- if (displayStepElement && octaveElement) {
- displayStep = NoteEnum[displayStepElement.value.toUpperCase()];
- displayOctave = parseInt(octaveElement.value, 10);
- pitch = new Pitch(displayStep, displayOctave, AccidentalEnum.NONE);
- }
- const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, pitch, this.currentMeasure, true);
- this.addNoteInfo(restNote, noteTypeXml, printObject, isCueNote, normalNotes, displayStep, displayOctave, noteheadColorXml, noteheadColorXml);
- this.currentVoiceEntry.Notes.push(restNote);
- if (this.openBeams.length > 0) {
- this.openBeams.last().ExtendedNoteList.push(restNote);
- }
- return restNote;
- }
- // common for "normal" notes and rest notes
- private addNoteInfo(note: Note, noteTypeXml: NoteType, printObject: boolean, isCueNote: boolean, normalNotes: number,
- displayStep: NoteEnum, displayOctave: number,
- noteheadColorXml: string, noteheadColor: string): void {
- // common for normal notes and rest note
- note.NoteTypeXml = noteTypeXml;
- note.PrintObject = printObject;
- note.IsCueNote = isCueNote;
- note.NormalNotes = normalNotes; // how many rhythmical notes the notes replace (e.g. for tuplets), see xml "actual-notes" and "normal-notes"
- note.displayStepUnpitched = displayStep;
- note.displayOctaveUnpitched = displayOctave;
- note.NoteheadColorXml = noteheadColorXml; // color set in Xml, shouldn't be changed.
- note.NoteheadColor = noteheadColorXml; // color currently used
- // add TypeLength for rest notes like with Note?
- // add IsGraceNote for rest notes like with Notes?
- // add PlaybackInstrumentId for rest notes?
- }
- /**
- * Handle the currentVoiceBeam.
- * @param node
- * @param note
- */
- private createBeam(node: IXmlElement, note: Note): void {
- try {
- const beamNode: IXmlElement = node.element("beam");
- let beamAttr: IXmlAttribute = undefined;
- if (beamNode !== undefined && beamNode.hasAttributes) {
- beamAttr = beamNode.attribute("number");
- }
- if (beamAttr) {
- let beamNumber: number = parseInt(beamAttr.value, 10);
- const mainBeamNode: IXmlElement[] = node.elements("beam");
- const currentBeamTag: string = mainBeamNode[0].value;
- if (mainBeamNode) {
- if (currentBeamTag === "begin") {
- if (beamNumber === this.openBeams.last()?.BeamNumber) {
- // beam with same number already existed (error in XML), bump beam number
- this.beamNumberOffset++;
- beamNumber += this.beamNumberOffset;
- } else if (this.openBeams.last()) {
- this.handleOpenBeam();
- }
- this.openBeams.push(new Beam(beamNumber, this.beamNumberOffset));
- } else {
- beamNumber += this.beamNumberOffset;
- }
- }
- let sameVoiceEntry: boolean = false;
- if (!(beamNumber > 0 && beamNumber <= this.openBeams.length) || !this.openBeams[beamNumber - 1]) {
- log.debug("[OSMD] invalid beamnumber"); // this shouldn't happen, probably error in this method
- return;
- }
- for (let idx: number = 0, len: number = this.openBeams[beamNumber - 1].Notes.length; idx < len; ++idx) {
- const beamNote: Note = this.openBeams[beamNumber - 1].Notes[idx];
- if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
- sameVoiceEntry = true;
- }
- }
- if (!sameVoiceEntry) {
- const openBeam: Beam = this.openBeams[beamNumber - 1];
- openBeam.addNoteToBeam(note);
- // const lastBeamNote: Note = openBeam.Notes.last();
- // const graceStatusChanged: boolean = (lastBeamNote?.IsCueNote || lastBeamNote?.IsGraceNote) !== (note.IsCueNote) || (note.IsGraceNote);
- if (currentBeamTag === "end") {
- this.endBeam();
- }
- }
- }
- } catch (e) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/BeamError", "Error while reading beam."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException("", e);
- }
- }
- private endBeam(): void {
- this.openBeams.pop(); // pop the last open beam from the stack. the latest openBeam will be the one before that now
- this.beamNumberOffset = Math.max(0, this.beamNumberOffset - 1);
- }
- /**
- * Check for open [[Beam]]s at end of [[SourceMeasure]] and closes them explicity.
- */
- private handleOpenBeam(): void {
- const openBeam: Beam = this.openBeams.last();
- if (openBeam.Notes.length === 1) {
- const beamNote: Note = openBeam.Notes[0];
- beamNote.NoteBeam = undefined;
- this.endBeam();
- return;
- }
- if (this.currentNote === CollectionUtil.last(openBeam.Notes)) {
- this.endBeam();
- } else {
- const beamLastNote: Note = CollectionUtil.last(openBeam.Notes);
- const beamLastNoteStaffEntry: SourceStaffEntry = beamLastNote.ParentStaffEntry;
- const horizontalIndex: number = this.currentMeasure.getVerticalContainerIndexByTimestamp(beamLastNoteStaffEntry.Timestamp);
- const verticalIndex: number = beamLastNoteStaffEntry.VerticalContainerParent.StaffEntries.indexOf(beamLastNoteStaffEntry);
- if (horizontalIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length - 1) {
- const nextStaffEntry: SourceStaffEntry = this.currentMeasure
- .VerticalSourceStaffEntryContainers[horizontalIndex + 1]
- .StaffEntries[verticalIndex];
- if (nextStaffEntry) {
- for (let idx: number = 0, len: number = nextStaffEntry.VoiceEntries.length; idx < len; ++idx) {
- const voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
- if (voiceEntry.ParentVoice === this.voice) {
- const candidateNote: Note = voiceEntry.Notes[0];
- if (candidateNote.Length.lte(new Fraction(1, 8))) {
- this.openBeams.last().addNoteToBeam(candidateNote);
- this.endBeam();
- } else {
- this.endBeam();
- }
- }
- }
- }
- } else {
- this.endBeam();
- }
- }
- }
- /**
- * Create a [[Tuplet]].
- * @param node
- * @param tupletNodeList
- * @returns {number}
- */
- private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
- let bracketed: boolean = false; // xml bracket attribute value
- // TODO refactor this to not duplicate lots of code for the cases tupletNodeList.length == 1 and > 1
- if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
- let timeModNode: IXmlElement = node.element("time-modification");
- if (timeModNode) {
- timeModNode = timeModNode.element("actual-notes");
- }
- const tupletNodeListArr: IXmlElement[] = tupletNodeList;
- for (let idx: number = 0, len: number = tupletNodeListArr.length; idx < len; ++idx) {
- const tupletNode: IXmlElement = tupletNodeListArr[idx];
- if (tupletNode !== undefined && tupletNode.attributes()) {
- const bracketAttr: Attr = tupletNode.attribute("bracket");
- if (bracketAttr && bracketAttr.value === "yes") {
- bracketed = true;
- }
- const type: Attr = tupletNode.attribute("type");
- if (type && type.value === "start") {
- let tupletNumber: number = 1;
- if (tupletNode.attribute("number")) {
- tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
- }
- let tupletLabelNumber: number = 0;
- if (timeModNode) {
- tupletLabelNumber = parseInt(timeModNode.value, 10);
- if (isNaN(tupletLabelNumber)) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException(errorMsg, undefined);
- }
- }
- const tuplet: Tuplet = new Tuplet(tupletLabelNumber, bracketed);
- //Default to above
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- //If we ever encounter a placement attribute for this tuplet, should override.
- //Even previous placement attributes for the tuplet
- const placementAttr: Attr = tupletNode.attribute("placement");
- if (placementAttr) {
- if (placementAttr.value === "below") {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- }
- tuplet.PlacementFromXml = true;
- }
- if (this.tupletDict[tupletNumber]) {
- delete this.tupletDict[tupletNumber];
- if (Object.keys(this.tupletDict).length === 0) {
- this.openTupletNumber = 0;
- } else if (Object.keys(this.tupletDict).length > 1) {
- this.openTupletNumber--;
- }
- }
- this.tupletDict[tupletNumber] = tuplet;
- const subnotelist: Note[] = [];
- subnotelist.push(this.currentNote);
- tuplet.Notes.push(subnotelist);
- tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
- this.currentNote.NoteTuplet = tuplet;
- this.openTupletNumber = tupletNumber;
- } else if (type.value === "stop") {
- let tupletNumber: number = 1;
- if (tupletNode.attribute("number")) {
- tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
- }
- const tuplet: Tuplet = this.tupletDict[tupletNumber];
- if (tuplet) {
- const placementAttr: Attr = tupletNode.attribute("placement");
- if (placementAttr) {
- if (placementAttr.value === "below") {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- } else {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- tuplet.PlacementFromXml = true;
- }
- const subnotelist: Note[] = [];
- subnotelist.push(this.currentNote);
- tuplet.Notes.push(subnotelist);
- //If our placement hasn't been from XML, check all the notes in the tuplet
- //Search for the first non-rest and use it's stem direction
- if (!tuplet.PlacementFromXml) {
- let foundNonRest: boolean = false;
- for (const subList of tuplet.Notes) {
- for (const note of subList) {
- if (!note.isRest()) {
- if(note.StemDirectionXml === StemDirectionType.Down) {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- } else {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- foundNonRest = true;
- break;
- }
- }
- if (foundNonRest) {
- break;
- }
- }
- }
- tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
- this.currentNote.NoteTuplet = tuplet;
- delete this.tupletDict[tupletNumber];
- if (Object.keys(this.tupletDict).length === 0) {
- this.openTupletNumber = 0;
- } else if (Object.keys(this.tupletDict).length > 1) {
- this.openTupletNumber--;
- }
- }
- }
- }
- }
- } else if (tupletNodeList[0]) {
- const n: IXmlElement = tupletNodeList[0];
- if (n.hasAttributes) {
- const type: string = n.attribute("type").value;
- let tupletnumber: number = 1;
- if (n.attribute("number")) {
- tupletnumber = parseInt(n.attribute("number").value, 10);
- }
- const noTupletNumbering: boolean = isNaN(tupletnumber);
- const bracketAttr: Attr = n.attribute("bracket");
- if (bracketAttr && bracketAttr.value === "yes") {
- bracketed = true;
- }
- if (type === "start") {
- let tupletLabelNumber: number = 0;
- let timeModNode: IXmlElement = node.element("time-modification");
- if (timeModNode) {
- timeModNode = timeModNode.element("actual-notes");
- }
- if (timeModNode) {
- tupletLabelNumber = parseInt(timeModNode.value, 10);
- if (isNaN(tupletLabelNumber)) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException(errorMsg);
- }
- }
- if (noTupletNumbering) {
- this.openTupletNumber++;
- tupletnumber = this.openTupletNumber;
- }
- let tuplet: Tuplet = this.tupletDict[tupletnumber];
- if (!tuplet) {
- tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber, bracketed);
- //Default to above
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- //If we ever encounter a placement attribute for this tuplet, should override.
- //Even previous placement attributes for the tuplet
- const placementAttr: Attr = n.attribute("placement");
- if (placementAttr) {
- if (placementAttr.value === "below") {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- } else {
- //Just in case
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- tuplet.PlacementFromXml = true;
- }
- const subnotelist: Note[] = [];
- subnotelist.push(this.currentNote);
- tuplet.Notes.push(subnotelist);
- tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
- this.currentNote.NoteTuplet = tuplet;
- this.openTupletNumber = tupletnumber;
- } else if (type === "stop") {
- if (noTupletNumbering) {
- tupletnumber = this.openTupletNumber;
- }
- const tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
- if (tuplet) {
- const placementAttr: Attr = n.attribute("placement");
- if (placementAttr) {
- if (placementAttr.value === "below") {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- } else {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- tuplet.PlacementFromXml = true;
- }
- const subnotelist: Note[] = [];
- subnotelist.push(this.currentNote);
- tuplet.Notes.push(subnotelist);
- //If our placement hasn't been from XML, check all the notes in the tuplet
- //Search for the first non-rest and use it's stem direction
- if (!tuplet.PlacementFromXml) {
- let foundNonRest: boolean = false;
- for (const subList of tuplet.Notes) {
- for (const note of subList) {
- if (!note.isRest()) {
- if(note.StemDirectionXml === StemDirectionType.Down) {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Below;
- } else {
- tuplet.tupletLabelNumberPlacement = PlacementEnum.Above;
- }
- foundNonRest = true;
- break;
- }
- }
- if (foundNonRest) {
- break;
- }
- }
- }
- tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
- this.currentNote.NoteTuplet = tuplet;
- if (Object.keys(this.tupletDict).length === 0) {
- this.openTupletNumber = 0;
- } else if (Object.keys(this.tupletDict).length > 1) {
- this.openTupletNumber--;
- }
- delete this.tupletDict[tupletnumber];
- }
- }
- }
- }
- return this.openTupletNumber;
- }
- /**
- * This method handles the time-modification IXmlElement for the Tuplet case (tupletNotes not at begin/end of Tuplet).
- * @param noteNode
- */
- private handleTimeModificationNode(noteNode: IXmlElement): void {
- if (this.tupletDict[this.openTupletNumber]) {
- try {
- // Tuplet should already be created
- const tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
- const notes: Note[] = CollectionUtil.last(tuplet.Notes);
- const lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;
- let noteList: Note[];
- if (lastTupletVoiceEntry.Timestamp.Equals(this.currentVoiceEntry.Timestamp)) {
- noteList = notes;
- } else {
- noteList = [];
- tuplet.Notes.push(noteList);
- tuplet.Fractions.push(this.getTupletNoteDurationFromType(noteNode));
- }
- noteList.push(this.currentNote);
- this.currentNote.NoteTuplet = tuplet;
- } catch (ex) {
- const errorMsg: string = ITextTranslation.translateText(
- "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
- );
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw ex;
- }
- } else if (this.currentVoiceEntry.Notes.length > 0) {
- const firstNote: Note = this.currentVoiceEntry.Notes[0];
- if (firstNote.NoteTuplet) {
- const tuplet: Tuplet = firstNote.NoteTuplet;
- const notes: Note[] = CollectionUtil.last(tuplet.Notes);
- notes.push(this.currentNote);
- this.currentNote.NoteTuplet = tuplet;
- }
- }
- }
- private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, tieType: TieTypes): void {
- if (tieNodeList) {
- if (tieNodeList.length === 1) {
- const tieNode: IXmlElement = tieNodeList[0];
- if (tieNode !== undefined && tieNode.attributes()) {
- let tieDirection: PlacementEnum = PlacementEnum.NotYetDefined;
- // read tie direction/placement from XML
- const placementAttr: IXmlAttribute = tieNode.attribute("placement");
- if (placementAttr) {
- if (placementAttr.value === "above") {
- tieDirection = PlacementEnum.Above;
- } else if (placementAttr.value === "below") {
- tieDirection = PlacementEnum.Below;
- }
- }
- // tie direction also be given like this:
- const orientationAttr: IXmlAttribute = tieNode.attribute("orientation");
- if (orientationAttr) {
- if (orientationAttr.value === "over") {
- tieDirection = PlacementEnum.Above;
- } else if (orientationAttr.value === "under") {
- tieDirection = PlacementEnum.Below;
- }
- }
- const type: string = tieNode.attribute("type").value;
- try {
- if (type === "start") {
- const num: number = this.findCurrentNoteInTieDict(this.currentNote);
- if (num < 0) {
- delete this.openTieDict[num];
- }
- const newTieNumber: number = this.getNextAvailableNumberForTie();
- const tie: Tie = new Tie(this.currentNote, tieType);
- this.openTieDict[newTieNumber] = tie;
- tie.TieNumber = newTieNumber;
- tie.TieDirection = tieDirection;
- } else if (type === "stop") {
- const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
- const tie: Tie = this.openTieDict[tieNumber];
- if (tie) {
- tie.AddNote(this.currentNote);
- delete this.openTieDict[tieNumber];
- }
- }
- } catch (err) {
- const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- }
- }
- } else if (tieNodeList.length === 2) {
- const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
- if (tieNumber >= 0) {
- const tie: Tie = this.openTieDict[tieNumber];
- tie.AddNote(this.currentNote);
- }
- }
- }
- }
- /**
- * Find the next free int (starting from 0) to use as key in TieDict.
- * @returns {number}
- */
- private getNextAvailableNumberForTie(): number {
- const keys: string[] = Object.keys(this.openTieDict);
- if (keys.length === 0) {
- return 1;
- }
- keys.sort((a, b) => (+a - +b)); // FIXME Andrea: test
- for (let i: number = 0; i < keys.length; i++) {
- if ("" + (i + 1) !== keys[i]) {
- return i + 1;
- }
- }
- return +(keys[keys.length - 1]) + 1;
- }
- /**
- * Search the tieDictionary for the corresponding candidateNote to the currentNote (same FundamentalNote && Octave).
- * @param candidateNote
- * @returns {number}
- */
- private findCurrentNoteInTieDict(candidateNote: Note): number {
- const openTieDict: { [_: number]: Tie } = this.openTieDict;
- for (const key in openTieDict) {
- if (openTieDict.hasOwnProperty(key)) {
- const tie: Tie = openTieDict[key];
- const tieTabNote: TabNote = tie.Notes[0] as TabNote;
- const tieCandidateNote: TabNote = candidateNote as TabNote;
- if (tie.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Pitch.Octave === candidateNote.Pitch.Octave) {
- return parseInt(key, 10);
- } else if (tieTabNote.StringNumberTab !== undefined) {
- if (tieTabNote.StringNumberTab === tieCandidateNote.StringNumberTab) {
- return parseInt(key, 10);
- }
- }
- }
- }
- return -1;
- }
- /**
- * Calculate the normal duration of a [[Tuplet]] note.
- * @param xmlNode
- * @returns {any}
- */
- private getTupletNoteDurationFromType(xmlNode: IXmlElement): Fraction {
- if (xmlNode.element("type")) {
- const typeNode: IXmlElement = xmlNode.element("type");
- if (typeNode) {
- const type: string = typeNode.value;
- try {
- return NoteTypeHandler.getNoteDurationFromType(type);
- } catch (e) {
- const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid note duration.");
- this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
- throw new MusicSheetReadingException("", e);
- }
- }
- }
- return undefined;
- }
- }
|