Bladeren bron

Added code for VoiceGenerator class

Oliver 9 jaren geleden
bovenliggende
commit
807703c8ce
1 gewijzigde bestanden met toevoegingen van 852 en 1 verwijderingen
  1. 852 1
      src/MusicalScore/ScoreIO/VoiceGenerator.ts

+ 852 - 1
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -1 +1,852 @@
-//TODO
+import {Instrument} from "../Instrument";
+import {LinkedVoice} from "../VoiceData/LinkedVoice";
+import {Voice} from "../VoiceData/Voice";
+import {MusicSheet} from "../MusicSheet";
+import {VoiceEntry} 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 {Tuplet} from "../VoiceData/Tuplet";
+import {Fraction} from "../../Common/DataObjects/fraction";
+
+export class VoiceGenerator {
+    constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = null) {
+        this.musicSheet = instrument.GetMusicSheet;
+        this.slurReader = slurReader;
+        if (mainVoice != null)
+            this.voice = new LinkedVoice(instrument, voiceId, mainVoice);
+        else {
+            this.voice = new Voice(instrument, voiceId);
+        }
+        instrument.Voices.Add(this.voice);
+        this.lyricsReader = MusicSymbolModuleFactory.createLyricsReader(this.musicSheet);
+        this.articulationReader = MusicSymbolModuleFactory.createArticulationReader();
+    }
+    private slurReader: SlurReader;
+    private lyricsReader: LyricsReader;
+    private articulationReader: ArticulationReader;
+    private musicSheet: MusicSheet;
+    private voice: Voice;
+    private currentVoiceEntry: VoiceEntry;
+    private currentNote: Note;
+    private currentMeasure: SourceMeasure;
+    private currentStaffEntry: SourceStaffEntry;
+    private lastBeamTag: string = "";
+    private openBeam: Beam;
+    private openGraceBeam: Beam;
+    private openTieDict: Dictionary<number, Tie> = new Dictionary<number, Tie>();
+    private currentOctaveShift: number = 0;
+    private tupletDict: Dictionary<number, Tuplet> = new Dictionary<number, Tuplet>();
+    private openTupletNumber: number = 0;
+    public get GetVoice(): Voice {
+        return this.voice;
+    }
+    public get OctaveShift(): number {
+        return this.currentOctaveShift;
+    }
+    public set OctaveShift(value: number) {
+        this.currentOctaveShift = value;
+    }
+    public createVoiceEntry(musicTimestamp: Fraction, parentStaffEntry: SourceStaffEntry, addToVoice: boolean): void {
+        this.currentVoiceEntry = new VoiceEntry(new Fraction(musicTimestamp), this.voice, parentStaffEntry);
+        if (addToVoice)
+            this.voice.VoiceEntries.Add(this.currentVoiceEntry);
+        if (!parentStaffEntry.VoiceEntries.Contains(this.currentVoiceEntry))
+            parentStaffEntry.VoiceEntries.Add(this.currentVoiceEntry);
+    }
+    public read(noteNode: IXmlElement, noteDuration: number, divisions: number, restNote: boolean, graceNote: boolean,
+        parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
+        measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean): Note {
+        this.currentStaffEntry = parentStaffEntry;
+        this.currentMeasure = parentMeasure;
+        try {
+            this.currentNote = restNote ? this.addRestNote(noteDuration, divisions) : this.addSingleNote(noteNode, noteDuration, divisions, graceNote, chord, guitarPro);
+            if (this.lyricsReader != null && noteNode.Element("lyric") != null) {
+                this.lyricsReader.addLyricEntry(noteNode, this.currentVoiceEntry);
+                this.voice.Parent.HasLyrics = true;
+            }
+            var notationNode: IXmlElement = noteNode.Element("notations");
+            if (notationNode != null) {
+                var articNode: IXmlElement = null;
+                if (this.articulationReader != null) {
+                    this.readArticulations(notationNode, this.currentVoiceEntry);
+                }
+                var slurNodes: IEnumerable<IXmlElement> = null;
+                if (this.slurReader != null && (slurNodes = notationNode.Elements("slur")).Any())
+                    this.slurReader.addSlur(slurNodes.ToArray(), this.currentNote);
+                var tupletNodeList: IEnumerable<IXmlElement> = null;
+                if ((tupletNodeList = notationNode.Elements("tuplet")).Any())
+                    this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
+                if (notationNode.Element("arpeggiate") != null && !graceNote)
+                    this.currentVoiceEntry.ArpeggiosNotesIndices.Add(this.currentVoiceEntry.Notes.IndexOf(this.currentNote));
+                var tiedNodeList: IEnumerable<IXmlElement> = null;
+                if ((tiedNodeList = notationNode.Elements("tied")).Any())
+                    this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
+                var toRemove: List<number> = new List<number>();
+                var openTieDictArr: KeyValuePair<number, Tie>[] = this.openTieDict.ToArray();
+                for (var idx: number = 0, len = openTieDictArr.length; idx < len; ++idx) {
+                    var openTie: KeyValuePair<number, Tie> = openTieDictArr[idx];
+                    var tie: Tie = openTie.Value;
+                    if (tie.Start.ParentStaffEntry.Timestamp + tie.Start.Length < this.currentStaffEntry.Timestamp)
+                        toRemove.Add(openTie.Key);
+                }
+                for (var idx: number = 0, len = toRemove.Count; idx < len; ++idx) {
+                    var i: number = toRemove[idx];
+                    this.openTieDict.Remove(i);
+                }
+            }
+            if (noteNode.Element("time-modification") != null && notationNode == null) {
+                this.handleTimeModificationNode(noteNode);
+            }
+        }
+        catch (err) {
+            var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteError",
+                "Ignored erroneous Note.");
+            this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+        }
+
+        return this.currentNote;
+    }
+    public checkForOpenGraceNotes(): void {
+        if (this.currentStaffEntry != null && this.currentStaffEntry.VoiceEntries.Count == 0 && this.currentVoiceEntry.GraceVoiceEntriesBefore != null && this.currentVoiceEntry.GraceVoiceEntriesBefore.Count > 0) {
+            var voice: Voice = this.currentVoiceEntry.ParentVoice;
+            var horizontalIndex: number = this.currentMeasure.VerticalSourceStaffEntryContainers.IndexOf(this.currentStaffEntry.VerticalContainerParent);
+            var verticalIndex: number = this.currentStaffEntry.VerticalContainerParent.StaffEntries.IndexOf(this.currentStaffEntry);
+            var previousStaffEntry: SourceStaffEntry = this.currentMeasure.getPreviousSourceStaffEntryFromIndex(verticalIndex, horizontalIndex);
+            if (previousStaffEntry != null) {
+                var previousVoiceEntry: VoiceEntry = null;
+                for (var idx: number = 0, len = previousStaffEntry.VoiceEntries.Count; idx < len; ++idx) {
+                    var voiceEntry: VoiceEntry = previousStaffEntry.VoiceEntries[idx];
+                    if (voiceEntry.ParentVoice == voice) {
+                        previousVoiceEntry = voiceEntry;
+                        previousVoiceEntry.GraceVoiceEntriesAfter = new List<VoiceEntry>();
+                        for (var idx2: number = 0, len2 = this.currentVoiceEntry.GraceVoiceEntriesBefore.Count; idx2 < len2; ++idx2) {
+                            var graceVoiceEntry: VoiceEntry = this.currentVoiceEntry.GraceVoiceEntriesBefore[idx2];
+                            previousVoiceEntry.GraceVoiceEntriesAfter.Add(graceVoiceEntry);
+                        }
+                        this.currentVoiceEntry.GraceVoiceEntriesBefore.Clear();
+                        this.currentStaffEntry = null;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    public checkForStaffEntryLink(index: number, currentStaff: Staff, currentStaffEntry: SourceStaffEntry,
+        currentMeasure: SourceMeasure): SourceStaffEntry {
+        var staffEntryLink: StaffEntryLink = new StaffEntryLink(this.currentVoiceEntry);
+        staffEntryLink.LinkStaffEntries.Add(currentStaffEntry);
+        currentStaffEntry.Link = staffEntryLink;
+        var linkMusicTimestamp: Fraction = new Fraction(this.currentVoiceEntry.Timestamp);
+        var verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = currentMeasure.getVerticalContainerByTimestamp(linkMusicTimestamp);
+        currentStaffEntry = verticalSourceStaffEntryContainer[index];
+        if (currentStaffEntry == null) {
+            currentStaffEntry = new SourceStaffEntry(verticalSourceStaffEntryContainer, currentStaff);
+            verticalSourceStaffEntryContainer[index] = currentStaffEntry;
+        }
+        currentStaffEntry.VoiceEntries.Add(this.currentVoiceEntry);
+        staffEntryLink.LinkStaffEntries.Add(currentStaffEntry);
+        currentStaffEntry.Link = staffEntryLink;
+        return currentStaffEntry;
+    }
+    public checkForOpenBeam(): void {
+        if (this.openBeam != null && this.currentNote != null)
+            this.handleOpenBeam();
+    }
+    public checkOpenTies(): void {
+        var toRemove: List<number> = new List<number>();
+        var openTieDictArr: KeyValuePair<number, Tie>[] = this.openTieDict.ToArray();
+        for (var idx: number = 0, len = openTieDictArr.length; idx < len; ++idx) {
+            var openTie: KeyValuePair<number, Tie> = openTieDictArr[idx];
+            var tie: Tie = openTie.Value;
+            if (tie.Start.ParentStaffEntry.Timestamp + tie.Start.Length < tie.Start.ParentStaffEntry.VerticalContainerParent.ParentMeasure.Duration)
+                toRemove.Add(openTie.Key);
+        }
+        for (var idx: number = 0, len = toRemove.Count; idx < len; ++idx) {
+            var i: number = toRemove[idx];
+            this.openTieDict.Remove(i);
+        }
+    }
+    public hasVoiceEntry(): boolean {
+        return this.currentVoiceEntry != null;
+    }
+    public getNoteDurationFromType(type: string): Fraction {
+        switch (type) {
+            case "1024th":
+                return new Fraction(1, 1024);
+            case "512th":
+                return new Fraction(1, 512);
+            case "256th":
+                return new Fraction(1, 256);
+            case "128th":
+                return new Fraction(1, 128);
+            case "64th":
+                return new Fraction(1, 64);
+            case "32th":
+            case "32nd":
+                return new Fraction(1, 32);
+            case "16th":
+                return new Fraction(1, 16);
+            case "eighth":
+                return new Fraction(1, 8);
+            case "quarter":
+                return new Fraction(1, 4);
+            case "half":
+                return new Fraction(1, 2);
+            case "whole":
+                return new Fraction(1, 1);
+            case "breve":
+                return new Fraction(2, 1);
+            case "long":
+                return new Fraction(4, 1);
+            case "maxima":
+                return new Fraction(8, 1);
+            default:
+                {
+                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError",
+                        "Invalid note duration.");
+                    throw new MusicSheetReadingException(errorMsg, 0);
+                }
+        }
+    }
+    private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+        var articNode: IXmlElement;
+        if ((articNode = notationNode.Element("articulations")) != null)
+            this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
+        var fermaNode: IXmlElement = null;
+        if ((fermaNode = notationNode.Element("fermata")) != null)
+            this.articulationReader.addFermata(fermaNode, currentVoiceEntry);
+        var tecNode: IXmlElement = null;
+        if ((tecNode = notationNode.Element("technical")) != null)
+            this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry);
+        var ornaNode: IXmlElement = null;
+        if ((ornaNode = notationNode.Element("ornaments")) != null)
+            this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
+    }
+    private addSingleNote(node: IXmlElement, noteDuration: number, divisions: number, graceNote: boolean, chord: boolean,
+        guitarPro: boolean): Note {
+        var noteAlter: AccidentalEnum = AccidentalEnum.NONE;
+        var noteStep: NoteEnum = NoteEnum.C;
+        var noteOctave: number = 0;
+        var playbackInstrumentId: string = null;
+        var xmlnodeElementsArr: IXmlElement[] = node.Elements().ToArray();
+        for (var idx: number = 0, len = xmlnodeElementsArr.length; idx < len; ++idx) {
+            var noteElement: IXmlElement = xmlnodeElementsArr[idx];
+            try {
+                if (noteElement.Name == "pitch") {
+                    var noteElementsArr: IXmlElement[] = noteElement.Elements().ToArray();
+                    for (var idx2: number = 0, len2 = noteElementsArr.length; idx2 < len2; ++idx2) {
+                        var pitchElement: IXmlElement = noteElementsArr[idx2];
+                        try {
+                            if (pitchElement.Name == "step") {
+                                try {
+                                    switch (pitchElement.Value) {
+                                        case "C":
+                                            {
+                                                noteStep = NoteEnum.C;
+                                                break;
+                                            }
+                                        case "D":
+                                            {
+                                                noteStep = NoteEnum.D;
+                                                break;
+                                            }
+                                        case "E":
+                                            {
+                                                noteStep = NoteEnum.E;
+                                                break;
+                                            }
+                                        case "F":
+                                            {
+                                                noteStep = NoteEnum.F;
+                                                break;
+                                            }
+                                        case "G":
+                                            {
+                                                noteStep = NoteEnum.G;
+                                                break;
+                                            }
+                                        case "A":
+                                            {
+                                                noteStep = NoteEnum.A;
+                                                break;
+                                            }
+                                        case "B":
+                                            {
+                                                noteStep = NoteEnum.B;
+                                                break;
+                                            }
+                                    }
+                                }
+                                catch (e) {
+                                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NotePitchError",
+                                        "Invalid pitch while reading note.");
+                                    this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                                    throw new MusicSheetReadingException("", e, 0);
+                                }
+
+                            }
+                            else if (pitchElement.Name == "alter") {
+                                try {
+                                    noteAlter = <AccidentalEnum>StringToNumberConverter.ToInteger(pitchElement.Value);
+                                }
+                                catch (e) {
+                                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteAlterationError",
+                                        "Invalid alteration while reading note.");
+                                    this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                                    throw new MusicSheetReadingException("", e, 0);
+                                }
+
+                            }
+                            else if (pitchElement.Name == "octave") {
+                                try {
+                                    noteOctave = <number>StringToNumberConverter.ToInteger(pitchElement.Value);
+                                }
+                                catch (e) {
+                                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteOctaveError",
+                                        "Invalid octave value while reading note.");
+                                    this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                                    throw new MusicSheetReadingException("", e, 0);
+                                }
+
+                            }
+                        }
+                        catch (ex) {
+                            Logger.DefaultLogger.LogError(LogLevel.NORMAL,
+                                "VoiceGenerator.addSingleNote read Step: ", ex);
+                        }
+
+                    }
+                }
+                else if (noteElement.Name == "unpitched") {
+                    var displayStep: IXmlElement = null;
+                    if ((displayStep = noteElement.Element("display-step")) != null) {
+                        noteStep = <NoteEnum>Enum.Parse(/*typeof*/NoteEnum, displayStep.Value);
+                    }
+                    var octave: IXmlElement = null;
+                    if ((octave = noteElement.Element("display-octave")) != null) {
+                        noteOctave = <number>(StringToNumberConverter.ToInteger(octave.Value));
+                        if (guitarPro)
+                            noteOctave += 1;
+                    }
+                }
+                else if (noteElement.Name == "instrument") {
+                    if (noteElement.FirstAttribute != null)
+                        playbackInstrumentId = noteElement.FirstAttribute.Value;
+                }
+            }
+            catch (ex) {
+                Logger.DefaultLogger.LogError(LogLevel.NORMAL, "VoiceGenerator.addSingleNote: ", ex);
+            }
+
+        }
+        noteOctave -= Pitch.XmlOctaveDifference;
+        var pitch: Pitch = new Pitch(noteStep, noteOctave, noteAlter);
+        var noteLength: Fraction = new Fraction(noteDuration, divisions);
+        var note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
+        note.PlaybackInstrumentId = playbackInstrumentId;
+        if (!graceNote)
+            this.currentVoiceEntry.Notes.Add(note);
+        else this.handleGraceNote(node, note);
+        if (node.Elements("beam").Any() && !chord) {
+            this.createBeam(node, note, graceNote);
+        }
+        return note;
+    }
+    private addRestNote(noteDuration: number, divisions: number): Note {
+        var restFraction: Fraction = new Fraction(noteDuration, divisions);
+        var restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, null);
+        this.currentVoiceEntry.Notes.Add(restNote);
+        if (this.openBeam != null)
+            this.openBeam.ExtendedNoteList.Add(restNote);
+        return restNote;
+    }
+    private createBeam(node: IXmlElement, note: Note, grace: boolean): void {
+        try {
+            var beamNode: IXmlElement = node.Element("beam");
+            var beamAttr: IXmlAttribute = null;
+            if (beamNode != null && beamNode.HasAttributes)
+                beamAttr = beamNode.Attribute("number");
+            if (beamAttr != null) {
+                var beamNumber: number = StringToNumberConverter.ToInteger(beamAttr.Value);
+                var mainBeamNode: IEnumerable<IXmlElement> = node.Elements("beam");
+                var currentBeamTag: string = mainBeamNode.First().Value;
+                if (beamNumber == 1 && mainBeamNode != null) {
+                    if (currentBeamTag == "begin" && this.lastBeamTag != currentBeamTag) {
+                        if (grace) {
+                            if (this.openGraceBeam != null)
+                                this.handleOpenBeam();
+                            this.openGraceBeam = new Beam();
+                        }
+                        else {
+                            if (this.openBeam != null)
+                                this.handleOpenBeam();
+                            this.openBeam = new Beam();
+                        }
+                    }
+                    this.lastBeamTag = currentBeamTag;
+                }
+                var sameVoiceEntry: boolean = false;
+                if (grace) {
+                    if (this.openGraceBeam == null)
+                        return
+                    for (var idx: number = 0, len = this.openGraceBeam.Notes.Count; idx < len; ++idx) {
+                        var beamNote: Note = this.openGraceBeam.Notes[idx];
+                        if (this.currentVoiceEntry == beamNote.ParentVoiceEntry)
+                            sameVoiceEntry = true;
+                    }
+                    if (!sameVoiceEntry) {
+                        this.openGraceBeam.addNoteToBeam(note);
+                        if (currentBeamTag == "end" && beamNumber == 1)
+                            this.openGraceBeam = null;
+                    }
+                }
+                else {
+                    if (this.openBeam == null)
+                        return
+                    for (var idx: number = 0, len = this.openBeam.Notes.Count; idx < len; ++idx) {
+                        var beamNote: Note = this.openBeam.Notes[idx];
+                        if (this.currentVoiceEntry == beamNote.ParentVoiceEntry)
+                            sameVoiceEntry = true;
+                    }
+                    if (!sameVoiceEntry) {
+                        this.openBeam.addNoteToBeam(note);
+                        if (currentBeamTag == "end" && beamNumber == 1)
+                            this.openBeam = null;
+                    }
+                }
+            }
+        }
+        catch (e) {
+            var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/BeamError",
+                "Error while reading beam.");
+            this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+            throw new MusicSheetReadingException("", e, 0);
+        }
+
+    }
+    private handleOpenBeam(): void {
+        if (this.openBeam.Notes.Count == 1) {
+            var beamNote: Note = this.openBeam.Notes[0];
+            beamNote.NoteBeam = null;
+            this.openBeam = null;
+            return
+        }
+        if (this.currentNote == this.openBeam.Notes.Last())
+            this.openBeam = null;
+        else {
+            var beamLastNote: Note = this.openBeam.Notes.Last();
+            var beamLastNoteStaffEntry: SourceStaffEntry = beamLastNote.ParentStaffEntry;
+            var horizontalIndex: number = this.currentMeasure.getVerticalContainerIndexByTimestamp(beamLastNoteStaffEntry.Timestamp);
+            var verticalIndex: number = beamLastNoteStaffEntry.VerticalContainerParent.StaffEntries.IndexOf(beamLastNoteStaffEntry);
+            if (horizontalIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.Count - 1) {
+                var nextStaffEntry: SourceStaffEntry = this.currentMeasure.VerticalSourceStaffEntryContainers[horizontalIndex + 1][verticalIndex];
+                if (nextStaffEntry != null) {
+                    for (var idx: number = 0, len = nextStaffEntry.VoiceEntries.Count; idx < len; ++idx) {
+                        var voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
+                        if (voiceEntry.ParentVoice == this.voice) {
+                            var candidateNote: Note = voiceEntry.Notes[0];
+                            if (candidateNote.Length <= new Fraction(1, 8)) {
+                                this.openBeam.addNoteToBeam(candidateNote);
+                                this.openBeam = null;
+                            }
+                            else {
+                                this.openBeam = null;
+                            }
+                        }
+                    }
+                }
+            }
+            else {
+                this.openBeam = null;
+            }
+        }
+    }
+    private handleGraceNote(node: IXmlElement, note: Note): void {
+        var graceChord: boolean = false;
+        var type: string = "";
+        if (node.Elements("type").Any()) {
+            var typeNode: IEnumerable<IXmlElement> = node.Elements("type");
+            if (typeNode.Any()) {
+                type = typeNode.First().Value;
+                try {
+                    note.Length = this.getNoteDurationFromType(type);
+                    note.Length.Numerator = 1;
+                }
+                catch (e) {
+                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError",
+                        "Invalid note duration.");
+                    this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                    throw new MusicSheetReadingException("", e, 0);
+                }
+
+            }
+        }
+        var graceNode: IXmlElement = node.Element("grace");
+        if (graceNode != null && graceNode.Attributes().Any()) {
+            if (graceNode.Attribute("slash") != null) {
+                var slash: string = graceNode.Attribute("slash").Value;
+                if (slash == "yes")
+                    note.GraceNoteSlash = true;
+            }
+        }
+        if (node.Element("chord") != null)
+            graceChord = true;
+        var graceVoiceEntry: VoiceEntry = null;
+        if (!graceChord) {
+            graceVoiceEntry = new VoiceEntry(new Fraction(new Fraction(0, 1)), this.currentVoiceEntry.ParentVoice,
+                this.currentStaffEntry);
+            if (this.currentVoiceEntry.GraceVoiceEntriesBefore == null)
+                this.currentVoiceEntry.GraceVoiceEntriesBefore = new List<VoiceEntry>();
+            this.currentVoiceEntry.GraceVoiceEntriesBefore.Add(graceVoiceEntry);
+        }
+        else {
+            if (this.currentVoiceEntry.GraceVoiceEntriesBefore != null && this.currentVoiceEntry.GraceVoiceEntriesBefore.Count > 0)
+                graceVoiceEntry = this.currentVoiceEntry.GraceVoiceEntriesBefore.Last();
+        }
+        if (graceVoiceEntry != null) {
+            graceVoiceEntry.Notes.Add(note);
+            note.ParentVoiceEntry = graceVoiceEntry;
+        }
+    }
+    private addTuplet(node: IXmlElement, tupletNodeList: IEnumerable<IXmlElement>): number {
+        if (tupletNodeList != null && tupletNodeList.Count() > 1) {
+            var timeModNode: IXmlElement = node.Element("time-modification");
+            if (timeModNode != null)
+                timeModNode = timeModNode.Element("actual-notes");
+            var tupletNodeListArr: IXmlElement[] = tupletNodeList.ToArray();
+            for (var idx: number = 0, len = tupletNodeListArr.length; idx < len; ++idx) {
+                var tupletNode: IXmlElement = tupletNodeListArr[idx];
+                if (tupletNode != null && tupletNode.Attributes().Any()) {
+                    var type: string = tupletNode.Attribute("type").Value;
+                    if (type == "start") {
+                        var tupletNumber: number = 1;
+                        if (tupletNode.Attribute("nummber") != null)
+                            tupletNumber = StringToNumberConverter.ToInteger(tupletNode.Attribute("number").Value);
+                        var tupletLabelNumber: number = 0;
+                        if (timeModNode != null) {
+                            try {
+                                tupletLabelNumber = StringToNumberConverter.ToInteger(timeModNode.Value);
+                            }
+                            catch (e) {
+                                var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TupletNoteDurationError",
+                                    "Invalid tuplet note duration.");
+                                this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                                throw new MusicSheetReadingException("", e, 0);
+                            }
+
+                        }
+                        var tuplet: Tuplet = new Tuplet(tupletLabelNumber);
+                        if (this.tupletDict.ContainsKey(tupletNumber)) {
+                            this.tupletDict.Remove(tupletNumber);
+                            if (this.tupletDict.Count == 0)
+                                this.openTupletNumber = 0;
+                            else if (this.tupletDict.Count > 1)
+                                this.openTupletNumber--;
+                        }
+                        this.tupletDict.Add(tupletNumber, tuplet);
+                        var subnotelist: List<Note> = new List<Note>();
+                        subnotelist.Add(this.currentNote);
+                        tuplet.Notes.Add(subnotelist);
+                        tuplet.Fractions.Add(this.getTupletNoteDurationFromType(node));
+                        this.currentNote.NoteTuplet = tuplet;
+                        this.openTupletNumber = tupletNumber;
+                    }
+                    else if (type == "stop") {
+                        var tupletNumber: number = 1;
+                        if (tupletNode.Attribute("nummber") != null)
+                            tupletNumber = StringToNumberConverter.ToInteger(tupletNode.Attribute("number").Value);
+                        if (this.tupletDict.ContainsKey(tupletNumber)) {
+                            var tuplet: Tuplet = this.tupletDict[tupletNumber];
+                            var subnotelist: List<Note> = new List<Note>();
+                            subnotelist.Add(this.currentNote);
+                            tuplet.Notes.Add(subnotelist);
+                            tuplet.Fractions.Add(this.getTupletNoteDurationFromType(node));
+                            this.currentNote.NoteTuplet = tuplet;
+                            this.tupletDict.Remove(tupletNumber);
+                            if (this.tupletDict.Count == 0)
+                                this.openTupletNumber = 0;
+                            else if (this.tupletDict.Count > 1)
+                                this.openTupletNumber--;
+                        }
+                    }
+                }
+            }
+        }
+        else if (tupletNodeList.First() != null) {
+            var n: IXmlElement = tupletNodeList.First();
+            if (n.HasAttributes) {
+                var type: string = n.Attribute("type").Value;
+                var tupletnumber: number = 1;
+                var noTupletNumbering: boolean = false;
+                try {
+                    if (n.Attribute("number") != null)
+                        tupletnumber = StringToNumberConverter.ToInteger(n.Attribute("number").Value);
+                }
+                catch (err) {
+                    noTupletNumbering = true;
+                }
+
+                if (type == "start") {
+                    var tupletLabelNumber: number = 0;
+                    var timeModNode: IXmlElement = node.Element("time-modification");
+                    if (timeModNode != null)
+                        timeModNode = timeModNode.Element("actual-notes");
+                    if (timeModNode != null) {
+                        try {
+                            tupletLabelNumber = StringToNumberConverter.ToInteger(timeModNode.Value);
+                        }
+                        catch (e) {
+                            var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TupletNoteDurationError",
+                                "Invalid tuplet note duration.");
+                            this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                            throw new MusicSheetReadingException("", e, 0);
+                        }
+
+                    }
+                    if (noTupletNumbering) {
+                        this.openTupletNumber++;
+                        tupletnumber = this.openTupletNumber;
+                    }
+                    var tuplet: Tuplet;
+                    if (this.tupletDict.ContainsKey(tupletnumber)) {
+                        tuplet = this.tupletDict[tupletnumber];
+                    }
+                    else {
+                        tuplet = new Tuplet(tupletLabelNumber);
+                        this.tupletDict.Add(tupletnumber, tuplet);
+                    }
+                    var subnotelist: List<Note> = new List<Note>();
+                    subnotelist.Add(this.currentNote);
+                    tuplet.Notes.Add(subnotelist);
+                    tuplet.Fractions.Add(this.getTupletNoteDurationFromType(node));
+                    this.currentNote.NoteTuplet = tuplet;
+                    this.openTupletNumber = tupletnumber;
+                }
+                else if (type == "stop") {
+                    if (noTupletNumbering)
+                        tupletnumber = this.openTupletNumber;
+                    if (this.tupletDict.ContainsKey(tupletnumber)) {
+                        var tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
+                        var subnotelist: List<Note> = new List<Note>();
+                        subnotelist.Add(this.currentNote);
+                        tuplet.Notes.Add(subnotelist);
+                        tuplet.Fractions.Add(this.getTupletNoteDurationFromType(node));
+                        this.currentNote.NoteTuplet = tuplet;
+                        if (this.tupletDict.Count == 0)
+                            this.openTupletNumber = 0;
+                        else if (this.tupletDict.Count > 1)
+                            this.openTupletNumber--;
+                        this.tupletDict.Remove(tupletnumber);
+                    }
+                }
+            }
+        }
+        return this.openTupletNumber;
+    }
+    private handleTimeModificationNode(noteNode: IXmlElement): void {
+        if (this.tupletDict.Count != 0) {
+            try {
+                var tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
+                var notes: List<Note> = tuplet.Notes.Last();
+                var lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;
+                var noteList: List<Note>;
+                if (lastTupletVoiceEntry.Timestamp == this.currentVoiceEntry.Timestamp)
+                    noteList = notes;
+                else {
+                    noteList = new List<Note>();
+                    tuplet.Notes.Add(noteList);
+                    tuplet.Fractions.Add(this.getTupletNoteDurationFromType(noteNode));
+                }
+                noteList.Add(this.currentNote);
+                this.currentNote.NoteTuplet = tuplet;
+            }
+            catch (ex) {
+                var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TupletNumberError",
+                    "Invalid tuplet number.");
+                this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                throw ex;
+            }
+
+        }
+        else if (this.currentVoiceEntry.Notes.Count > 0) {
+            var firstNote: Note = this.currentVoiceEntry.Notes[0];
+            if (firstNote.NoteTuplet != null) {
+                var tuplet: Tuplet = firstNote.NoteTuplet;
+                var notes: List<Note> = tuplet.Notes.Last();
+                notes.Add(this.currentNote);
+                this.currentNote.NoteTuplet = tuplet;
+            }
+        }
+    }
+    private addTie(tieNodeList: IEnumerable<IXmlElement>, measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction): void {
+        if (tieNodeList != null) {
+            if (tieNodeList.Count() == 1) {
+                var tieNode: IXmlElement = tieNodeList.First();
+                if (tieNode != null && tieNode.Attributes().Any()) {
+                    var type: string = tieNode.Attribute("type").Value;
+                    try {
+                        if (type == "start") {
+                            var number: number = this.findCurrentNoteInTieDict(this.currentNote);
+                            if (number < 0)
+                                this.openTieDict.Remove(number);
+                            var newTieNumber: number = this.getNextAvailableNumberForTie();
+                            var tie: Tie = new Tie(this.currentNote);
+                            this.openTieDict.Add(newTieNumber, tie);
+                            if (this.currentNote.NoteBeam != null)
+                                if (this.currentNote.NoteBeam.Notes[0] == this.currentNote) {
+                                    tie.BeamStartTimestamp = measureStartAbsoluteTimestamp + this.currentVoiceEntry.Timestamp;
+                                }
+                                else {
+                                    for (var idx: number = 0, len = this.currentNote.NoteBeam.Notes.Count; idx < len; ++idx) {
+                                        var note: Note = this.currentNote.NoteBeam.Notes[idx];
+                                        if (note.NoteTie != null && note.NoteTie != tie && note.NoteTie.BeamStartTimestamp != null) {
+                                            tie.BeamStartTimestamp = note.NoteTie.BeamStartTimestamp;
+                                            break;
+                                        }
+                                    }
+                                    if (this.currentNote == this.currentNote.NoteBeam.Notes.Last())
+                                        tie.BeamStartTimestamp = measureStartAbsoluteTimestamp + this.currentVoiceEntry.Timestamp;
+                                }
+                        }
+                        else if (type == "stop") {
+                            var tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
+                            if (this.openTieDict.ContainsKey(tieNumber)) {
+                                var tie: Tie = this.openTieDict[tieNumber];
+                                var tieStartNote: Note = tie.Start;
+                                tieStartNote.NoteTie = tie;
+                                tieStartNote.Length.Add(this.currentNote.Length);
+                                tie.Fractions.Add(this.currentNote.Length);
+                                if (maxTieNoteFraction < this.currentStaffEntry.Timestamp + this.currentNote.Length)
+                                    maxTieNoteFraction = this.currentStaffEntry.Timestamp + this.currentNote.Length;
+                                this.currentVoiceEntry.Notes.Remove(this.currentNote);
+                                if (this.currentVoiceEntry.Articulations.Count == 1 && this.currentVoiceEntry.Articulations[0] == ArticulationEnum.fermata && !tieStartNote.ParentVoiceEntry.Articulations.Contains(ArticulationEnum.fermata))
+                                    tieStartNote.ParentVoiceEntry.Articulations.Add(ArticulationEnum.fermata);
+                                if (this.currentNote.NoteBeam != null) {
+                                    var noteBeamIndex: number = this.currentNote.NoteBeam.Notes.IndexOf(this.currentNote);
+                                    if (noteBeamIndex == 0 && tie.BeamStartTimestamp == null)
+                                        tie.BeamStartTimestamp = measureStartAbsoluteTimestamp + this.currentVoiceEntry.Timestamp;
+                                    var noteBeam: Beam = this.currentNote.NoteBeam;
+                                    noteBeam.Notes[noteBeamIndex] = tieStartNote;
+                                    tie.TieBeam = noteBeam;
+                                }
+                                if (this.currentNote.NoteTuplet != null) {
+                                    var noteTupletIndex: number = this.currentNote.NoteTuplet.getNoteIndex(this.currentNote);
+                                    var index: number = this.currentNote.NoteTuplet.Notes[noteTupletIndex].IndexOf(this.currentNote);
+                                    var noteTuplet: Tuplet = this.currentNote.NoteTuplet;
+                                    noteTuplet.Notes[noteTupletIndex][index] = tieStartNote;
+                                    tie.TieTuplet = noteTuplet;
+                                }
+                                for (var idx: number = 0, len = this.currentNote.NoteSlurs.Count; idx < len; ++idx) {
+                                    var slur: Slur = this.currentNote.NoteSlurs[idx];
+                                    if (slur.StartNote == this.currentNote) {
+                                        slur.StartNote = tie.Start;
+                                        slur.StartNote.NoteSlurs.Add(slur);
+                                    }
+                                    if (slur.EndNote == this.currentNote) {
+                                        slur.EndNote = tie.Start;
+                                        slur.EndNote.NoteSlurs.Add(slur);
+                                    }
+                                }
+                                var lyricsEntriesArr: KeyValuePair<number, LyricsEntry>[] = this.currentVoiceEntry.LyricsEntries.ToArray();
+                                for (var idx: number = 0, len = lyricsEntriesArr.length; idx < len; ++idx) {
+                                    var lyricsEntry: KeyValuePair<number, LyricsEntry> = lyricsEntriesArr[idx];
+                                    if (!tieStartNote.ParentVoiceEntry.LyricsEntries.ContainsKey(lyricsEntry.Key)) {
+                                        tieStartNote.ParentVoiceEntry.LyricsEntries.Add(lyricsEntry.Key, lyricsEntry.Value);
+                                        lyricsEntry.Value.Parent = tieStartNote.ParentVoiceEntry;
+                                    }
+                                }
+                                this.openTieDict.Remove(tieNumber);
+                            }
+                        }
+                    }
+                    catch (err) {
+                        var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
+                        this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                    }
+
+                }
+            }
+            else if (tieNodeList.Count() == 2) {
+                var tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
+                if (tieNumber >= 0) {
+                    var tie: Tie = this.openTieDict[tieNumber];
+                    var tieStartNote: Note = tie.Start;
+                    tieStartNote.Length.Add(this.currentNote.Length);
+                    tie.Fractions.Add(this.currentNote.Length);
+                    if (this.currentNote.NoteBeam != null) {
+                        var noteBeamIndex: number = this.currentNote.NoteBeam.Notes.IndexOf(this.currentNote);
+                        if (noteBeamIndex == 0 && tie.BeamStartTimestamp == null)
+                            tie.BeamStartTimestamp = measureStartAbsoluteTimestamp + this.currentVoiceEntry.Timestamp;
+                        var noteBeam: Beam = this.currentNote.NoteBeam;
+                        noteBeam.Notes[noteBeamIndex] = tieStartNote;
+                        tie.TieBeam = noteBeam;
+                    }
+                    for (var idx: number = 0, len = this.currentNote.NoteSlurs.Count; idx < len; ++idx) {
+                        var slur: Slur = this.currentNote.NoteSlurs[idx];
+                        if (slur.StartNote == this.currentNote) {
+                            slur.StartNote = tie.Start;
+                            slur.StartNote.NoteSlurs.Add(slur);
+                        }
+                        if (slur.EndNote == this.currentNote) {
+                            slur.EndNote = tie.Start;
+                            slur.EndNote.NoteSlurs.Add(slur);
+                        }
+                    }
+                    var lyricsEntries: KeyValuePair<number, LyricsEntry>[] = this.currentVoiceEntry.LyricsEntries.ToArray();
+                    for (var idx: number = 0, len = lyricsEntries.length; idx < len; ++idx) {
+                        var lyricsEntry: KeyValuePair<number, LyricsEntry> = lyricsEntries[idx];
+                        if (!tieStartNote.ParentVoiceEntry.LyricsEntries.ContainsKey(lyricsEntry.Key)) {
+                            tieStartNote.ParentVoiceEntry.LyricsEntries.Add(lyricsEntry.Key, lyricsEntry.Value);
+                            lyricsEntry.Value.Parent = tieStartNote.ParentVoiceEntry;
+                        }
+                    }
+                    if (maxTieNoteFraction < this.currentStaffEntry.Timestamp + this.currentNote.Length)
+                        maxTieNoteFraction = this.currentStaffEntry.Timestamp + this.currentNote.Length;
+                    this.currentVoiceEntry.Notes.Remove(this.currentNote);
+                }
+            }
+        }
+    }
+    private getNextAvailableNumberForTie(): number {
+        var keys: List<number> = new List<number>(this.openTieDict.Keys.ToList());
+        if (keys.Count == 0)
+            return 1;
+        keys.Sort();
+        for (var i: number = 0; i < keys.Count; i++) {
+            if (i + 1 != keys[i])
+                return i + 1;
+        }
+        return keys[keys.Count - 1] + 1;
+    }
+    private findCurrentNoteInTieDict(candidateNote: Note): number {
+        var openTieDictArr: KeyValuePair<number, Tie>[] = this.openTieDict.ToArray();
+        for (var idx: number = 0, len = openTieDictArr.length; idx < len; ++idx) {
+            var keyValuePair: KeyValuePair<number, Tie> = openTieDictArr[idx];
+            if (keyValuePair.Value.Start.Pitch.FundamentalNote == candidateNote.Pitch.FundamentalNote && keyValuePair.Value.Start.Pitch.Octave == candidateNote.Pitch.Octave) {
+                return keyValuePair.Key;
+            }
+        }
+        return -1;
+    }
+    private getTupletNoteDurationFromType(xmlNode: IXmlElement): Fraction {
+        if (xmlNode.Element("type") != null) {
+            var typeNode: IXmlElement = xmlNode.Element("type");
+            if (typeNode != null) {
+                var type: string = typeNode.Value;
+                try {
+                    return this.getNoteDurationFromType(type);
+                }
+                catch (e) {
+                    var errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid note duration.");
+                    this.musicSheet.SheetErrors.AddErrorMessageInTempList(errorMsg);
+                    throw new MusicSheetReadingException("", e, 0);
+                }
+
+            }
+        }
+        return null;
+    }
+}