Browse Source

Merge branch 'develop' of github.com:opensheetmusicdisplay/opensheetmusicdisplay into experiment/refactor

anita2822 4 years ago
parent
commit
19d2eba1c6

+ 35 - 0
CHANGELOG.md

@@ -1,3 +1,38 @@
+## [0.8.3](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.8.2...0.8.3) (2020-07-28)
+
+
+### Bug Fixes
+
+* **MetronomeMark:** render correct beat unit (e.g. half instead of quarter). fix [#828](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/828) ([b2fb539](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/b2fb539f800e83c1adbb9bcdee76acc3d1e78f2c))
+* **Parsing:** fix missing default-y value for credit-words causing parse error ([4773487](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/47734879482a89068286cbd79f63b00db46ebff4)), closes [#62](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/62)
+* **Repeat:** don't draw vertical line at the beginning of a system for single staff systems ([#834](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/834)) ([e556978](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/e55697812581e1f1b60f391cdaadfdca38fca2d5))
+* **Slur:** Slurs avoid note collision ([c114296](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/c114296369e2705ac7e528d00129c58d4d7a5b39))
+* **Slur:** Slurs avoid note collision ([#802](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/802)) ([18397f2](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/18397f2be60f1ea4f134d807c437bcbdc5eb5090))
+* **Slur:** Tweaks to slur rendering from matt-uib ([b6d628f](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/b6d628f0b02296979632e1ac3a5fe5491f4c865a))
+* **Tabs:** by default, don't draw tuplet numbers in tabs ([#805](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/805)) ([89a9f0f](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/89a9f0f5ab970dc88c02438c078b786f5b83aee4))
+* **Transparency:** ledger lines of invisible notes are now invisible as well. ([#799](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/799)) ([d73e8ac](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/d73e8acddfeddb1d68f8e927d541d8ed3819ce40))
+* fixed right alignment (and also x-center alingment) of text lines in multi-line-label (e.g. for Composer on the right side) ([3988fde](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/3988fdec09bf698f62cb7c39a0b07d324f05a705))
+
+
+### Features
+
+* **Articulation:** render marcato, up and down ([8104dfa](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/8104dfa415a1b8abc915bac2cfd761402819fb79))
+* **Label:** Add support for no-print on part names ([c3e6c8d](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/c3e6c8d878ab2ed64afbcc562ef6cc2cb7af61a1))
+* **Label:** Add support for multiline labels ([#801](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/801)) ([eda4cbf](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/eda4cbf40b17f8781f45bb1abf40730dc80a2640))
+* **Lyrics:** Spacing: add maximum measure elongation factor in EngravingRules. ([b575746](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/b575746af1318916984b5a2be307ca99aaf1ac02))
+* **MultipleRest:** Render multiple measure rests. add option to not render them. ([#506](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/506)) ([7a04814](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/7a04814e7f77a3c59006116a95ba7d3010f489f6))
+* **Noteheads:** support breve type (brevis) ([#803](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/803)) ([09fc5e7](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/09fc5e74e46edb4dbc37349638fe1003e99190ec))
+* **Options:** add EngravingRules.PageBottomExtraWhiteSpace ([#788](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/788)). merge [#822](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/822) ([6e5ae88](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/6e5ae88b4b84dbd6c0296deccbd5969f6c4ebc9e))
+* **Options:** add new EngravingRules for notationFontScale, stemWidth, staffLineColor ([#836](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/836)) ([2e1ab25](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/2e1ab25fe02cc88af06e334098eae5aef090c3df))
+* **Options:** add option to draw measure numbers only at the start of a system (line) ([ca9b88e](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/ca9b88ee7d135f0ff830e65320cce0e4a993d5d6)), closes [#59](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/59)
+* **Options:** add option to not render time signatures (drawTimeSignatures: false). ([#793](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/793)) ([21b089b](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/21b089b4841c648ec22f7c34c685dbaa945972d6))
+* **Options:** set default ledger line color to black (instead of grey). set in EngravingRules.LedgerLineColorDefault ([cb31c98](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/cb31c9817b91c72ed1758659b02939168d269ffe)), closes [#799](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/799)
+* **Spacing:** Tweaks to compress spacing more ([#820](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/820)) ([682f77c](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/682f77c613d764882a0d449d864a30417b6a7368))
+* **Spacing:** optimize vexflow voice spacing/scaling, refactor ([817762a](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/817762a593aeb8bd13b49e550203b28ece698843))
+* **Tabs:** Add Slides, Bends, Hammer-on and Pull-offs (PR [#839](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/839)) ([8de1400](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/8de1400ce0b51c55050d9961d1d2db16a0cb2d37))
+
+
+
 ## [0.8.2](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.8.1...0.8.2) (2020-06-13)
 
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "opensheetmusicdisplay",
-  "version": "0.8.2",
+  "version": "0.8.3",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
   "main": "build/opensheetmusicdisplay.min.js",
   "typings": "build/dist/src/",

+ 10 - 0
src/Common/Enums/TieTypes.ts

@@ -0,0 +1,10 @@
+/**
+ * The types of ties available
+ */
+export enum TieTypes {
+    "SIMPLE" = "",
+    "HAMMERON" = "H",
+    "PULLOFF" = "P",
+    "SLIDE" = "S",
+    "TAPPING" = "T"
+}

+ 1 - 0
src/Common/Enums/index.ts

@@ -3,3 +3,4 @@
 export * from "./FontStyles";
 export * from "./Fonts";
 export * from "./TextAlignment";
+export * from "./TieTypes";

+ 9 - 0
src/MusicalScore/Graphical/EngravingRules.ts

@@ -116,6 +116,8 @@ export class EngravingRules {
     private tupletNumberYOffset: number;
     private labelMarginBorderFactor: number;
     private tupletVerticalLineLength: number;
+    private tupletNumbersInTabs: boolean;
+
     private repetitionEndingLabelHeight: number;
     private repetitionEndingLabelXOffset: number;
     private repetitionEndingLabelYOffset: number;
@@ -379,6 +381,7 @@ export class EngravingRules {
         this.tupletNumberYOffset = 0.5;
         this.labelMarginBorderFactor = 0.1;
         this.tupletVerticalLineLength = 0.5;
+        this.tupletNumbersInTabs = false; // disabled by default, nonstandard in tabs, at least how we show them in non-tabs.
 
         // Slur and Tie variables
         this.bezierCurveStepSize = 1000;
@@ -1082,6 +1085,12 @@ export class EngravingRules {
     public set TupletVerticalLineLength(value: number) {
         this.tupletVerticalLineLength = value;
     }
+    public get TupletNumbersInTabs(): boolean {
+        return this.tupletNumbersInTabs;
+    }
+    public set TupletNumbersInTabs(value: boolean) {
+        this.tupletNumbersInTabs = value;
+    }
     public get RepetitionEndingLabelHeight(): number {
         return this.repetitionEndingLabelHeight;
     }

+ 3 - 0
src/MusicalScore/Graphical/GraphicalTie.ts

@@ -21,6 +21,9 @@ export class GraphicalTie {
     public get StartNote(): GraphicalNote {
         return this.startNote;
     }
+    public get Tie(): Tie {
+        return this.tie;
+    }
     public set StartNote(value: GraphicalNote) {
         this.startNote = value;
     }

+ 2 - 2
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -438,7 +438,7 @@ export abstract class MusicSheetCalculator {
      * @param tie
      * @param tieIsAtSystemBreak
      */
-    protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
+    protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
         throw new Error("abstract, not implemented");
     }
 
@@ -2436,7 +2436,7 @@ export abstract class MusicSheetCalculator {
                                     graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine !==
                                     graphicalTie.EndNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine
                                 );
-                                this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak);
+                                this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak, measure.ParentStaff.isTab);
                             }
                         }
                     }

+ 26 - 22
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -544,6 +544,7 @@ export class VexFlowConverter {
      */
     public static CreateTabNote(gve: GraphicalVoiceEntry): Vex.Flow.TabNote {
         const tabPositions: {str: number, fret: number}[] = [];
+        const notes: GraphicalNote[] = gve.notes.reverse();
         const tabPhrases: { type: number, text: string, width: number }[] = [];
         const frac: Fraction = gve.notes[0].graphicalNoteLength;
         const isTuplet: boolean = gve.notes[0].sourceNote.NoteTuplet !== undefined;
@@ -554,26 +555,29 @@ export class VexFlowConverter {
             const tabNote: TabNote = note.sourceNote as TabNote;
             const tabPosition: {str: number, fret: number} = {str: tabNote.StringNumber, fret: tabNote.FretNumber};
             tabPositions.push(tabPosition);
-            tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {
-                let phraseText: string;
-                const phraseStep: number = bend.bendalter - tabPosition.fret;
-                if (phraseStep > 1) {
-                    phraseText = "Full";
-                } else if (phraseStep === 1) {
-                    phraseText = "1/2";
-                } else {
-                    phraseText = "1/4";
-                }
-                if (bend.direction === "up") {
-                    tabPhrases.push({type: Vex.Flow.Bend.UP, text: phraseText, width: 10});
-                } else {
-                    tabPhrases.push({type: Vex.Flow.Bend.DOWN, text: phraseText, width: 10});
-                }
-            });
+            if (tabNote.BendArray) {
+                tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {
+                    let phraseText: string;
+                    const phraseStep: number = bend.bendalter - tabPosition.fret;
+                    if (phraseStep > 1) {
+                        phraseText = "Full";
+                    } else if (phraseStep === 1) {
+                        phraseText = "1/2";
+                    } else {
+                        phraseText = "1/4";
+                    }
+                    if (bend.direction === "up") {
+                        tabPhrases.push({type: Vex.Flow.Bend.UP, text: phraseText, width: 10});
+                    } else {
+                        tabPhrases.push({type: Vex.Flow.Bend.DOWN, text: phraseText, width: 10});
+                    }
+                });
+            }
 
             if (tabNote.VibratoStroke) {
                 tabVibrato = true;
             }
+
             if (numDots < note.numberOfDots) {
                 numDots = note.numberOfDots;
             }
@@ -581,10 +585,16 @@ export class VexFlowConverter {
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             duration += "d";
         }
+
         const vfnote: Vex.Flow.TabNote = new Vex.Flow.TabNote({
             duration: duration,
             positions: tabPositions,
         });
+
+        for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
+            (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
+        }
+
         tabPhrases.forEach(function(phrase: { type: number, text: string, width: number }): void {
             if (phrase.type === Vex.Flow.Bend.UP) {
                 vfnote.addModifier (new Vex.Flow.Bend(phrase.text, false));
@@ -592,12 +602,6 @@ export class VexFlowConverter {
                 vfnote.addModifier (new Vex.Flow.Bend(phrase.text, true));
             }
         });
-        // does not work well to add phrases as array
-        /*
-        if (tabPhrases.length > 0) {
-           vfnote.addModifier (new Vex.Flow.Bend(undefined, undefined, tabPhrases), 1);
-        }
-        */
         if (tabVibrato) {
             vfnote.addModifier(new Vex.Flow.Vibrato());
         }

+ 2 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts

@@ -30,7 +30,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
     // The pitch of this note as given by VexFlowConverter.pitch
     public vfpitch: [string, string, ClefInstruction];
     // The corresponding VexFlow StaveNote (plus its index in the chord)
-    public vfnote: [Vex.Flow.StaveNote, number];
+    public vfnote: [Vex.Flow.StemmableNote, number];
     // The current clef
     private clef: ClefInstruction;
 
@@ -67,7 +67,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
      * @param note
      * @param index
      */
-    public setIndex(note: Vex.Flow.StaveNote, index: number): void {
+    public setIndex(note: Vex.Flow.StemmableNote, index: number): void {
         this.vfnote = [note, index];
     }
 

+ 13 - 11
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -57,6 +57,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         this.resetLayout();
     }
 
+    public isTabMeasure: boolean = false;
     /** octaveOffset according to active clef */
     public octaveOffset: number = 3;
     /** The VexFlow Voices in the measure */
@@ -556,17 +557,18 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 beam.setContext(ctx).draw();
             }
         }
-        if (this.autoTupletVfBeams) {
-            for (const beam of this.autoTupletVfBeams) {
-                beam.setContext(ctx).draw();
+        if (!this.isTabMeasure || this.rules.TupletNumbersInTabs) {
+            if (this.autoTupletVfBeams) {
+                for (const beam of this.autoTupletVfBeams) {
+                    beam.setContext(ctx).draw();
+                }
             }
-        }
-
-        // Draw tuplets
-        for (const voiceID in this.vftuplets) {
-            if (this.vftuplets.hasOwnProperty(voiceID)) {
-                for (const tuplet of this.vftuplets[voiceID]) {
-                    tuplet.setContext(ctx).draw();
+            // Draw tuplets
+            for (const voiceID in this.vftuplets) {
+                if (this.vftuplets.hasOwnProperty(voiceID)) {
+                    for (const tuplet of this.vftuplets[voiceID]) {
+                        tuplet.setContext(ctx).draw();
+                    }
                 }
             }
         }
@@ -1161,7 +1163,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         for ( const vfStaffEntry of this.staffEntries ) {
             for ( const gVoiceEntry of vfStaffEntry.graphicalVoiceEntries) {
                 for ( const gnote of gVoiceEntry.notes) {
-                    const vfnote: [StaveNote, number] = (gnote as VexFlowGraphicalNote).vfnote;
+                    const vfnote: [StemmableNote , number] = (gnote as VexFlowGraphicalNote).vfnote;
                     if (!vfnote || !vfnote[0]) {
                         continue;
                     }

+ 45 - 9
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -51,6 +51,7 @@ import { EngravingRules } from "../EngravingRules";
 import { VexflowStafflineNoteCalculator } from "./VexflowStafflineNoteCalculator";
 import { NoteTypeHandler } from "../../VoiceData/NoteType";
 import { VexFlowConverter } from "./VexFlowConverter";
+import { TabNote } from "../../VoiceData/TabNote";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
@@ -466,19 +467,20 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    * Calculate the shape (Bezier curve) for this tie.
    * @param tie
    * @param tieIsAtSystemBreak
+   * @param isTab Whether this tie is for a tab note (guitar tabulature)
    */
-  protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
+  protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
     const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
     const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
 
-    let vfStartNote: Vex.Flow.StaveNote = undefined;
+    let vfStartNote: Vex.Flow.StemmableNote  = undefined;
     let startNoteIndexInTie: number = 0;
     if (startNote && startNote.vfnote && startNote.vfnote.length >= 2) {
       vfStartNote = startNote.vfnote[0];
       startNoteIndexInTie = startNote.vfnote[1];
     }
 
-    let vfEndNote: Vex.Flow.StaveNote = undefined;
+    let vfEndNote: Vex.Flow.StemmableNote  = undefined;
     let endNoteIndexInTie: number = 0;
     if (endNote && endNote.vfnote && endNote.vfnote.length >= 2) {
       vfEndNote = endNote.vfnote[0];
@@ -507,12 +509,46 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     } else {
       // normal case
       if (vfStartNote || vfEndNote) { // one of these must be not null in Vexflow
-        const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-          first_indices: [startNoteIndexInTie],
-          first_note: vfStartNote,
-          last_indices: [endNoteIndexInTie],
-          last_note: vfEndNote
-        });
+        let vfTie: any;
+        if (isTab) {
+          if (tie.Tie.Type === "S") {
+            //calculate direction
+            const startTieNote: TabNote = <TabNote> tie.StartNote.sourceNote;
+            const endTieNote: TabNote = <TabNote> tie.EndNote.sourceNote;
+            let slideDirection: number = 1;
+            if (startTieNote.FretNumber > endTieNote.FretNumber) {
+              slideDirection = -1;
+            }
+            vfTie = new Vex.Flow.TabSlide(
+              {
+                first_indices: [startNoteIndexInTie],
+                first_note: vfStartNote,
+                last_indices: [endNoteIndexInTie],
+                last_note: vfEndNote,
+              },
+              slideDirection
+            );
+          } else {
+            vfTie = new Vex.Flow.TabTie(
+              {
+                first_indices: [startNoteIndexInTie],
+                first_note: vfStartNote,
+                last_indices: [endNoteIndexInTie],
+                last_note: vfEndNote,
+              },
+              tie.Tie.Type
+            );
+          }
+
+        } else { // not Tab (guitar), normal StaveTie
+          vfTie = new Vex.Flow.StaveTie({
+            first_indices: [startNoteIndexInTie],
+            first_note: vfStartNote,
+            last_indices: [endNoteIndexInTie],
+            last_note: vfEndNote
+          });
+        }
+
         const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
         measure.vfTies.push(vfTie);
       }

+ 4 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowTabMeasure.ts

@@ -14,6 +14,7 @@ import log from "loglevel";
 export class VexFlowTabMeasure extends VexFlowMeasure {
     constructor(staff: Staff, sourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) {
         super(staff, sourceMeasure, staffLine);
+        this.isTabMeasure = true;
     }
 
     /**
@@ -47,7 +48,9 @@ export class VexFlowTabMeasure extends VexFlowMeasure {
             }
         }
 
-        this.finalizeTuplets();
+        if (this.rules.TupletNumbersInTabs) { // default false, don't show tuplets in tab measures
+            this.finalizeTuplets();
+        }
 
         const voices: Voice[] = this.getVoicesWithinMeasure();
 

+ 25 - 4
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -8,6 +8,7 @@ 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";
@@ -190,9 +191,23 @@ export class VoiceGenerator {
         // check for Ties - must be the last check
         const tiedNodeList: IXmlElement[] = notationNode.elements("tied");
         if (tiedNodeList.length > 0) {
-          this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
+          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 slides, they are the same as Ties but with a different connection
+        const technicalNode: IXmlElement = notationNode.element("technical");
+        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) {
@@ -757,7 +772,7 @@ export class VoiceGenerator {
     }
   }
 
-  private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction): void {
+  private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, tieType: TieTypes): void {
     if (tieNodeList) {
       if (tieNodeList.length === 1) {
         const tieNode: IXmlElement = tieNodeList[0];
@@ -770,7 +785,7 @@ export class VoiceGenerator {
                 delete this.openTieDict[num];
               }
               const newTieNumber: number = this.getNextAvailableNumberForTie();
-              const tie: Tie = new Tie(this.currentNote);
+              const tie: Tie = new Tie(this.currentNote, tieType);
               this.openTieDict[newTieNumber] = tie;
             } else if (type === "stop") {
               const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
@@ -830,8 +845,14 @@ export class VoiceGenerator {
     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 +key;
+        } else {
+          if (tieTabNote.StringNumber === tieCandidateNote.StringNumber) {
+            return +key;
+          }
         }
       }
     }

+ 8 - 1
src/MusicalScore/VoiceData/Tie.ts

@@ -1,22 +1,29 @@
 import {Note} from "./Note";
 import { Fraction } from "../../Common/DataObjects/Fraction";
 import { Pitch } from "../../Common/DataObjects/Pitch";
+import { TieTypes } from "../../Common/Enums/";
 
 /**
  * A [[Tie]] connects two notes of the same pitch and name, indicating that they have to be played as a single note.
  */
 export class Tie {
 
-    constructor(note: Note) {
+    constructor(note: Note, type: TieTypes) {
         this.AddNote(note);
+        this.type = type;
     }
 
     private notes: Note[] = [];
+    private type: TieTypes;
 
     public get Notes(): Note[] {
         return this.notes;
     }
 
+    public get Type(): TieTypes {
+        return this.type;
+    }
+
     public get StartNote(): Note {
         return this.notes[0];
     }

+ 1 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -27,7 +27,7 @@ import { ITransposeCalculator } from "../MusicalScore/Interfaces";
  * After the constructor, use load() and render() to load and render a MusicXML file.
  */
 export class OpenSheetMusicDisplay {
-    private version: string = "0.8.2-dev"; // getter: this.Version
+    private version: string = "0.8.3-dev"; // getter: this.Version
     // at release, bump version and change to -release, afterwards to -dev again
 
     /**