瀏覽代碼

Merge branch 'develop' into greenkeeper/webpack-4.2.0

Benjamin Giesinger 7 年之前
父節點
當前提交
af2ccb1c04
共有 37 個文件被更改,包括 2682 次插入977 次删除
  1. 2 2
      demo/index.html
  2. 15 11
      demo/index.js
  3. 55 3
      external/vexflow/vexflow.d.ts
  4. 2 1
      src/Common/DataObjects/Pitch.ts
  5. 11 0
      src/Common/Strings/StringUtil.ts
  6. 1 0
      src/MusicalScore/Graphical/GraphicalLyricEntry.ts
  7. 1 0
      src/MusicalScore/Graphical/GraphicalLyricWord.ts
  8. 18 0
      src/MusicalScore/Graphical/GraphicalStaffEntry.ts
  9. 424 56
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  10. 13 0
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  11. 6 3
      src/MusicalScore/Graphical/MusicSystem.ts
  12. 24 0
      src/MusicalScore/Graphical/StaffLine.ts
  13. 98 5
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  14. 12 9
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts
  15. 128 60
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  16. 314 184
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  17. 23 0
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  18. 6 2
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts
  19. 23 11
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  20. 8 0
      src/MusicalScore/Interfaces/IAfterSheetReadingModule.ts
  21. 2 1
      src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts
  22. 25 29
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  23. 21 26
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  24. 31 0
      src/MusicalScore/ScoreIO/MusicSymbolModuleFactory.ts
  25. 195 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts
  26. 140 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/LyricsReader.ts
  27. 99 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionCalculator.ts
  28. 380 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts
  29. 576 566
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  30. 6 3
      src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts
  31. 8 1
      src/MusicalScore/VoiceData/Lyrics/LyricsEntry.ts
  32. 4 0
      src/MusicalScore/VoiceData/Note.ts
  33. 8 1
      src/MusicalScore/VoiceData/SourceMeasure.ts
  34. 1 1
      src/OSMD/OSMD.ts
  35. 1 1
      test/data/Beethoven_AnDieFerneGeliebte.xml
  36. 二進制
      test/data/Cornelius_P_Christbaum_Opus_8_1_1865.mxl
  37. 1 1
      test/data/Mozart_DasVeilchen.xml

+ 2 - 2
demo/index.html

@@ -25,9 +25,9 @@
     </div>
     <div class="column">
         <h3 class="ui header">Render backend:</h3>
-        <select class="ui dropdown" id="backend-select" value="canvas">
-            <option value="canvas">Canvas</option>>
+        <select class="ui dropdown" id="backend-select" value="svg">
             <option value="svg">SVG</option>
+            <option value="canvas">Canvas</option>>
         </select>
     </div>
     <div class="column">

+ 15 - 11
demo/index.js

@@ -8,26 +8,27 @@ import { OSMD } from '../src/OSMD/OSMD';
     var folder = process.env.STATIC_FILES_SUBFOLDER ? process.env.STATIC_FILES_SUBFOLDER + "/" : "",
     // The available demos
         demos = {
-            "Beethoven - AnDieFerneGeliebte": "Beethoven_AnDieFerneGeliebte.xml",
+            "Beethoven - An die ferne Geliebte": "Beethoven_AnDieFerneGeliebte.xml",
             "M. Clementi - Sonatina Op.36 No.1 Pt.1": "MuzioClementi_SonatinaOpus36No1_Part1.xml",
             "M. Clementi - Sonatina Op.36 No.1 Pt.2": "MuzioClementi_SonatinaOpus36No1_Part2.xml",
             "M. Clementi - Sonatina Op.36 No.3 Pt.1": "MuzioClementi_SonatinaOpus36No3_Part1.xml",
             "M. Clementi - Sonatina Op.36 No.3 Pt.2": "MuzioClementi_SonatinaOpus36No3_Part2.xml",
+            "J.S. Bach - Praeludium In C Dur BWV846 1": "JohannSebastianBach_PraeludiumInCDur_BWV846_1.xml",
             "J.S. Bach - Air": "JohannSebastianBach_Air.xml",
-            "G.P. Telemann - Sonata, TWV 40:102 - 1. Dolce": "TelemannWV40.102_Sonate-Nr.1.1-Dolce.xml",
             "C. Gounod - Meditation": "CharlesGounod_Meditation.xml",
-            "J.S. Bach - Praeludium In C Dur BWV846 1": "JohannSebastianBach_PraeludiumInCDur_BWV846_1.xml",
             "J. Haydn - Concertante Cello": "JosephHaydn_ConcertanteCello.xml",
+            "Mozart - An Chloe": "Mozart_AnChloe.xml",
+            "Mozart - Das Veilchen": "Mozart_DasVeilchen.xml",
+            "Mozart - Trio": "MozartTrio.mxl",
             "S. Joplin - Elite Syncopations": "ScottJoplin_EliteSyncopations.xml",
             "S. Joplin - The Entertainer": "ScottJoplin_The_Entertainer.xml",
             "ActorPreludeSample": "ActorPreludeSample.xml",
-            "an chloe - mozart": "an chloe - mozart.xml",
-            "das veilchen - mozart": "das veilchen - mozart.xml",
-            "Dichterliebe01": "Dichterliebe01.xml",
-            "mandoline - debussy": "mandoline - debussy.xml",
-            "MozartTrio": "MozartTrio.mxl",
-            "Cornelius P. Christbaum Opus 8.1": "Cornelius_P_Christbaum_Opus_8_1_1865.mxl",
+            "R. Schumann - Dichterliebe": "Dichterliebe01.xml",
+            "C. Debussy - Mandoline": "Debussy_Mandoline.xml",
             "France Levasseur - Parlez Mois": "Parlez-moi.mxl",
+            "Telemann - Sonate-Nr.1.1-Dolce": "TelemannWV40.102_Sonate-Nr.1.1-Dolce.xml",
+            "Telemann - Sonate-Nr.1.2-Allegro": "TelemannWV40.102_Sonate-Nr.1.2-Allegro-F-Dur.xml",
+            "Saltarello": "Saltarello.mxl",
         },
 
         zoom = 1.0,
@@ -80,6 +81,9 @@ import { OSMD } from '../src/OSMD/OSMD';
         }
         select.onchange = selectOnChange;
 
+        // Pre-select default music piece
+        select.value = "MuzioClementi_SonatinaOpus36No1_Part1.xml";
+
         custom.appendChild(document.createTextNode("Custom"));
 
         // Create zoom controls
@@ -93,7 +97,7 @@ import { OSMD } from '../src/OSMD/OSMD';
         };
 
         // Create OSMD object and canvas
-        osmdObj = new OSMD(canvas, false);
+        osmdObj = new OSMD(canvas, false, backendSelect.value);
         osmdObj.setLogLevel('info');
         document.body.appendChild(canvas);
 
@@ -189,7 +193,7 @@ import { OSMD } from '../src/OSMD/OSMD';
             function() {
                 return onLoadingEnd(isCustom);
             }, function(e) {
-                error("Error rendering sheet: " + e);
+                error("Error rendering sheet: " + process.env.DEBUG ? e.stack : e);
                 onLoadingEnd(isCustom);
             }
         );

+ 55 - 3
external/vexflow/vexflow.d.ts

@@ -27,6 +27,16 @@ declare namespace Vex {
             public getW(): number;
 
             public getH(): number;
+
+            public draw(ctx: Vex.Flow.RenderContext) : void;            
+        }
+
+        export class Tickable {
+            public reset(): void;
+
+            public setStave(stave: Stave);
+
+            public getBoundingBox(): BoundingBox;
         }
 
         export class Voice {
@@ -34,6 +44,10 @@ declare namespace Vex {
 
             public static Mode: any;
 
+            public context: RenderContext;
+
+            public tickables: Tickable[];
+
             public getBoundingBox(): BoundingBox;
 
             public setStave(stave: Stave): Voice;
@@ -47,7 +61,19 @@ declare namespace Vex {
             public draw(ctx: any, stave: Stave): void;
         }
 
-        export class StaveNote {
+        export class Note extends Tickable {
+        }
+
+        export class Stem {
+            public static UP: number;
+            public static DOWN: number;
+        }
+        export class StemmableNote extends Note {
+            public getStemDirection(): number;
+            public setStemDirection(direction: number): StemmableNote;
+        }
+
+        export class StaveNote extends StemmableNote{
             constructor(note_struct: any);
 
             public getNoteHeadBounds(): any;
@@ -56,10 +82,14 @@ declare namespace Vex {
 
             public getNoteHeadEndX(): number;
 
+            public getGlyphWidth(): number;
+
             public addAccidental(index: number, accidental: Accidental): StaveNote;
 
             public addAnnotation(index: number, annotation: Annotation): StaveNote;
 
+            public addModifier(index: number, modifier: Modifier): StaveNote;
+
             public setStyle(style: any): void;
 
             public addDotToAll(): void;
@@ -82,6 +112,10 @@ declare namespace Vex {
 
             public getX(): number;
 
+            public setBegBarType(type: any): Stave;
+
+            public setEndBarType(type: any): Stave;
+
             public addClef(clefSpec: string, size: any, annotation: any, position: any): void;
 
             public setEndClef(clefSpec: string, size: any, annotation: any): void;
@@ -95,6 +129,8 @@ declare namespace Vex {
             public setWidth(width: number): Stave;
 
             public getNoteStartX(): number;
+            
+            public getModifierXShift(): number;
 
             public getNoteEndX(): number;
 
@@ -111,6 +147,7 @@ declare namespace Vex {
             public getLineForY(y: number): number;
 
             public getModifiers(pos: any, cat: any): Clef[]; // FIXME
+            
             public setContext(ctx: RenderContext): Stave;
 
             public addModifier(mod: any, pos: any): void;
@@ -128,9 +165,20 @@ declare namespace Vex {
             public getWidth(): number;
 
             public getPadding(index: number): number;
+
+            public getPosition(): number;
+
+            public setPosition(position: number): Modifier;
         }
 
         export class StaveModifier extends Modifier {
+            public static Position: any;
+            
+            public getPosition(): number;
+        }
+
+        export class Repetition extends StaveModifier {
+            constructor(type: any, x: number, y_shift: number);
         }
 
         export class Clef extends StaveModifier {
@@ -162,10 +210,10 @@ declare namespace Vex {
             public getContext(): CanvasContext|SVGContext;
         }
 
-        export class TimeSignature {
+        export class TimeSignature extends StaveModifier {
             constructor(timeSpec: string, customPadding?: any);
         }
-        export class KeySignature {
+        export class KeySignature extends StaveModifier {
             constructor(keySpec: string, cancelKeySpec: string, alterKeySpec?: string);
         }
 
@@ -177,6 +225,10 @@ declare namespace Vex {
             constructor(type: string);
         }
 
+        export class Articulation extends Modifier {
+            constructor(type: string);
+        }
+        
         export class Beam {
             constructor(notes: StaveNote[], auto_stem: boolean);
 

+ 2 - 1
src/Common/DataObjects/Pitch.ts

@@ -14,7 +14,8 @@ export enum AccidentalEnum {
     FLAT = -1,
     NONE = 0,
     SHARP = 1,
-    DOUBLESHARP = 2
+    DOUBLESHARP = 2,
+    NATURAL = 3
 }
 
 // This class represents a musical note. The middle A (440 Hz) lies in the octave with the value 1.

+ 11 - 0
src/Common/Strings/StringUtil.ts

@@ -0,0 +1,11 @@
+export class StringUtil {
+  public static StringContainsSeparatedWord(str: string, word: string): boolean {
+    if (str === word ||
+      str.search(" " + word) !== -1 ||
+      str.search(word + " ") !== -1 ||
+      str.search(word + ".") !== -1) {
+      return true;
+    }
+    return false;
+  }
+}

+ 1 - 0
src/MusicalScore/Graphical/GraphicalLyricEntry.ts

@@ -27,6 +27,7 @@ export class GraphicalLyricEntry {
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, staffHeight);
     }
 
+    // FIXME: This should actually be called LyricsEntry or be a function
     public get GetLyricsEntry(): LyricsEntry {
         return this.lyricsEntry;
     }

+ 1 - 0
src/MusicalScore/Graphical/GraphicalLyricWord.ts

@@ -35,6 +35,7 @@ export class GraphicalLyricWord {
     }
 
     private initialize(): void {
+        // FIXME: This is actually not needed in Javascript as we have dynamic memory allication?
         for (let i: number = 0; i < this.lyricWord.Syllables.length; i++) {
             this.graphicalLyricsEntries.push(undefined);
         }

+ 18 - 0
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -69,6 +69,10 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return this.lyricsEntries;
     }
 
+    public set LyricsEntries(value: GraphicalLyricEntry[]) {
+        this.lyricsEntries = value;
+    }
+
     /**
      * Calculate the absolute Timestamp.
      * @returns {Fraction}
@@ -349,4 +353,18 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
             }
         }
     }
+
+    // FIXME: implement
+    public hasOnlyRests(): boolean {
+        const hasOnlyRests: boolean = true;
+        for (const graphicalNotes of this.notes) {
+            for (const graphicalNote of graphicalNotes) {
+                const note: Note = graphicalNote.sourceNote;
+                if (!note.isRest()) {
+                    return false;
+                }
+            }
+        }
+        return hasOnlyRests;
+    }
 }

+ 424 - 56
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -14,7 +14,6 @@ import {GraphicalMusicPage} from "./GraphicalMusicPage";
 import {GraphicalNote} from "./GraphicalNote";
 import {Beam} from "../VoiceData/Beam";
 import {OctaveEnum} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
-import {LyricsEntry} from "../VoiceData/Lyrics/LyricsEntry";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
 import {OrnamentContainer} from "../VoiceData/OrnamentContainer";
 import {ArticulationEnum} from "../VoiceData/VoiceEntry";
@@ -51,6 +50,10 @@ import {OctaveShift} from "../VoiceData/Expressions/ContinuousExpressions/Octave
 import {Logging} from "../../Common/Logging";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import {CollectionUtil} from "../../Util/CollectionUtil";
+import {GraphicalLyricEntry} from "./GraphicalLyricEntry";
+import {GraphicalLyricWord} from "./GraphicalLyricWord";
+import {GraphicalLine} from "./GraphicalLine";
+import {Label} from "../Label";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -65,6 +68,8 @@ export abstract class MusicSheetCalculator {
     protected staffLinesWithLyricWords: StaffLine[] = [];
     protected staffLinesWithGraphicalExpressions: StaffLine[] = [];
 
+    protected graphicalLyricWords: GraphicalLyricWord[] = [];
+
     protected graphicalMusicSheet: GraphicalMusicSheet;
     protected rules: EngravingRules;
     protected symbolFactory: IGraphicalSymbolFactory;
@@ -194,6 +199,11 @@ export abstract class MusicSheetCalculator {
         // create new MusicSystems and StaffLines (as many as necessary) and populate them with Measures from measureList
         this.calculateMusicSystems();
 
+        this.formatMeasures();
+
+        // calculate all LyricWords Positions
+        this.calculateLyricsPosition();
+
         // Add some white space at the end of the piece:
         this.graphicalMusicSheet.MusicPages[0].PositionAndShape.BorderMarginBottom += 9;
 
@@ -222,6 +232,9 @@ export abstract class MusicSheetCalculator {
         this.graphicalMusicSheet.MinAllowedSystemWidth = minLength;
     }
 
+    protected formatMeasures(): void {
+        throw new Error("abstract, not implemented");
+    }
     /**
      * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
      * All staff entries are x-aligned throughout all the measures.
@@ -263,7 +276,7 @@ export abstract class MusicSheetCalculator {
         throw new Error("abstract, not implemented");
     }
 
-    protected handleVoiceEntryLyrics(lyricsEntries: Dictionary<number, LyricsEntry>, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry,
+    protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry,
                                      openLyricWords: LyricWord[]): void {
         throw new Error("abstract, not implemented");
     }
@@ -275,11 +288,23 @@ export abstract class MusicSheetCalculator {
 
     protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
                                             voiceEntry: VoiceEntry,
-                                            graphicalStaffEntry: GraphicalStaffEntry): void {
+                                            staffEntry: GraphicalStaffEntry): void {
         throw new Error("abstract, not implemented");
     }
 
-    protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
+  /**
+   * Adds a technical instruction at the given staff entry.
+   * @param technicalInstructions
+   * @param voiceEntry
+   * @param staffEntry
+   */
+  protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
+                                                  voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
+                                                    throw new Error("abstract, not implemented");
+  }
+
+
+  protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
         throw new Error("abstract, not implemented");
     }
 
@@ -318,13 +343,142 @@ export abstract class MusicSheetCalculator {
         throw new Error("abstract, not implemented");
     }
 
+    // FIXME: There are several HACKS in this function to make multiline lyrics work without the skyline.
+    // These need to be reverted once the skyline is available
     /**
      * Calculate the Lyrics YPositions for a single [[StaffLine]].
      * @param staffLine
      * @param lyricVersesNumber
      */
-    protected calculateSingleStaffLineLyricsPosition(staffLine: StaffLine, lyricVersesNumber: number[]): void {
-        throw new Error("abstract, not implemented");
+    protected calculateSingleStaffLineLyricsPosition(staffLine: StaffLine, lyricVersesNumber: number[]): GraphicalStaffEntry[] {
+        let numberOfVerses: number = 0;
+        // FIXME: There is no class SkyBottomLineCalculator -> Fix value
+        let lyricsStartYPosition: number = this.rules.StaffHeight + 6.0; // Add offset to prevent collision
+        const lyricsStaffEntriesList: GraphicalStaffEntry[] = [];
+        // const skyBottomLineCalculator: SkyBottomLineCalculator = new SkyBottomLineCalculator(this.rules);
+
+        // first find maximum Ycoordinate for the whole StaffLine
+        let len: number = staffLine.Measures.length;
+        for (let idx: number = 0; idx < len; ++idx) {
+            const measure: StaffMeasure = staffLine.Measures[idx];
+            const measureRelativePosition: PointF2D = measure.PositionAndShape.RelativePosition;
+            const len2: number = measure.staffEntries.length;
+            for (let idx2: number = 0; idx2 < len2; ++idx2) {
+                const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
+                if (staffEntry.LyricsEntries.length > 0) {
+                    lyricsStaffEntriesList.push(staffEntry);
+                    numberOfVerses = Math.max(numberOfVerses, staffEntry.LyricsEntries.length);
+
+                    // Position of Staffentry relative to StaffLine
+                    const staffEntryPositionX: number = staffEntry.PositionAndShape.RelativePosition.x +
+                                                        measureRelativePosition.x;
+
+                    let minMarginLeft: number = Number.MAX_VALUE;
+                    let maxMarginRight: number = Number.MAX_VALUE;
+
+                    // if more than one LyricEntry in StaffEntry, find minMarginLeft, maxMarginRight of all corresponding Labels
+                    for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
+                        const lyricsEntryLabel: GraphicalLabel = staffEntry.LyricsEntries[i].GraphicalLabel;
+                        minMarginLeft = Math.min(minMarginLeft, staffEntryPositionX + lyricsEntryLabel.PositionAndShape.BorderMarginLeft);
+                        maxMarginRight = Math.max(maxMarginRight, staffEntryPositionX + lyricsEntryLabel.PositionAndShape.BorderMarginRight);
+                    }
+
+                    // check BottomLine in this range and take the maximum between the two values
+                    // FIXME: There is no class SkyBottomLineCalculator -> Fix value
+                    // float bottomLineMax = skyBottomLineCalculator.getBottomLineMaxInRange(staffLine, minMarginLeft, maxMarginRight);
+                    const bottomLineMax: number = 0.0;
+                    lyricsStartYPosition = Math.max(lyricsStartYPosition, bottomLineMax);
+                }
+            }
+        }
+
+        let maxPosition: number = 4.0;
+        // iterate again through the Staffentries with LyricEntries
+        len = lyricsStaffEntriesList.length;
+        for (let idx: number = 0; idx < len; ++idx) {
+            const staffEntry: GraphicalStaffEntry = lyricsStaffEntriesList[idx];
+            // set LyricEntryLabel RelativePosition
+            for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
+                const lyricEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[i];
+                const lyricsEntryLabel: GraphicalLabel = lyricEntry.GraphicalLabel;
+
+                // read the verseNumber and get index of this number in the sorted LyricVerseNumbersList of Instrument
+                // eg verseNumbers: 2,3,4,6 => 1,2,3,4
+                const verseNumber: number = lyricEntry.GetLyricsEntry.VerseNumber;
+                const sortedLyricVerseNumberIndex: number = lyricVersesNumber.indexOf(verseNumber);
+                const firstPosition: number = lyricsStartYPosition + this.rules.LyricsHeight;
+
+                // Y-position calculated according to aforementioned mapping
+                let position: number = firstPosition + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
+                if (this.leadSheet) {
+                    position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
+                }
+                lyricsEntryLabel.PositionAndShape.RelativePosition = new PointF2D(0, position);
+                maxPosition = Math.max(maxPosition, position);
+            }
+        }
+
+        // update BottomLine (on the whole StaffLine's length)
+        if (lyricsStaffEntriesList.length > 0) {
+            /**
+             * HACK START
+             */
+            let additionalPageLength: number = 0;
+            maxPosition -= this.rules.StaffHeight;
+            let iterator: StaffLine = staffLine.NextStaffLine;
+            let systemMaxCount: number = 0;
+            while (iterator !== undefined) {
+                iterator.PositionAndShape.RelativePosition.y += maxPosition;
+                iterator = iterator.NextStaffLine;
+                systemMaxCount += maxPosition;
+                additionalPageLength += maxPosition;
+            }
+
+            systemMaxCount -= this.rules.BetweenStaffDistance;
+            let systemIterator: MusicSystem = staffLine.ParentMusicSystem.NextSystem;
+            while (systemIterator !== undefined) {
+                systemIterator.PositionAndShape.RelativePosition.y += systemMaxCount;
+                systemIterator = systemIterator.NextSystem;
+                additionalPageLength += systemMaxCount;
+            }
+            staffLine.ParentMusicSystem.Parent.PositionAndShape.BorderBottom += additionalPageLength;
+            // Update the instrument labels
+            staffLine.ParentMusicSystem.setMusicSystemLabelsYPosition();
+            /**
+             * HACK END
+             */
+            // const endX: number = staffLine.PositionAndShape.Size.width;
+            // const startX: number = lyricsStaffEntriesList[0].PositionAndShape.RelativePosition.x +
+            // lyricsStaffEntriesList[0].PositionAndShape.BorderMarginLeft +
+            // lyricsStaffEntriesList[0].parentMeasure.PositionAndShape.RelativePosition.x;
+            // FIXME: There is no class SkyBottomLineCalculator. This call should update the positions according to the last run
+            // skyBottomLineCalculator.updateBottomLineInRange(staffLine, startX, endX, maxPosition);
+        }
+        return lyricsStaffEntriesList;
+    }
+
+    /**
+     * calculates the dashes of lyric words and the extending underscore lines of syllables sung on more than one note.
+     * @param lyricsStaffEntries
+     */
+    protected calculateLyricsExtendsAndDashes(lyricsStaffEntries: GraphicalStaffEntry[]): void {
+        // iterate again to create now the extend lines and dashes for words
+        for (let idx: number = 0, len: number = lyricsStaffEntries.length; idx < len; ++idx) {
+            const staffEntry: GraphicalStaffEntry = lyricsStaffEntries[idx];
+            // set LyricEntryLabel RelativePosition
+            for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
+                const lyricEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[i];
+                // calculate LyricWord's Dashes and underscoreLine
+                if (lyricEntry.ParentLyricWord !== undefined &&
+                    lyricEntry.ParentLyricWord.GraphicalLyricsEntries[lyricEntry.ParentLyricWord.GraphicalLyricsEntries.length - 1] !== lyricEntry) {
+                    this.calculateSingleLyricWord(lyricEntry);
+                }
+                // calculate the underscore line extend if needed
+                if (lyricEntry.GetLyricsEntry.extend) {
+                    this.calculateLyricExtend(lyricEntry);
+                }
+            }
+        }
     }
 
     /**
@@ -340,7 +494,7 @@ export abstract class MusicSheetCalculator {
     }
 
     /**
-     * Calculate all the [[RepetitionInstruction]]s for a single [[SourceMeasure]].
+     * Calculate all the textual [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
      * @param repetitionInstruction
      * @param measureIndex
      */
@@ -485,8 +639,7 @@ export abstract class MusicSheetCalculator {
         if (!this.leadSheet) {
             this.calculateTempoExpressions();
         }
-        // calculate all LyricWords Positions
-        this.calculateLyricsPosition();
+
         // update all StaffLine's Borders
         // create temporary Object, just to call the methods (in order to avoid declaring them static)
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
@@ -733,10 +886,10 @@ export abstract class MusicSheetCalculator {
             this.handleVoiceEntryArticulations(voiceEntry.Articulations, voiceEntry, graphicalStaffEntry);
         }
         if (voiceEntry.TechnicalInstructions.length > 0) {
-            this.checkVoiceEntriesForTechnicalInstructions(voiceEntry, graphicalStaffEntry);
+            this.handleVoiceEntryTechnicalInstructions(voiceEntry.TechnicalInstructions, voiceEntry, graphicalStaffEntry);
         }
         if (voiceEntry.LyricsEntries.size() > 0) {
-            this.handleVoiceEntryLyrics(voiceEntry.LyricsEntries, voiceEntry, graphicalStaffEntry, openLyricWords);
+            this.handleVoiceEntryLyrics(voiceEntry, graphicalStaffEntry, openLyricWords);
         }
         if (voiceEntry.OrnamentContainer !== undefined) {
             this.handleVoiceEntryOrnaments(voiceEntry.OrnamentContainer, voiceEntry, graphicalStaffEntry);
@@ -967,8 +1120,8 @@ export abstract class MusicSheetCalculator {
         let rightStaffEntry: GraphicalStaffEntry = undefined;
         const numEntries: number = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
         const index: number = this.graphicalMusicSheet.GetInterpolatedIndexInVerticalContainers(timestamp);
-        const leftIndex: number = <number>Math.min(Math.floor(index), numEntries - 1);
-        const rightIndex: number = <number>Math.min(Math.ceil(index), numEntries - 1);
+        const leftIndex: number = Math.min(Math.floor(index), numEntries - 1);
+        const rightIndex: number = Math.min(Math.ceil(index), numEntries - 1);
         if (leftIndex < 0 || verticalIndex < 0) {
             return relative;
         }
@@ -1006,7 +1159,7 @@ export abstract class MusicSheetCalculator {
     protected getRelativeXPositionFromTimestamp(timestamp: Fraction): number {
         const numEntries: number = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
         const index: number = this.graphicalMusicSheet.GetInterpolatedIndexInVerticalContainers(timestamp);
-        const discreteIndex: number = <number>Math.max(0, Math.min(Math.round(index), numEntries - 1));
+        const discreteIndex: number = Math.max(0, Math.min(Math.round(index), numEntries - 1));
         const gse: GraphicalStaffEntry = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[discreteIndex].getFirstNonNullStaffEntry();
         const posX: number = gse.PositionAndShape.RelativePosition.x + gse.parentMeasure.PositionAndShape.RelativePosition.x;
         return posX;
@@ -1159,8 +1312,6 @@ export abstract class MusicSheetCalculator {
                     if (verticalContainer !== undefined) {
                         verticalContainer.StaffEntries[j] = graphicalStaffEntry;
                         graphicalStaffEntry.parentVerticalContainer = verticalContainer;
-                    } else {
-                        // TODO ?
                     }
                 }
             }
@@ -1326,13 +1477,6 @@ export abstract class MusicSheetCalculator {
         return measure;
     }
 
-    private checkVoiceEntriesForTechnicalInstructions(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
-        for (let idx: number = 0, len: number = voiceEntry.TechnicalInstructions.length; idx < len; ++idx) {
-            const technicalInstruction: TechnicalInstruction = voiceEntry.TechnicalInstructions[idx];
-            this.symbolFactory.createGraphicalTechnicalInstruction(technicalInstruction, graphicalStaffEntry);
-        }
-    }
-
     private checkNoteForAccidental(graphicalNote: GraphicalNote, accidentalCalculator: AccidentalCalculator, activeClef: ClefInstruction,
                                    octaveEnum: OctaveEnum, grace: boolean = false): void {
         let pitch: Pitch = graphicalNote.sourceNote.Pitch;
@@ -1585,55 +1729,279 @@ export abstract class MusicSheetCalculator {
         }
     }
 
-    // Commented because unused:
-    //private calculateFingering(): void {
-    //    for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
-    //        let graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
-    //        for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
-    //            let musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
-    //            for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
-    //                let staffLine: StaffLine = musicSystem.StaffLines[idx3];
-    //                let skyBottomLineCalculator: SkyBottomLineCalculator = new SkyBottomLineCalculator(this.rules);
-    //                for (let idx4: number = 0, len4: number = staffLine.Measures.length; idx4 < len4; ++idx4) {
-    //                    let measure: StaffMeasure = staffLine.Measures[idx4];
-    //                    let measureRelativePosition: PointF2D = measure.PositionAndShape.RelativePosition;
-    //                    for (let idx5: number = 0, len5: number = measure.staffEntries.length; idx5 < len5; ++idx5) {
-    //                        let staffEntry: GraphicalStaffEntry = measure.staffEntries[idx5];
-    //                        let hasTechnicalInstruction: boolean = false;
-    //                        for (let idx6: number = 0, len6: number = staffEntry.sourceStaffEntry.VoiceEntries.length; idx6 < len6; ++idx6) {
-    //                            let ve: VoiceEntry = staffEntry.sourceStaffEntry.VoiceEntries[idx6];
-    //                            if (ve.TechnicalInstructions.length > 0) {
-    //                                hasTechnicalInstruction = true;
-    //                                break;
-    //                            }
-    //                        }
-    //                        if (hasTechnicalInstruction) {
-    //                            this.layoutFingering(staffLine, skyBottomLineCalculator, staffEntry, measureRelativePosition);
-    //                        }
-    //                    }
-    //                }
-    //            }
-    //        }
-    //    }
-    //}
-
     private calculateLyricsPosition(): void {
+        const lyricStaffEntriesDict: Dictionary<StaffLine, GraphicalStaffEntry[]> = new Dictionary<StaffLine, GraphicalStaffEntry[]>();
+        // sort the lyriceVerseNumbers for every Instrument that has Lyrics
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.ParentMusicSheet.Instruments.length; idx < len; ++idx) {
             const instrument: Instrument = this.graphicalMusicSheet.ParentMusicSheet.Instruments[idx];
             if (instrument.HasLyrics && instrument.LyricVersesNumbers.length > 0) {
                 instrument.LyricVersesNumbers.sort();
             }
         }
+        // first calc lyrics text positions
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
             const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
             for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
                 const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
                 for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
                     const staffLine: StaffLine = musicSystem.StaffLines[idx3];
-                    this.calculateSingleStaffLineLyricsPosition(staffLine, staffLine.ParentStaff.ParentInstrument.LyricVersesNumbers);
+                    const lyricsStaffEntries: GraphicalStaffEntry[] =
+                        this.calculateSingleStaffLineLyricsPosition(staffLine, staffLine.ParentStaff.ParentInstrument.LyricVersesNumbers);
+                    lyricStaffEntriesDict.setValue(staffLine, lyricsStaffEntries);
+                    this.calculateLyricsExtendsAndDashes(lyricStaffEntriesDict.getValue(staffLine));
                 }
             }
         }
+        // the fill in the lyric word dashes and lyrics extends/underscores
+        for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
+            const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
+            for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
+                const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
+                for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
+                    const staffLine: StaffLine = musicSystem.StaffLines[idx3];
+                    this.calculateLyricsExtendsAndDashes(lyricStaffEntriesDict.getValue(staffLine));
+                }
+            }
+        }
+    }
+
+    /**
+     * This method calculates the dashes within the syllables of a LyricWord
+     * @param lyricEntry
+     */
+    private calculateSingleLyricWord(lyricEntry: GraphicalLyricEntry): void {
+        // const skyBottomLineCalculator: SkyBottomLineCalculator = new SkyBottomLineCalculator (this.rules);
+        const graphicalLyricWord: GraphicalLyricWord = lyricEntry.ParentLyricWord;
+        const index: number = graphicalLyricWord.GraphicalLyricsEntries.indexOf(lyricEntry);
+        let nextLyricEntry: GraphicalLyricEntry = undefined;
+        if (index >= 0) {
+            nextLyricEntry = graphicalLyricWord.GraphicalLyricsEntries[index + 1];
+        }
+        if (nextLyricEntry === undefined) {
+            return;
+        }
+        const startStaffLine: StaffLine = <StaffLine>lyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine;
+        const nextStaffLine: StaffLine = <StaffLine>nextLyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine;
+        const startStaffEntry: GraphicalStaffEntry = lyricEntry.StaffEntryParent;
+        const endStaffentry: GraphicalStaffEntry = nextLyricEntry.StaffEntryParent;
+
+        // if on the same StaffLine
+        if (lyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine === nextLyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine) {
+            // start- and End margins from the text Labels
+            const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+            startStaffEntry.PositionAndShape.RelativePosition.x +
+            lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+            const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
+            endStaffentry.PositionAndShape.RelativePosition.x +
+            nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
+            const y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
+            let numberOfDashes: number = 1;
+            if ((endX - startX) > this.rules.BetweenSyllabelMaximumDistance) {
+                numberOfDashes = Math.ceil((endX - startX) / this.rules.BetweenSyllabelMaximumDistance);
+            }
+            // check distance and create the adequate number of Dashes
+            if (numberOfDashes === 1) {
+                // distance between the two GraphicalLyricEntries is big for only one Dash, position in the middle
+                this.calculateSingleDashForLyricWord(startStaffLine, startX, endX, y);
+            } else {
+                // distance is big enough for more Dashes
+                // calculate the adequate number of Dashes from the distance between the two LyricEntries
+                // distance between the Dashes should be equal
+                this.calculateDashes(startStaffLine, startX, endX, y);
+            }
+        } else {
+            // start and end on different StaffLines
+            // start margin from the text Label until the End of StaffLine
+            const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+                startStaffEntry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+            const lastStaffMeasure: StaffMeasure = startStaffLine.Measures[startStaffLine.Measures.length - 1];
+            const endX: number = lastStaffMeasure.PositionAndShape.RelativePosition.x + lastStaffMeasure.PositionAndShape.Size.width;
+            let y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
+
+            // calculate Dashes for the first StaffLine
+            this.calculateDashes(startStaffLine, startX, endX, y);
+
+            // calculate Dashes for the second StaffLine (only if endStaffEntry isn't the first StaffEntry of the StaffLine)
+            if (!(endStaffentry === endStaffentry.parentMeasure.staffEntries[0] &&
+                    endStaffentry.parentMeasure === endStaffentry.parentMeasure.ParentStaffLine.Measures[0])) {
+                const secondStartX: number = nextStaffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x;
+                const secondEndX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
+                    endStaffentry.PositionAndShape.RelativePosition.x +
+                    nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
+                y = nextLyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
+                this.calculateDashes(nextStaffLine, secondStartX, secondEndX, y);
+            }
+        }
+    }
+
+    /**
+     * This method calculates Dashes for a LyricWord.
+     * @param staffLine
+     * @param startX
+     * @param endX
+     * @param y
+     */
+    private calculateDashes(staffLine: StaffLine, startX: number, endX: number, y: number): void {
+        let distance: number = endX - startX;
+        if (distance < this.rules.MinimumDistanceBetweenDashes) {
+            this.calculateSingleDashForLyricWord(staffLine, startX, endX, y);
+        } else {
+            // enough distance for more Dashes
+            const numberOfDashes: number = Math.floor(distance / this.rules.MinimumDistanceBetweenDashes);
+            const distanceBetweenDashes: number = distance / this.rules.MinimumDistanceBetweenDashes;
+            let counter: number = 0;
+
+            startX += distanceBetweenDashes / 2;
+            endX -= distanceBetweenDashes / 2;
+            while (counter <= Math.floor(numberOfDashes / 2.0) && endX > startX) {
+                distance = this.calculateRightAndLeftDashesForLyricWord(staffLine, startX, endX, y);
+                startX += distanceBetweenDashes;
+                endX -= distanceBetweenDashes;
+                counter++;
+            }
+
+            // if the remaining distance isn't big enough for two Dashes (another check would be if numberOfDashes is uneven),
+            // then put the last Dash in the middle of the remaining distance
+            if (distance > distanceBetweenDashes) {
+                this.calculateSingleDashForLyricWord(staffLine, startX, endX, y);
+            }
+        }
+    }
+
+    /**
+     * This method calculates a single Dash for a LyricWord, positioned in the middle of the given distance.
+     * @param {StaffLine} staffLine
+     * @param {number} startX
+     * @param {number} endX
+     * @param {number} y
+     */
+    private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
+        const dash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignment.CenterBottom);
+        dash.setLabelPositionAndShapeBorders();
+        staffLine.LyricsDashes.push(dash);
+        if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
+            this.staffLinesWithLyricWords.push(staffLine);
+        }
+        dash.PositionAndShape.Parent = staffLine.PositionAndShape;
+        const relative: PointF2D = new PointF2D(startX + (endX - startX) / 2, y);
+        dash.PositionAndShape.RelativePosition = relative;
+    }
+
+    /**
+     * Layouts the underscore line when a lyric entry is marked as extend
+     * @param {GraphicalLyricEntry} lyricEntry
+     */
+    private calculateLyricExtend(lyricEntry: GraphicalLyricEntry): void {
+        let startY: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
+        const startStaffEntry: GraphicalStaffEntry = lyricEntry.StaffEntryParent;
+        const startStaffLine: StaffLine = <StaffLine>lyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine;
+
+        // find endstaffEntry and staffLine
+        let endStaffEntry: GraphicalStaffEntry = undefined;
+        let endStaffLine: StaffLine = undefined;
+        const staffIndex: number = startStaffEntry.parentMeasure.ParentStaff.idInMusicSheet;
+        for (let index: number = startStaffEntry.parentVerticalContainer.Index + 1;
+             index < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
+             ++index) {
+            const gse: GraphicalStaffEntry = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[index].StaffEntries[staffIndex];
+            if (gse === undefined) {
+                continue;
+            }
+            if (gse.hasOnlyRests()) {
+                break;
+            }
+            if (gse.LyricsEntries.length > 0) {
+                break;
+            }
+            endStaffEntry = gse;
+            endStaffLine = <StaffLine>endStaffEntry.parentMeasure.ParentStaffLine;
+        }
+        if (endStaffEntry === undefined) {
+            return;
+        }
+        // if on the same StaffLine
+        if (startStaffLine === endStaffLine) {
+            // start- and End margins from the text Labels
+            const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+                startStaffEntry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+            const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+                endStaffEntry.PositionAndShape.RelativePosition.x +
+                endStaffEntry.PositionAndShape.BorderMarginRight;
+            // needed in order to line up with the Label's text bottom line (is the y psoition of the underscore)
+            startY -= lyricEntry.GraphicalLabel.PositionAndShape.Size.height / 4;
+            // create a Line (as underscope after the LyricLabel's End)
+            this.calculateSingleLyricWordWithUnderscore(startStaffLine, startX, endX, startY);
+        } else { // start and end on different StaffLines
+            // start margin from the text Label until the End of StaffLine
+            const lastMeasureBb: BoundingBox = startStaffLine.Measures[startStaffLine.Measures.length - 1].PositionAndShape;
+            const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+                startStaffEntry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+            const endX: number = lastMeasureBb.RelativePosition.x +
+                lastMeasureBb.Size.width;
+            // needed in order to line up with the Label's text bottom line
+            startY -= lyricEntry.GraphicalLabel.PositionAndShape.Size.height / 4;
+            // first Underscore until the StaffLine's End
+            this.calculateSingleLyricWordWithUnderscore(startStaffLine, startX, endX, startY);
+            if (endStaffEntry === undefined) {
+                return;
+            }
+            // second Underscore in the endStaffLine until endStaffEntry (if endStaffEntry isn't the first StaffEntry of the StaffLine))
+            if (!(endStaffEntry === endStaffEntry.parentMeasure.staffEntries[0] &&
+                    endStaffEntry.parentMeasure === endStaffEntry.parentMeasure.ParentStaffLine.Measures[0])) {
+                const secondStartX: number = endStaffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x;
+                const secondEndX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
+                    endStaffEntry.PositionAndShape.RelativePosition.x +
+                    endStaffEntry.PositionAndShape.BorderMarginRight;
+                this.calculateSingleLyricWordWithUnderscore(endStaffLine, secondStartX, secondEndX, startY);
+            }
+        }
+    }
+
+    /**
+     * This method calculates a single underscoreLine.
+     * @param staffLine
+     * @param startX
+     * @param end
+     * @param y
+     */
+    private calculateSingleLyricWordWithUnderscore(staffLine: StaffLine, startX: number, endX: number, y: number): void {
+        const lineStart: PointF2D = new PointF2D(startX, y);
+        const lineEnd: PointF2D = new PointF2D(endX, y);
+        const graphicalLine: GraphicalLine = new GraphicalLine(lineStart, lineEnd, this.rules.LyricUnderscoreLineWidth);
+        staffLine.LyricLines.push(graphicalLine);
+        if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
+            this.staffLinesWithLyricWords.push(staffLine);
+        }
+    }
+
+    /**
+     * This method calculates two Dashes for a LyricWord, positioned at the the two ends of the given distance.
+     * @param {StaffLine} staffLine
+     * @param {number} startX
+     * @param {number} endX
+     * @param {number} y
+     * @returns {number}
+     */
+    private calculateRightAndLeftDashesForLyricWord (staffLine: StaffLine, startX: number, endX: number, y: number): number {
+        const leftDash: GraphicalLabel = new GraphicalLabel (new Label ("-"), this.rules.LyricsHeight, TextAlignment.CenterBottom);
+        leftDash.setLabelPositionAndShapeBorders();
+        staffLine.LyricsDashes.push(leftDash);
+        if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
+            this.staffLinesWithLyricWords.push(staffLine);
+        }
+        leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
+        const leftDashRelative: PointF2D = new PointF2D (startX, y);
+        leftDash.PositionAndShape.RelativePosition = leftDashRelative;
+        const rightDash: GraphicalLabel = new GraphicalLabel (new Label ("-"), this.rules.LyricsHeight, TextAlignment.CenterBottom);
+        rightDash.setLabelPositionAndShapeBorders();
+        staffLine.LyricsDashes.push(rightDash);
+        rightDash.PositionAndShape.Parent = staffLine.PositionAndShape;
+        const rightDashRelative: PointF2D = new PointF2D (endX, y);
+        rightDash.PositionAndShape.RelativePosition = rightDashRelative;
+        return (rightDash.PositionAndShape.RelativePosition.x - leftDash.PositionAndShape.RelativePosition.x);
     }
 
     private calculateDynamicExpressions(): void {

+ 13 - 0
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -334,6 +334,19 @@ export abstract class MusicSheetDrawer {
         for (const measure of staffLine.Measures) {
             this.drawMeasure(measure);
         }
+
+        if (staffLine.LyricsDashes.length > 0) {
+            this.drawDashes(staffLine.LyricsDashes);
+        }
+    }
+
+    /**
+     * Draw all dashes to the canvas
+     * @param lyricsDashes Array of lyric dashes to be drawn
+     * @param layer Number of the layer that the lyrics should be drawn in
+     */
+    protected drawDashes(lyricsDashes: GraphicalLabel[]): void {
+        lyricsDashes.forEach(dash => this.drawLabel(dash, <number>GraphicalLayers.Notes));
     }
 
     // protected drawSlur(slur: GraphicalSlur, abs: PointF2D): void {

+ 6 - 3
src/MusicalScore/Graphical/MusicSystem.ts

@@ -57,6 +57,11 @@ export abstract class MusicSystem extends GraphicalObject {
         this.parent = value;
     }
 
+    public get NextSystem(): MusicSystem {
+        const idxInParent: number = this.Parent.MusicSystems.indexOf(this);
+        return idxInParent !== this.Parent.MusicSystems.length ? this.Parent.MusicSystems[idxInParent + 1] : undefined;
+    }
+
     public get StaffLines(): StaffLine[] {
         return this.staffLines;
     }
@@ -274,11 +279,9 @@ export abstract class MusicSystem extends GraphicalObject {
                 );
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 this.labels.setValue(graphicalLabel, instrument);
-                //graphicalLabel.PositionAndShape.Parent = this.PositionAndShape;
-
                 // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
                 // Y-Position will be calculated after the y-Spacing
-                graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
+                // graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
             }
 
             // calculate maxLabelLength (needed for X-Spacing)

+ 24 - 0
src/MusicalScore/Graphical/StaffLine.ts

@@ -8,6 +8,7 @@ import {StaffMeasure} from "./StaffMeasure";
 import {MusicSystem} from "./MusicSystem";
 import {StaffLineActivitySymbol} from "./StaffLineActivitySymbol";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
+import {GraphicalLabel} from "./GraphicalLabel";
 
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
@@ -20,6 +21,8 @@ export abstract class StaffLine extends GraphicalObject {
     protected parentStaff: Staff;
     protected skyLine: number[];
     protected bottomLine: number[];
+    protected lyricLines: GraphicalLine[] = [];
+    protected lyricsDashes: GraphicalLabel[] = [];
 
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super();
@@ -44,6 +47,27 @@ export abstract class StaffLine extends GraphicalObject {
         this.staffLines = value;
     }
 
+    public get NextStaffLine(): StaffLine {
+        const idxInParent: number = this.parentMusicSystem.StaffLines.indexOf(this);
+        return idxInParent !== this.parentMusicSystem.StaffLines.length ? this.parentMusicSystem.StaffLines[idxInParent + 1] : undefined;
+    }
+
+    public get LyricLines(): GraphicalLine[] {
+        return this.lyricLines;
+    }
+
+    public set LyricLines(value: GraphicalLine[]) {
+        this.lyricLines = value;
+    }
+
+    public get LyricsDashes(): GraphicalLabel[] {
+        return this.lyricsDashes;
+    }
+
+    public set LyricsDashes(value: GraphicalLabel[]) {
+        this.lyricsDashes = value;
+    }
+
     public get ParentMusicSystem(): MusicSystem {
         return this.parentMusicSystem;
     }

+ 98 - 5
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -16,6 +16,7 @@ import {FontStyles} from "../../../Common/Enums/FontStyles";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {OutlineAndFillStyleEnum, OUTLINE_AND_FILL_STYLE_DICT} from "../DrawingEnums";
 import {Logging} from "../../../Common/Logging";
+import { ArticulationEnum } from "../../VoiceData/VoiceEntry";
 
 /**
  * Helper class, which contains static methods which actually convert
@@ -145,6 +146,7 @@ export class VexFlowConverter {
         const accidentals: string[] = [];
         const frac: Fraction = notes[0].graphicalNoteLength;
         const isTuplet: boolean = notes[0].sourceNote.NoteTuplet !== undefined;
+        const articulations: ArticulationEnum[] = notes[0].sourceNote.ParentVoiceEntry.Articulations;
         let duration: string = VexFlowConverter.duration(frac, isTuplet);
         let vfClefType: string = undefined;
         let numDots: number = 0;
@@ -176,6 +178,8 @@ export class VexFlowConverter {
             keys: keys,
         });
 
+        const stemDirection: number = vfnote.getStemDirection();
+
         for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
             (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
             if (accidentals[i]) {
@@ -186,6 +190,70 @@ export class VexFlowConverter {
             vfnote.addDotToAll();
         }
 
+        // Articulations:
+        let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
+        if (stemDirection === Vex.Flow.Stem.UP) {
+            vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
+        }
+        for (const articulation of articulations) {
+            // tslint:disable-next-line:switch-default
+            let vfArt: Vex.Flow.Articulation = undefined;
+            switch (articulation) {
+                case ArticulationEnum.accent: {
+                    vfArt = new Vex.Flow.Articulation("a>");
+                    break;
+                }
+                case ArticulationEnum.downbow: {
+                    vfArt = new Vex.Flow.Articulation("am");
+                    break;
+                }
+                case ArticulationEnum.fermata: {
+                    vfArt = new Vex.Flow.Articulation("a@a");
+                    vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
+                    break;
+                }
+                case ArticulationEnum.invertedfermata: {
+                    vfArt = new Vex.Flow.Articulation("a@u");
+                    vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
+                    break;
+                }
+                case ArticulationEnum.lefthandpizzicato: {
+                    vfArt = new Vex.Flow.Articulation("a+");
+                    break;
+                }
+                case ArticulationEnum.snappizzicato: {
+                    vfArt = new Vex.Flow.Articulation("ao");
+                    break;
+                }
+                case ArticulationEnum.staccatissimo: {
+                    vfArt = new Vex.Flow.Articulation("av");
+                    break;
+                }
+                case ArticulationEnum.staccato: {
+                    vfArt = new Vex.Flow.Articulation("a.");
+                    break;
+                }
+                case ArticulationEnum.tenuto: {
+                    vfArt = new Vex.Flow.Articulation("a-");
+                    break;
+                }
+                case ArticulationEnum.upbow: {
+                    vfArt = new Vex.Flow.Articulation("a|");
+                    break;
+                }
+                case ArticulationEnum.strongaccent: {
+                    vfArt = new Vex.Flow.Articulation("a^");
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+            if (vfArt !== undefined) {
+                vfArt.setPosition(vfArtPosition);
+                vfnote.addModifier(0, vfArt);
+            }
+        }
         return vfnote;
     }
 
@@ -346,20 +414,19 @@ export class VexFlowConverter {
      * @returns {any}
      */
     public static line(lineType: SystemLinesEnum): any {
-        // TODO Not all line types are correctly mapped!
         switch (lineType) {
             case SystemLinesEnum.SingleThin:
                 return Vex.Flow.StaveConnector.type.SINGLE;
             case SystemLinesEnum.DoubleThin:
                 return Vex.Flow.StaveConnector.type.DOUBLE;
             case SystemLinesEnum.ThinBold:
-                return Vex.Flow.StaveConnector.type.SINGLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.BoldThinDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
             case SystemLinesEnum.DotsThinBold:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.DotsBoldBoldDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.None:
                 return Vex.Flow.StaveConnector.type.NONE;
             default:
@@ -434,3 +501,29 @@ export class VexFlowConverter {
         return ret;
     }
 }
+
+export enum VexFlowRepetitionType {
+    NONE = 1,         // no coda or segno
+    CODA_LEFT = 2,    // coda at beginning of stave
+    CODA_RIGHT = 3,   // coda at end of stave
+    SEGNO_LEFT = 4,   // segno at beginning of stave
+    SEGNO_RIGHT = 5,  // segno at end of stave
+    DC = 6,           // D.C. at end of stave
+    DC_AL_CODA = 7,   // D.C. al coda at end of stave
+    DC_AL_FINE = 8,   // D.C. al Fine end of stave
+    DS = 9,           // D.S. at end of stave
+    DS_AL_CODA = 10,  // D.S. al coda at end of stave
+    DS_AL_FINE = 11,  // D.S. al Fine at end of stave
+    FINE = 12,        // Fine at end of stave
+}
+
+export enum VexFlowBarlineType {
+    SINGLE = 1,
+    DOUBLE = 2,
+    END = 3,
+    REPEAT_BEGIN = 4,
+    REPEAT_END = 5,
+    REPEAT_BOTH = 6,
+    NONE = 7,
+}
+

+ 12 - 9
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -16,12 +16,12 @@ import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {GraphicalNote} from "../GraphicalNote";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
-import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {GraphicalChordSymbolContainer} from "../GraphicalChordSymbolContainer";
 import {GraphicalLabel} from "../GraphicalLabel";
 import {EngravingRules} from "../EngravingRules";
+import { TechnicalInstruction } from "../../VoiceData/Instructions/TechnicalInstruction";
 
 export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
     /**
@@ -150,14 +150,7 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
         return;
     }
 
-    /**
-     * Adds a technical instruction at the given staff entry.
-     * @param technicalInstruction
-     * @param graphicalStaffEntry
-     */
-    public createGraphicalTechnicalInstruction(technicalInstruction: TechnicalInstruction, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
-    }
+
 
     /**
      * Adds a clef change within a measure before the given staff entry.
@@ -185,4 +178,14 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
       graphicalChordSymbolContainer.PositionAndShape.calculateBoundingBox();
       graphicalStaffEntry.graphicalChordContainer = graphicalChordSymbolContainer;
     }
+
+    /**
+     * Adds a technical instruction at the given staff entry.
+     * @param technicalInstruction
+     * @param graphicalStaffEntry
+     */
+    public createGraphicalTechnicalInstruction(technicalInstruction: TechnicalInstruction, graphicalStaffEntry: GraphicalStaffEntry): void {
+        return;
+    }
+
 }

+ 128 - 60
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -7,7 +7,7 @@ import {SystemLinesEnum} from "../SystemLinesEnum";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
 import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
-import {VexFlowConverter} from "./VexFlowConverter";
+import {VexFlowConverter, VexFlowRepetitionType, VexFlowBarlineType} from "./VexFlowConverter";
 import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 import {Beam} from "../../VoiceData/Beam";
 import {GraphicalNote} from "../GraphicalNote";
@@ -17,6 +17,8 @@ import StaveNote = Vex.Flow.StaveNote;
 import {Logging} from "../../../Common/Logging";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {Tuplet} from "../../VoiceData/Tuplet";
+import { RepetitionInstructionEnum } from "../../VoiceData/Instructions/RepetitionInstruction";
+import { SystemLinePosition } from "../SystemLinePosition";
 
 export class VexFlowMeasure extends StaffMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -33,8 +35,10 @@ export class VexFlowMeasure extends StaffMeasure {
     public formatVoices: (width: number) => void;
     // The VexFlow Ties in the measure
     public vfTies: Vex.Flow.StaveTie[] = [];
+    // The repetition instructions given as words or symbols (coda, dal segno..)
+    public vfRepetitionWords: Vex.Flow.Repetition[] = [];
 
-    // The VexFlow Stave (one measure in one line)
+    // The VexFlow Stave (= one measure in a staffline)
     private stave: Vex.Flow.Stave;
     // VexFlow StaveConnectors (vertical lines)
     private connectors: Vex.Flow.StaveConnector[] = [];
@@ -76,18 +80,25 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     /**
-     * returns the x-width of a given measure line.
+     * returns the x-width (in units) of a given measure line {SystemLinesEnum}.
      * @param line
-     * @returns {SystemLinesEnum} the x-width
+     * @returns the x-width in osmd units
      */
     public getLineWidth(line: SystemLinesEnum): number {
-        // FIXME: See values in VexFlow's stavebarline.js
-        const vfline: any = VexFlowConverter.line(line);
-        switch (vfline) {
-            case Vex.Flow.StaveConnector.type.SINGLE:
-                return 1.0 / unitInPixels;
-            case Vex.Flow.StaveConnector.type.DOUBLE:
-                return 3.0 / unitInPixels;
+        switch (line) {
+            // return 0 for the normal lines, as the line width will be considered at the updateInstructionWidth() method using the stavemodifiers.
+            // case SystemLinesEnum.SingleThin:
+            //     return 5.0 / unitInPixels;
+            // case SystemLinesEnum.DoubleThin:
+            //     return 5.0 / unitInPixels;
+            //     case SystemLinesEnum.ThinBold:
+            //     return 5.0 / unitInPixels;
+            // but just add a little extra space for repetitions (cosmetics):
+            case SystemLinesEnum.BoldThinDots:
+            case SystemLinesEnum.DotsThinBold:
+                return 10.0 / unitInPixels;
+            case SystemLinesEnum.DotsBoldBoldDots:
+                return 10.0 / unitInPixels;
             default:
                 return 0;
         }
@@ -146,6 +157,86 @@ export class VexFlowMeasure extends StaffMeasure {
         this.updateInstructionWidth();
     }
 
+    public addMeasureLine(lineType: SystemLinesEnum, linePosition: SystemLinePosition): void {
+        switch (linePosition) {
+            case SystemLinePosition.MeasureBegin:
+                switch (lineType) {
+                    case SystemLinesEnum.BoldThinDots:
+                        this.stave.setBegBarType(VexFlowBarlineType.REPEAT_BEGIN);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case SystemLinePosition.MeasureEnd:
+                switch (lineType) {
+                    case SystemLinesEnum.DotsBoldBoldDots:
+                        this.stave.setEndBarType(VexFlowBarlineType.REPEAT_BOTH);
+                        break;
+                    case SystemLinesEnum.DotsThinBold:
+                        this.stave.setEndBarType(VexFlowBarlineType.REPEAT_END);
+                        break;
+                    case SystemLinesEnum.DoubleThin:
+                        this.stave.setEndBarType(VexFlowBarlineType.DOUBLE);
+                        break;
+                    case SystemLinesEnum.ThinBold:
+                        this.stave.setEndBarType(VexFlowBarlineType.END);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    public addWordRepetition(repetitionInstruction: RepetitionInstructionEnum): void {
+        let instruction: VexFlowRepetitionType = undefined;
+        let position: any = Vex.Flow.Modifier.Position.END;
+        switch (repetitionInstruction) {
+          case RepetitionInstructionEnum.Segno:
+            // create Segno Symbol:
+            instruction = VexFlowRepetitionType.SEGNO_LEFT;
+            position = Vex.Flow.Modifier.Position.BEGIN;
+            break;
+          case RepetitionInstructionEnum.Coda:
+            // create Coda Symbol:
+            instruction = VexFlowRepetitionType.CODA_LEFT;
+            position = Vex.Flow.Modifier.Position.BEGIN;
+            break;
+          case RepetitionInstructionEnum.DaCapo:
+            instruction = VexFlowRepetitionType.DC;
+            break;
+          case RepetitionInstructionEnum.DalSegno:
+            instruction = VexFlowRepetitionType.DS;
+            break;
+          case RepetitionInstructionEnum.Fine:
+            instruction = VexFlowRepetitionType.FINE;
+            break;
+          case RepetitionInstructionEnum.ToCoda:
+            //instruction = "To Coda";
+            break;
+          case RepetitionInstructionEnum.DaCapoAlFine:
+            instruction = VexFlowRepetitionType.DC_AL_FINE;
+            break;
+          case RepetitionInstructionEnum.DaCapoAlCoda:
+            instruction = VexFlowRepetitionType.DC_AL_CODA;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlFine:
+            instruction = VexFlowRepetitionType.DS_AL_FINE;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlCoda:
+            instruction = VexFlowRepetitionType.DS_AL_CODA;
+            break;
+          default:
+            break;
+        }
+        if (instruction !== undefined) {
+            this.stave.addModifier(new Vex.Flow.Repetition(instruction, 0, 0), position);
+        }
+    }
+
     /**
      * Sets the overall x-width of the measure.
      * @param width
@@ -182,15 +273,7 @@ export class VexFlowMeasure extends StaffMeasure {
      * @param ctx
      */
     public draw(ctx: Vex.Flow.RenderContext): void {
-        // If this is the first stave in the vertical measure, call the format
-        // method to set the width of all the voices
-        if (this.formatVoices) {
-            // The width of the voices does not include the instructions (StaveModifiers)
-            this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
-        }
 
-        // Force the width of the Begin Instructions
-        this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
         // Draw stave lines
         this.stave.setContext(ctx).draw();
         // Draw all voices
@@ -226,9 +309,18 @@ export class VexFlowMeasure extends StaffMeasure {
         for (const connector of this.connectors) {
             connector.setContext(ctx).draw();
         }
+    }
 
-        // now we can finally set the vexflow x positions back into the osmd object model:
-        this.setStaffEntriesXPositions();
+    public format(): void {
+        // If this is the first stave in the vertical measure, call the format
+        // method to set the width of all the voices
+        if (this.formatVoices) {
+            // The width of the voices does not include the instructions (StaveModifiers)
+            this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
+        }
+
+        // Force the width of the Begin Instructions
+        this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
     }
 
     /**
@@ -310,7 +402,7 @@ export class VexFlowMeasure extends StaffMeasure {
                         //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
                         // }
                     } else {
-                        Logging.log("Warning! Beam with no notes! Trying to ignore, but this is a serious problem.");
+                        Logging.log("Warning! Beam with no notes!");
                     }
                 }
             }
@@ -413,49 +505,25 @@ export class VexFlowMeasure extends StaffMeasure {
         return this.stave;
     }
 
-    //private increaseBeginInstructionWidth(): void {
-    //    let modifiers: StaveModifier[] = this.stave.getModifiers();
-    //    let modifier: StaveModifier = modifiers[modifiers.length - 1];
-    //    //let padding: number = modifier.getCategory() === "keysignatures" ? modifier.getPadding(2) : 0;
-    //    let padding: number = modifier.getPadding(20);
-    //    let width: number = modifier.getWidth();
-    //    this.beginInstructionsWidth += (padding + width) / UnitInPixels;
-    //}
-    //
-    //private increaseEndInstructionWidth(): void {
-    //    let modifiers: StaveModifier[] = this.stave.getModifiers();
-    //    let modifier: StaveModifier = modifiers[modifiers.length - 1];
-    //    let padding: number = 0;
-    //    let width: number = modifier.getWidth();
-    //    this.endInstructionsWidth += (padding + width) / UnitInPixels;
-    //
-    //}
-
     /**
      * After re-running the formatting on the VexFlow Stave, update the
      * space needed by Instructions (in VexFlow: StaveModifiers)
      */
     private updateInstructionWidth(): void {
-        this.beginInstructionsWidth = (this.stave.getNoteStartX() - this.stave.getX()) / unitInPixels;
-        this.endInstructionsWidth = (this.stave.getX() + this.stave.getWidth() - this.stave.getNoteEndX()) / unitInPixels;
-    }
-
-    /**
-     * sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
-     * The positions are needed for cursor placement and mouse/tap interactions
-     */
-    private setStaffEntriesXPositions(): void {
-        for (let idx3: number = 0, len3: number = this.staffEntries.length; idx3 < len3; ++idx3) {
-            const gse: VexFlowStaffEntry = (<VexFlowStaffEntry> this.staffEntries[idx3]);
-            const measure: StaffMeasure = gse.parentMeasure;
-            const x: number =
-                gse.getX() -
-                measure.PositionAndShape.RelativePosition.x -
-                measure.ParentStaffLine.PositionAndShape.RelativePosition.x -
-                measure.parentMusicSystem.PositionAndShape.RelativePosition.x;
-            gse.PositionAndShape.RelativePosition.x = x;
-            gse.PositionAndShape.calculateAbsolutePosition();
-            gse.PositionAndShape.calculateAbsolutePositionsOfChildren();
+        let beginInstructionsWidth: number = 0;
+        let endInstructionsWidth: number = 0;
+        const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();
+        for (const mod of modifiers) {
+            if (mod.getPosition() === Vex.Flow.StaveModifier.Position.BEGIN) {
+                beginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
+            } else if (mod.getPosition() === Vex.Flow.StaveModifier.Position.END) {
+                endInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
+            }
         }
+
+        this.beginInstructionsWidth = beginInstructionsWidth / unitInPixels;
+        this.endInstructionsWidth = endInstructionsWidth / unitInPixels;
+        //this.beginInstructionsWidth =  (this.stave.getNoteStartX() - this.stave.getX()) / unitInPixels;
+        //this.endInstructionsWidth = (this.stave.getX() + this.stave.getWidth() - this.stave.getNoteEndX()) / unitInPixels;
     }
 }

+ 314 - 184
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -16,12 +16,10 @@ import {Beam} from "../../VoiceData/Beam";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
-import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
 import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
 import {Tuplet} from "../../VoiceData/Tuplet";
-import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
 
@@ -29,250 +27,382 @@ import Vex = require("vexflow");
 import {Logging} from "../../../Common/Logging";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
+import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
+import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
+import { GraphicalLabel } from "../GraphicalLabel";
+import { LyricsEntry } from "../../VoiceData/Lyrics/LyricsEntry";
+import { GraphicalLyricWord } from "../GraphicalLyricWord";
+import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
-    constructor() {
-        super(new VexFlowGraphicalSymbolFactory());
-        MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+  constructor() {
+    super(new VexFlowGraphicalSymbolFactory());
+    MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+  }
+
+  protected clearRecreatedObjects(): void {
+    super.clearRecreatedObjects();
+    for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
+            for (const staffMeasure of staffMeasures) {
+        (<VexFlowMeasure>staffMeasure).clean();
+      }
     }
+  }
 
-    protected clearRecreatedObjects(): void {
-        super.clearRecreatedObjects();
+    protected formatMeasures(): void {
         for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
             for (const staffMeasure of staffMeasures) {
-                (<VexFlowMeasure>staffMeasure).clean();
+                (<VexFlowMeasure>staffMeasure).format();
+                for (const staffEntry of staffMeasure.staffEntries) {
+                    (<VexFlowStaffEntry>staffEntry).calculateXPosition();
+                }
             }
         }
     }
 
-    //protected clearSystemsAndMeasures(): void {
-    //    for (let measure of measures) {
-    //
-    //    }
-    //}
+  //protected clearSystemsAndMeasures(): void {
+  //    for (let measure of measures) {
+  //
+  //    }
+  //}
+
+  /**
+   * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
+   * All staff entries are x-aligned throughout all vertically aligned staff measures.
+   * This method is called within calculateXLayout.
+   * The staff entries are aligned with minimum needed x distances.
+   * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
+   * @param measures
+   * @returns the minimum required x width of the source measure (=list of staff measures)
+   */
+  protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
+    // Finalize beams
+    /*for (let measure of measures) {
+     (measure as VexFlowMeasure).finalizeBeams();
+     (measure as VexFlowMeasure).finalizeTuplets();
+     }*/
+    // Format the voices
+    const allVoices: Vex.Flow.Voice[] = [];
+    const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({align_rests: true,
+    });
+
+    for (const measure of measures) {
+        const mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
+        const voices: Vex.Flow.Voice[] = [];
+        for (const voiceID in mvoices) {
+            if (mvoices.hasOwnProperty(voiceID)) {
+                voices.push(mvoices[voiceID]);
+                allVoices.push(mvoices[voiceID]);
 
-    /**
-     * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
-     * All staff entries are x-aligned throughout all vertically aligned staff measures.
-     * This method is called within calculateXLayout.
-     * The staff entries are aligned with minimum needed x distances.
-     * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
-     * @param measures
-     * @returns the minimum required x width of the source measure (=list of staff measures)
-     */
-    protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
-        // Finalize beams
-        /*for (let measure of measures) {
-            (measure as VexFlowMeasure).finalizeBeams();
-            (measure as VexFlowMeasure).finalizeTuplets();
-        }*/
-        // Format the voices
-        const allVoices: Vex.Flow.Voice[] = [];
-        const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({
-            align_rests: true,
-        });
-
-        for (const measure of measures) {
-            const mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
-            const voices: Vex.Flow.Voice[] = [];
-            for (const voiceID in mvoices) {
-                if (mvoices.hasOwnProperty(voiceID)) {
-                    voices.push(mvoices[voiceID]);
-                    allVoices.push(mvoices[voiceID]);
-
-                }
             }
-            if (voices.length === 0) {
-                Logging.warn("Found a measure with no voices... Continuing anyway.", mvoices);
-                continue;
-            }
-            formatter.joinVoices(voices);
         }
+        if (voices.length === 0) {
+            Logging.warn("Found a measure with no voices... Continuing anyway.", mvoices);
+            continue;
+        }
+        formatter.joinVoices(voices);
+    }
 
-        let width: number = 200;
-        if (allVoices.length > 0) {
-            const firstMeasure: VexFlowMeasure = measures[0] as VexFlowMeasure;
-            // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
-            // FIXME: a more relaxed formatting of voices
-            width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
-            for (const measure of measures) {
-                measure.minimumStaffEntriesWidth = width;
+    let width: number = 200;
+    if (allVoices.length > 0) {
+        // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
+        // FIXME: a more relaxed formatting of voices
+        width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
+        // firstMeasure.formatVoices = (w: number) => {
+        //     formatter.format(allVoices, w);
+        // };
+        for (const measure of measures) {
+            measure.minimumStaffEntriesWidth = width;
+            if (measure !== measures[0]) {
                 (measure as VexFlowMeasure).formatVoices = undefined;
+            } else {
+                (measure as VexFlowMeasure).formatVoices = (w: number) => {
+                    formatter.format(allVoices, w);
+                };
             }
-            firstMeasure.formatVoices = (w: number) => {
-                formatter.format(allVoices, w);
-            };
         }
-
-        return width;
-    }
-
-    protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
-                                 startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
-        return new GraphicalTie(tie, startNote, endNote);
-    }
-
-
-    protected updateStaffLineBorders(staffLine: StaffLine): void {
-        return;
-    }
-
-    protected calculateMeasureNumberPlacement(musicSystem: MusicSystem): void {
-        return;
-    }
-
-    protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
-        (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
-    }
-
-    /**
-     * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
-     * Is Excecuted per voice entry of a staff entry.
-     * After that layoutStaffEntry is called.
-     * @param voiceEntry
-     * @param graphicalNotes
-     * @param graphicalStaffEntry
-     * @param hasPitchedNote
-     * @param isGraceStaffEntry
-     */
-    protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
-                               hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
-        return;
-    }
-
-    /**
-     * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
-     * This method is called after the voice entries are handled by layoutVoiceEntry().
-     * @param graphicalStaffEntry
-     */
-    protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
-        (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
     }
+    return width;
+}
 
-    /**
-     * calculates the y positions of the staff lines within a system and
-     * furthermore the y positions of the systems themselves.
-     */
-    protected calculateSystemYLayout(): void {
-        for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
+  protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
+                               startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
+    return new GraphicalTie(tie, startNote, endNote);
+  }
+
+
+  protected updateStaffLineBorders(staffLine: StaffLine): void {
+    return;
+  }
+
+  protected calculateMeasureNumberPlacement(musicSystem: MusicSystem): void {
+    return;
+  }
+
+  protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
+    (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
+  }
+
+  /**
+   * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
+   * Is Excecuted per voice entry of a staff entry.
+   * After that layoutStaffEntry is called.
+   * @param voiceEntry
+   * @param graphicalNotes
+   * @param graphicalStaffEntry
+   * @param hasPitchedNote
+   * @param isGraceStaffEntry
+   */
+  protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
+                             hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
+    return;
+  }
+
+  /**
+   * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
+   * This method is called after the voice entries are handled by layoutVoiceEntry().
+   * @param graphicalStaffEntry
+   */
+  protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
+    (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
+  }
+
+  /**
+   * calculates the y positions of the staff lines within a system and
+   * furthermore the y positions of the systems themselves.
+   */
+  protected calculateSystemYLayout(): void {
+    for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
             const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
             if (!this.leadSheet) {
-                let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
-                    this.rules.TitleBottomDistance;
-                for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
+        let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
+          this.rules.TitleBottomDistance;
+        for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
                     const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
-                    // calculate y positions of stafflines within system
+          // calculate y positions of stafflines within system
                     let y: number = 0;
                     for (const line of musicSystem.StaffLines) {
-                        line.PositionAndShape.RelativePosition.y = y;
-                        y += 10;
-                    }
-                    // set y positions of systems using the previous system and a fixed distance.
+            line.PositionAndShape.RelativePosition.y = y;
+            y += 10;
+          }
+          // set y positions of systems using the previous system and a fixed distance.
                     musicSystem.PositionAndShape.BorderBottom = y + 0;
                     musicSystem.PositionAndShape.RelativePosition.x = this.rules.PageLeftMargin + this.rules.SystemLeftMargin;
                     musicSystem.PositionAndShape.RelativePosition.y = globalY;
                     globalY += y + 5;
-                }
-            }
         }
+      }
     }
+  }
+
+  /**
+   * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
+   */
+  protected initStaffMeasuresCreation(): void {
+    return;
+  }
+
+  /**
+   * add here all given articulations to the VexFlowGraphicalStaffEntry and prepare them for rendering.
+   * @param articulations
+   * @param voiceEntry
+   * @param graphicalStaffEntry
+   */
+  protected layoutArticulationMarks(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+
+    return;
+  }
 
     /**
-     * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
+     * Calculate the shape (B�zier curve) for this tie.
+     * @param tie
+     * @param tieIsAtSystemBreak
      */
-    protected initStaffMeasuresCreation(): void {
-        return;
-    }
-
-    protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
+  protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
         const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
         let vfStartNote: Vex.Flow.StaveNote = undefined;
         if (startNote !== undefined) {
-            vfStartNote = startNote.vfnote[0];
-        }
+      vfStartNote = startNote.vfnote[0];
+    }
 
         const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
         let vfEndNote: Vex.Flow.StaveNote = undefined;
         if (endNote !== undefined) {
-            vfEndNote = endNote.vfnote[0];
-        }
+      vfEndNote = endNote.vfnote[0];
+    }
 
 
         if (tieIsAtSystemBreak) {
-            // split tie into two ties:
+      // split tie into two ties:
             const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                first_note: vfStartNote,
-            });
+        first_note: vfStartNote,
+      });
             const measure1: VexFlowMeasure = (startNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
             measure1.vfTies.push(vfTie1);
 
             const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                last_note : vfEndNote,
-            });
+        last_note: vfEndNote,
+      });
             const measure2: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
             measure2.vfTies.push(vfTie2);
-        } else {
+    } else {
             const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                first_note: vfStartNote,
-                last_note : vfEndNote,
-            });
+        first_note: vfStartNote,
+        last_note: vfEndNote,
+      });
             const measure: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
             measure.vfTies.push(vfTie);
-        }
-    }
-
-    protected calculateSingleStaffLineLyricsPosition(staffLine: StaffLine, lyricVersesNumber: number[]): void {
-        return;
-    }
-
-    protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
-        return;
-    }
-
-    protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
-        return;
-    }
-
-    protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
-        return;
-    }
-
-    protected handleTiedGraphicalNote(  tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
-                                        octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
-                                        openTie: Tie, isLastTieNote: boolean): void {
-        return;
     }
+  }
 
     /**
-     * Is called if a note is part of a beam.
-     * @param graphicalNote
-     * @param beam
-     * @param openBeams a list of all currently open beams
+     * Calculate a single OctaveShift for a [[MultiExpression]].
+     * @param sourceMeasure
+     * @param multiExpression
+     * @param measureIndex
+     * @param staffIndex
      */
-    protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
-        (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
-    }
+  protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
+    return;
+  }
 
-    protected handleVoiceEntryLyrics(lyricsEntries: Dictionary<number, LyricsEntry>, voiceEntry: VoiceEntry,
-                                     graphicalStaffEntry: GraphicalStaffEntry, openLyricWords: LyricWord[]): void {
-        return;
-    }
-
-    protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
+    /**
+     * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
+     * @param repetitionInstruction
+     * @param measureIndex
+     */
+  protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
+      // find first visible StaffLine
+      let uppermostMeasure: VexFlowMeasure = undefined;
+      const measures: VexFlowMeasure[]  = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
+      for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
+        const graphicalMeasure: VexFlowMeasure = measures[idx];
+        if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
+            uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
+            break;
+        }
+      }
+      // ToDo: feature/Repetitions
+      // now create corresponding graphical symbol or Text in VexFlow:
+      // use top measure and staffline for positioning.
+      if (uppermostMeasure !== undefined) {
+        uppermostMeasure.addWordRepetition(repetitionInstruction.type);
+      }
     }
 
-    protected handleVoiceEntryArticulations(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
-    }
+  protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
+    return;
+  }
 
     /**
-     * Is called if a note is part of a tuplet.
-     * @param graphicalNote
-     * @param tuplet
-     * @param openTuplets a list of all currently open tuplets
+     * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
+     * @param tiedGraphicalNote
+     * @param beams
+     * @param activeClef
+     * @param octaveShiftValue
+     * @param graphicalStaffEntry
+     * @param duration
+     * @param openTie
+     * @param isLastTieNote
      */
-    protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
-        (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
+  protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
+                                    octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
+                                    openTie: Tie, isLastTieNote: boolean): void {
+    return;
+  }
+
+  /**
+   * Is called if a note is part of a beam.
+   * @param graphicalNote
+   * @param beam
+   * @param openBeams a list of all currently open beams
+   */
+  protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
+    (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
+  }
+
+    protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
+        voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
+            const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
+                                                                                     graphicalStaffEntry,
+                                                                                     this.rules.LyricsHeight,
+                                                                                     this.rules.StaffHeight);
+
+            graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
+
+            // create corresponding GraphicalLabel
+            const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
+            graphicalLabel.setLabelPositionAndShapeBorders();
+
+            if (lyricsEntry.Word !== undefined) {
+                const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
+                let index: number = lyricWords.indexOf(lyricsEntry.Word);
+                if (index === -1) {
+                    lyricWords.push(lyricsEntry.Word);
+                    index = lyricWords.indexOf(lyricsEntry.Word);
+  }
+
+                if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
+                    const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
+
+                    graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+                    graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+                    this.graphicalLyricWords.push(graphicalLyricWord);
+                } else {
+                    const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
+
+                    graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+                    graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+
+                    if (graphicalLyricWord.isFilled()) {
+                        lyricWords.splice(index, 1);
+                        this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
+                    }
+                }
+            }
+        });
     }
+
+  protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
+    return;
+  }
+
+  /**
+   * Add articulations to the given vexflow staff entry.
+   * @param articulations
+   * @param voiceEntry
+   * @param graphicalStaffEntry
+   */
+  protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
+                                          voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+
+    return;
+  }
+
+  /**
+   * Add technical instructions to the given vexflow staff entry.
+   * @param technicalInstructions
+   * @param voiceEntry
+   * @param staffEntry
+   */
+  protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
+                                                  voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+    return;
+  }
+
+  /**
+   * Is called if a note is part of a tuplet.
+   * @param graphicalNote
+   * @param tuplet
+   * @param openTuplets a list of all currently open tuplets
+   */
+  protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
+    (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
+  }
 }

+ 23 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -10,6 +10,7 @@ import {GraphicalLayers} from "../DrawingEnums";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {VexFlowBackend} from "./VexFlowBackend";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
 
 /**
  * This is a global constant which denotes the height in pixels of the space between two lines of the stave
@@ -70,6 +71,16 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             measure.PositionAndShape.AbsolutePosition.y * unitInPixels
         );
         measure.draw(this.backend.getContext());
+        for (const voiceID in measure.vfVoices) {
+            if (measure.vfVoices.hasOwnProperty(voiceID)) {
+                const tickables: Vex.Flow.Tickable[] = measure.vfVoices[voiceID].tickables;
+                for (const tick of tickables) {
+                    if ((<any>tick).getAttribute("type") === "StaveNote" && process.env.DEBUG) {
+                        tick.getBoundingBox().draw(this.backend.getContext());
+                    }
+                }
+            }
+        }
 
         // Draw the StaffEntries
         for (const staffEntry of measure.staffEntries) {
@@ -82,6 +93,18 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         if (staffEntry.graphicalChordContainer !== undefined) {
             this.drawLabel(staffEntry.graphicalChordContainer.GetGraphicalLabel, <number>GraphicalLayers.Notes);
         }
+        if (staffEntry.LyricsEntries.length > 0) {
+            this.drawLyrics(staffEntry.LyricsEntries, <number>GraphicalLayers.Notes);
+        }
+    }
+
+    /**
+     * Draw all lyrics to the canvas
+     * @param lyricEntries Array of lyric entries to be drawn
+     * @param layer Number of the layer that the lyrics should be drawn in
+     */
+    private drawLyrics(lyricEntries: GraphicalLyricEntry[], layer: number): void {
+        lyricEntries.forEach(lyricsEntry => this.drawLabel(lyricsEntry.GraphicalLabel, layer));
     }
 
     protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {

+ 6 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts

@@ -39,9 +39,13 @@ export class VexFlowMusicSystem extends MusicSystem {
      */
     protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
                                musicSystem: MusicSystem, topMeasure: StaffMeasure, bottomMeasure: StaffMeasure = undefined): SystemLine {
-        // ToDo: create line in Vexflow
+        const vfMeasure: VexFlowMeasure = topMeasure as VexFlowMeasure;
+        vfMeasure.addMeasureLine(lineType, linePosition);
         if (bottomMeasure) {
-            (bottomMeasure as VexFlowMeasure).lineTo(topMeasure as VexFlowMeasure, VexFlowConverter.line(lineType));
+          // ToDo: feature/Repetitions
+          // create here the correct lines according to the given lineType.
+          (bottomMeasure as VexFlowMeasure).lineTo(topMeasure as VexFlowMeasure, VexFlowConverter.line(lineType));
+          (bottomMeasure as VexFlowMeasure).addMeasureLine(lineType, linePosition);
         }
         return new SystemLine(lineType, linePosition, this, topMeasure, bottomMeasure);
     }

+ 23 - 11
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -15,22 +15,34 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
     public vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = {};
 
     /**
-     *
-     * @returns {number} the x-position (in units) of this StaffEntry
+     * Calculates the staff entry positions from the VexFlow stave information and the tickabels inside the staff.
+     * This is needed in order to set the OSMD staff entries (which are almost the same as tickables) to the correct positionts.
+     * It is also needed to be done after formatting!
      */
-    public getX(): number {
-        let x: number = 0;
-        let n: number = 0;
+    public calculateXPosition(): void {
         const vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = this.vfNotes;
+        const stave: Vex.Flow.Stave = (this.parentMeasure as VexFlowMeasure).getVFStave();
+        let tickablePosition: number = 0;
+        let numberOfValidTickables: number = 0;
         for (const voiceId in vfNotes) {
             if (vfNotes.hasOwnProperty(voiceId)) {
-                x += (vfNotes[voiceId].getNoteHeadBeginX() + vfNotes[voiceId].getNoteHeadEndX()) / 2;
-                n += 1;
+                const tickable: Vex.Flow.StaveNote = vfNotes[voiceId];
+                // This will let the tickable know how to calculate it's bounding box
+                tickable.setStave(stave);
+                // The middle of the tickable is also the OSMD BoundingBox center
+                const staveNote: Vex.Flow.StaveNote = (<Vex.Flow.StaveNote>tickable);
+                tickablePosition += staveNote.getNoteHeadEndX() - staveNote.getGlyphWidth() / 2;
+                numberOfValidTickables++;
             }
         }
-        if (n === 0) {
-            return 0;
-        }
-        return x / n / unitInPixels;
+        tickablePosition = tickablePosition / numberOfValidTickables;
+        // Calculate parent absolute position and reverse calculate the relative position
+        // All the modifiers signs, clefs, you name it have an offset in the measure. Therefore remove it.
+        // NOTE: Somehow vexflows shift is off by 25px.
+        const modifierOffset: number = stave.getModifierXShift() - (this.parentMeasure.MeasureNumber === 1 ? 25 : 0);
+        // const modifierOffset: number = 0;
+        // sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
+        // The positions are needed for cursor placement and mouse/tap interactions
+        this.PositionAndShape.RelativePosition.x = (tickablePosition - stave.getNoteStartX() + modifierOffset) / unitInPixels;
     }
 }

+ 8 - 0
src/MusicalScore/Interfaces/IAfterSheetReadingModule.ts

@@ -0,0 +1,8 @@
+import {MusicSheet} from "../MusicSheet";
+/**
+ * Created by Matthias on 22.02.2017.
+ */
+
+export interface IAfterSheetReadingModule {
+  calculate(musicSheet: MusicSheet): void;
+}

+ 2 - 1
src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts

@@ -12,7 +12,7 @@ import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
 import {Staff} from "../VoiceData/Staff";
 import {StaffLine} from "../Graphical/StaffLine";
 import {StaffMeasure} from "../Graphical/StaffMeasure";
-import {TechnicalInstruction} from "../VoiceData/Instructions/TechnicalInstruction";
+import { TechnicalInstruction } from "../VoiceData/Instructions/TechnicalInstruction";
 
 export interface IGraphicalSymbolFactory {
 
@@ -48,6 +48,7 @@ export interface IGraphicalSymbolFactory {
         technicalInstruction: TechnicalInstruction,
         graphicalStaffEntry: GraphicalStaffEntry): void;
 
+
     createInStaffClef(graphicalStaffEntry: GraphicalStaffEntry, clefInstruction: ClefInstruction): void;
 
     createChordSymbol(

+ 25 - 29
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -20,13 +20,12 @@ import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
 import {Logging} from "../../Common/Logging";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
+import { RepetitionInstructionReader } from "./MusicSymbolModules/RepetitionInstructionReader";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 // FIXME: The following classes are missing
-//type repetitionInstructionReader = any;
 //type ChordSymbolContainer = any;
 //type SlurReader = any;
-//type RepetitionInstructionReader = any;
 //type ExpressionReader = any;
 //declare class MusicSymbolModuleFactory {
 //  public static createSlurReader(x: any): any;
@@ -44,10 +43,6 @@ import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
 //  }
 //}
 
-/**
- * To be implemented
- */
-export type RepetitionInstructionReader = any;
 
 /**
  * An InstrumentReader is used during the reading phase to keep parsing new measures from the MusicXML file
@@ -56,7 +51,7 @@ export type RepetitionInstructionReader = any;
 export class InstrumentReader {
 
   constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
-      // this.repetitionInstructionReader = repetitionInstructionReader;
+      this.repetitionInstructionReader = repetitionInstructionReader;
       this.xmlMeasureList = xmlMeasureList;
       this.musicSheet = instrument.GetMusicSheet;
       this.instrument = instrument;
@@ -69,7 +64,7 @@ export class InstrumentReader {
       // (*) this.slurReader = MusicSymbolModuleFactory.createSlurReader(this.musicSheet);
   }
 
-  // private repetitionInstructionReader: RepetitionInstructionReader;
+  private repetitionInstructionReader: RepetitionInstructionReader;
   private xmlMeasureList: IXmlElement[];
   private musicSheet: MusicSheet;
   private slurReader: any; // (*) SlurReader;
@@ -124,9 +119,9 @@ export class InstrumentReader {
     }
     this.currentMeasure = currentMeasure;
     this.inSourceMeasureInstrumentIndex = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument);
-    // (*) if (this.repetitionInstructionReader !== undefined) {
-    //  this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
-    //}
+    if (this.repetitionInstructionReader !== undefined) {
+     this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
+    }
     let currentFraction: Fraction = new Fraction(0, 1);
     let previousFraction: Fraction = new Fraction(0, 1);
     let divisionsException: boolean = false;
@@ -309,15 +304,17 @@ export class InstrumentReader {
         } else if (xmlNode.name === "direction") {
           // unused let directionTypeNode: IXmlElement = xmlNode.element("direction-type");
           // (*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
-          // let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
-          // if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
-            // relativePositionInMeasure /= this.activeRhythm.Rhythm.RealValue;
-          // }
-                    // unused:
-                    // let handeled: boolean = false;
-                    // if (this.repetitionInstructionReader !== undefined) {
-          //  handeled = this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode,
-          //                                                                                              relativePositionInMeasure);
+          let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
+          if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
+            relativePositionInMeasure /= this.activeRhythm.Rhythm.RealValue;
+          }
+          const directionTypeNode: IXmlElement = xmlNode.element("direction-type");
+          //let handeled: boolean = false;
+          if (this.repetitionInstructionReader !== undefined) {
+            //handeled =
+            this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode,
+                                                                                            relativePositionInMeasure);
+          }
           //}
           //if (!handeled) {
           //  let expressionReader: ExpressionReader = this.expressionReaders[0];
@@ -339,15 +336,14 @@ export class InstrumentReader {
           //  }
           //}
         } else if (xmlNode.name === "barline") {
-
-          //if (this.repetitionInstructionReader !== undefined) {
-          //  let measureEndsSystem: boolean = false;
-          //  this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode, measureEndsSystem);
-          //  if (measureEndsSystem) {
-          //    this.currentMeasure.BreakSystemAfter = true;
-          //    this.currentMeasure.endsPiece = true;
-          //  }
-          //}
+          if (this.repetitionInstructionReader !== undefined) {
+           const measureEndsSystem: boolean = false;
+           this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode, measureEndsSystem);
+           if (measureEndsSystem) {
+             this.currentMeasure.BreakSystemAfter = true;
+             this.currentMeasure.endsPiece = true;
+           }
+          }
         } else if (xmlNode.name === "sound") {
           // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
         } else if (xmlNode.name === "harmony") {

+ 21 - 26
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -17,31 +17,26 @@ import {SubInstrument} from "../SubInstrument";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {Label} from "../Label";
-
-/**
- * To be implemented
- */
-type RepetitionInstructionReader = any;
-/**
- * To be implemented
- */
-type RepetitionCalculator = any;
+import {MusicSymbolModuleFactory} from "./MusicSymbolModuleFactory";
+import {IAfterSheetReadingModule} from "../Interfaces/IAfterSheetReadingModule";
+import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
+import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
 
 export class MusicSheetReader /*implements IMusicSheetReader*/ {
 
-    //constructor(afterSheetReadingModules: IAfterSheetReadingModule[]) {
-    //  if (afterSheetReadingModules === undefined) {
-    //    this.afterSheetReadingModules = [];
-    //  } else {
-    //    this.afterSheetReadingModules = afterSheetReadingModules;
-    //  }
-    //  this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
-    //  this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
-    //}
+    constructor(afterSheetReadingModules: IAfterSheetReadingModule[] = undefined) {
+     if (afterSheetReadingModules === undefined) {
+       this.afterSheetReadingModules = [];
+     } else {
+       this.afterSheetReadingModules = afterSheetReadingModules;
+     }
+     this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
+     this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
+    }
 
     private repetitionInstructionReader: RepetitionInstructionReader;
     private repetitionCalculator: RepetitionCalculator;
-    // private afterSheetReadingModules: IAfterSheetReadingModule[];
+    private afterSheetReadingModules: IAfterSheetReadingModule[];
     private musicSheet: MusicSheet;
     private completeNumberOfStaves: number = 0;
     private currentMeasure: SourceMeasure;
@@ -176,16 +171,16 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         if (this.repetitionInstructionReader !== undefined) {
             this.repetitionInstructionReader.removeRedundantInstructions();
             if (this.repetitionCalculator !== undefined) {
-                this.repetitionCalculator.calculateRepetitions(this.musicSheet, this.repetitionInstructionReader.RepetitionInstructions);
+                this.repetitionCalculator.calculateRepetitions(this.musicSheet, this.repetitionInstructionReader.repetitionInstructions);
             }
         }
         this.musicSheet.checkForInstrumentWithNoVoice();
         this.musicSheet.fillStaffList();
         //this.musicSheet.DefaultStartTempoInBpm = this.musicSheet.SheetPlaybackSetting.BeatsPerMinute;
-        //for (let idx: number = 0, len: number = this.afterSheetReadingModules.length; idx < len; ++idx) {
-        //  let afterSheetReadingModule: IAfterSheetReadingModule = this.afterSheetReadingModules[idx];
-        //  afterSheetReadingModule.calculate(this.musicSheet);
-        //}
+        for (let idx: number = 0, len: number = this.afterSheetReadingModules.length; idx < len; ++idx) {
+         const afterSheetReadingModule: IAfterSheetReadingModule = this.afterSheetReadingModules[idx];
+         afterSheetReadingModule.calculate(this.musicSheet);
+        }
 
         return this.musicSheet;
     }
@@ -194,7 +189,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         const instrumentDict: { [_: string]: Instrument; } = this.createInstrumentGroups(partList);
         this.completeNumberOfStaves = this.getCompleteNumberOfStavesFromXml(partInst);
         if (partInst.length !== 0) {
-            // (*) this.repetitionInstructionReader.MusicSheet = this.musicSheet;
+            this.repetitionInstructionReader.MusicSheet = this.musicSheet;
             this.currentFraction = new Fraction(0, 1);
             this.currentMeasure = undefined;
             this.previousMeasure = undefined;
@@ -220,7 +215,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                 currentInstrument.createStaves(instrumentNumberOfStaves);
                 instrumentReaders.push(new InstrumentReader(this.repetitionInstructionReader, xmlMeasureList, currentInstrument));
                 if (this.repetitionInstructionReader !== undefined) {
-                    this.repetitionInstructionReader.XmlMeasureList[counter] = xmlMeasureList;
+                    this.repetitionInstructionReader.xmlMeasureList[counter] = xmlMeasureList;
                 }
                 counter++;
             }

+ 31 - 0
src/MusicalScore/ScoreIO/MusicSymbolModuleFactory.ts

@@ -0,0 +1,31 @@
+import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
+import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
+
+export class MusicSymbolModuleFactory {
+  public static createRepetitionInstructionReader(): RepetitionInstructionReader {
+    return new RepetitionInstructionReader();
+  }
+
+  public static createRepetitionCalculator(): RepetitionCalculator {
+    return new RepetitionCalculator();
+  }
+
+  /*
+   public static createExpressionGenerator(musicSheet: MusicSheet,
+   instrument: Instrument, staffNumber: number): ExpressionReader {
+   return new ExpressionReader(musicSheet, instrument, staffNumber);
+   }
+
+   public static createSlurReader(musicSheet: MusicSheet): SlurReader {
+   return new SlurReader(musicSheet);
+   }
+
+   public static createLyricsReader(musicSheet: MusicSheet): LyricsReader {
+   return new LyricsReader(musicSheet);
+   }
+
+   public static createArticulationReader(): ArticulationReader {
+   return new ArticulationReader();
+   }
+   */
+}

+ 195 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts

@@ -0,0 +1,195 @@
+import {ArticulationEnum, VoiceEntry} from "../../VoiceData/VoiceEntry";
+import {IXmlAttribute, IXmlElement} from "../../../Common/FileIO/Xml";
+import * as log from "loglevel";
+import {TechnicalInstruction, TechnicalInstructionType} from "../../VoiceData/Instructions/TechnicalInstruction";
+import {OrnamentContainer, OrnamentEnum} from "../../VoiceData/OrnamentContainer";
+import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
+import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
+export class ArticulationReader {
+
+  private getAccEnumFromString(input: string): AccidentalEnum {
+    switch (input) {
+      case "natural":
+        return AccidentalEnum.NATURAL;
+      case "sharp":
+        return AccidentalEnum.SHARP;
+      case "sharp-sharp":
+      case "double-sharp":
+        return AccidentalEnum.DOUBLESHARP;
+      case "flat":
+        return AccidentalEnum.FLAT;
+      case "flat-flat":
+        return AccidentalEnum.DOUBLEFLAT;
+      default:
+        return AccidentalEnum.NONE;
+    }
+  }
+
+  /**
+   * This method adds an Articulation Expression to the currentVoiceEntry.
+   * @param node
+   * @param currentVoiceEntry
+   */
+  public addArticulationExpression(node: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    if (node !== undefined && node.elements().length > 0) {
+      const childNotes: IXmlElement[] = node.elements();
+      for (let idx: number = 0, len: number = childNotes.length; idx < len; ++idx) {
+        const childNote: IXmlElement = childNotes[idx];
+        const name: string = childNote.name;
+        try {
+          // some Articulations appear in Xml separated with a "-" (eg strong-accent), we remove it for enum parsing
+          name.replace("-", "");
+          const articulationEnum: ArticulationEnum = ArticulationEnum[name];
+          if (VoiceEntry.isSupportedArticulation(articulationEnum)) {
+            // staccato should be first
+            if (name === "staccato") {
+              if (currentVoiceEntry.Articulations.length > 0 &&
+                currentVoiceEntry.Articulations[0] !== ArticulationEnum.staccato) {
+                currentVoiceEntry.Articulations.splice(0, 0, articulationEnum);
+              }
+            }
+
+            // don't add the same articulation twice
+            if (currentVoiceEntry.Articulations.indexOf(articulationEnum) === -1) {
+              currentVoiceEntry.Articulations.push(articulationEnum);
+            }
+          }
+        } catch (ex) {
+          const errorMsg: string = "Invalid note articulation.";
+          log.debug("addArticulationExpression", errorMsg, ex);
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * This method add a Fermata to the currentVoiceEntry.
+   * @param xmlNode
+   * @param currentVoiceEntry
+   */
+  public addFermata(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    // fermata appears as separate tag in XML
+    let articulationEnum: ArticulationEnum = ArticulationEnum.fermata;
+    if (xmlNode.attributes().length > 0 && xmlNode.attribute("type") !== undefined) {
+      if (xmlNode.attribute("type").value === "inverted") {
+        articulationEnum = ArticulationEnum.invertedfermata;
+      }
+    }
+    // add to VoiceEntry
+    currentVoiceEntry.Articulations.push(articulationEnum);
+  }
+
+  /**
+   * This method add a technical Articulation to the currentVoiceEntry.
+   * @param xmlNode
+   * @param currentVoiceEntry
+   */
+  public addTechnicalArticulations(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    let node: IXmlElement = xmlNode.element("up-bow");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.upbow) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.upbow);
+      }
+    }
+    node = xmlNode.element("down-bow");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.downbow) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.downbow);
+      }
+    }
+    node = xmlNode.element("open-string");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.naturalharmonic) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.naturalharmonic);
+      }
+    }
+    node = xmlNode.element("stopped");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.lefthandpizzicato) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.lefthandpizzicato);
+      }
+    }
+    node = xmlNode.element("snap-pizzicato");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.snappizzicato) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.snappizzicato);
+      }
+    }
+    node = xmlNode.element("fingering");
+    if (node !== undefined) {
+      const currentTechnicalInstruction: TechnicalInstruction = new TechnicalInstruction();
+      currentTechnicalInstruction.type = TechnicalInstructionType.Fingering;
+      currentTechnicalInstruction.value = node.value;
+      currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
+    }
+  }
+
+  /**
+   * This method adds an Ornament to the currentVoiceEntry.
+   * @param ornamentsNode
+   * @param currentVoiceEntry
+   */
+  public addOrnament(ornamentsNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    if (ornamentsNode !== undefined) {
+      let ornament: OrnamentContainer = undefined;
+      let node: IXmlElement = ornamentsNode.element("trill-mark");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Trill);
+      }
+      node = ornamentsNode.element("turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Turn);
+      }
+      node = ornamentsNode.element("inverted-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.InvertedTurn);
+      }
+      node = ornamentsNode.element("delayed-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.DelayedTurn);
+      }
+      node = ornamentsNode.element("delayed-inverted-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.DelayedInvertedTurn);
+      }
+      node = ornamentsNode.element("mordent");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Mordent);
+      }
+      node = ornamentsNode.element("inverted-mordent");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.InvertedMordent);
+      }
+      if (ornament !== undefined) {
+        const accidentalsList: IXmlElement[] = ornamentsNode.elements("accidental-mark");
+        if (accidentalsList !== undefined) {
+          let placement: PlacementEnum = PlacementEnum.Below;
+          let accidental: AccidentalEnum = AccidentalEnum.NONE;
+          const accidentalsListArr: IXmlElement[] = accidentalsList;
+          for (let idx: number = 0, len: number = accidentalsListArr.length; idx < len; ++idx) {
+            const accidentalNode: IXmlElement = accidentalsListArr[idx];
+            let text: string = accidentalNode.value;
+            accidental = this.getAccEnumFromString(text);
+            const placementAttr: IXmlAttribute = accidentalNode.attribute("placement");
+            if (accidentalNode.hasAttributes && placementAttr !== undefined) {
+              text = placementAttr.value;
+              if (text === "above") {
+                placement = PlacementEnum.Above;
+              } else if (text === "below") {
+                placement = PlacementEnum.Below;
+              }
+            }
+            if (placement === PlacementEnum.Above) {
+              ornament.AccidentalAbove = accidental;
+            } else if (placement === PlacementEnum.Below) {
+              ornament.AccidentalBelow = accidental;
+            }
+          }
+        }
+        // add this to currentVoiceEntry
+        currentVoiceEntry.OrnamentContainer = ornament;
+      }
+    }
+  }
+}

+ 140 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/LyricsReader.ts

@@ -0,0 +1,140 @@
+import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
+import {VoiceEntry} from "../../VoiceData/VoiceEntry";
+import {IXmlElement} from "../../../Common/FileIO/Xml";
+import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
+import {ITextTranslation} from "../../Interfaces/ITextTranslation";
+import {MusicSheet} from "../../MusicSheet";
+
+export class LyricsReader {
+    private openLyricWords: { [_: number]: LyricWord; } = {};
+    private currentLyricWord: LyricWord;
+    private musicSheet: MusicSheet;
+
+    constructor(musicSheet: MusicSheet) {
+        this.musicSheet = musicSheet;
+    }
+    /**
+     * This method adds a single LyricEntry to a VoiceEntry
+     * @param {IXmlElement[]} lyricNodeList
+     * @param {VoiceEntry} currentVoiceEntry
+     */
+    public addLyricEntry(lyricNodeList: IXmlElement[], currentVoiceEntry: VoiceEntry): void {
+        if (lyricNodeList !== undefined) {
+            const lyricNodeListArr: IXmlElement[] = lyricNodeList;
+            for (let idx: number = 0, len: number = lyricNodeListArr.length; idx < len; ++idx) {
+                const lyricNode: IXmlElement = lyricNodeListArr[idx];
+                try {
+                    let syllabic: string = "single"; // Single as default
+                    if (lyricNode.element("text") !== undefined) {
+                        let textNode: IXmlElement = lyricNode.element("text");
+                        if (lyricNode.element("syllabic") !== undefined) {
+                            syllabic = lyricNode.element("syllabic").value;
+                        }
+                        if (textNode !== undefined) {
+                            const text: string = textNode.value;
+                            // <elision> separates Multiple syllabels on a single LyricNote
+                            // "-" text indicating separated syllabel should be ignored
+                            // we calculate the Dash element much later
+                            if (lyricNode.element("elision") !== undefined && text === "-") {
+                                const lyricNodeChildren: IXmlElement[] = lyricNode.elements();
+                                let elisionIndex: number = 0;
+                                for (let i: number = 0; i < lyricNodeChildren.length; i++) {
+                                    const child: IXmlElement = lyricNodeChildren[i];
+                                    if (child.name === "elision") {
+                                        elisionIndex = i;
+                                        break;
+                                    }
+                                }
+                                let nextText: IXmlElement = undefined;
+                                let nextSyllabic: IXmlElement = undefined;
+                                // read the next nodes
+                                if (elisionIndex > 0) {
+                                    for (let i: number = elisionIndex; i < lyricNodeChildren.length; i++) {
+                                        const child: IXmlElement = lyricNodeChildren[i];
+                                        if (child.name === "text") {
+                                            nextText = child;
+                                        }
+                                        if (child.name === "syllabic") {
+                                            nextSyllabic = child;
+                                        }
+                                    }
+                                }
+                                if (nextText !== undefined && nextSyllabic !== undefined) {
+                                    textNode = nextText;
+                                    syllabic = "middle";
+                                }
+                            }
+                            let currentLyricVerseNumber: number = 1;
+                            if (lyricNode.attributes() !== undefined && lyricNode.attribute("number") !== undefined) {
+                                try {
+                                    currentLyricVerseNumber = parseInt(lyricNode.attribute("number").value, 10);
+                                } catch (err) {
+                                    try {
+                                        const result: string[] = lyricNode.attribute("number").value.toLowerCase().split("verse");
+                                        if (result.length > 1) {
+                                            currentLyricVerseNumber = parseInt(result[1], 10);
+                                        }
+                                    } catch (err) {
+                                        const errorMsg: string =
+                                        ITextTranslation.translateText("ReaderErrorMessages/LyricVerseNumberError", "Invalid lyric verse number");
+                                        this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                                        continue;
+                                    }
+                                }
+                            }
+                            let lyricsEntry: LyricsEntry = undefined;
+                            if (syllabic === "single" || syllabic === "end") {
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) { // word end given or some word still open
+                                    this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                    this.currentLyricWord.Syllables.push(lyricsEntry);
+                                    delete this.openLyricWords[currentLyricVerseNumber];
+                                    this.currentLyricWord = undefined;
+                                } else { // single syllable given or end given while no word has been started
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
+                                }
+                                lyricsEntry.extend = lyricNode.element("extend") !== undefined;
+                            } else if (syllabic === "begin") { // first finishing, if a word already is open (can only happen, when wrongly given)
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) {
+                                    delete this.openLyricWords[currentLyricVerseNumber];
+                                    this.currentLyricWord = undefined;
+                                }
+                                this.currentLyricWord = new LyricWord();
+                                this.openLyricWords[currentLyricVerseNumber] = this.currentLyricWord;
+                                lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                this.currentLyricWord.Syllables.push(lyricsEntry);
+                            } else if (syllabic === "middle") {
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) {
+                                    this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                    this.currentLyricWord.Syllables.push(lyricsEntry);
+                                } else {
+                                    // in case the wrong syllabel information is given, create a single Entry and add it to currentVoiceEntry
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
+                                }
+                            }
+                            // add each LyricEntry to currentVoiceEntry
+                            if (lyricsEntry !== undefined) {
+                                // only add the lyric entry if not another entry has already been given:
+                                if (!currentVoiceEntry.LyricsEntries[currentLyricVerseNumber] !== undefined) {
+                                    currentVoiceEntry.LyricsEntries.setValue(currentLyricVerseNumber, lyricsEntry);
+                                }
+                                // save in currentInstrument the verseNumber (only once)
+                                if (!currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers[currentLyricVerseNumber] !== undefined) {
+                                    currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.push(currentLyricVerseNumber);
+                                }
+                            }
+                        }
+                    }
+                } catch (err) {
+                    const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/LyricError", "Error while reading lyric entry.");
+                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                    continue;
+                }
+            }
+            // Squash to unique numbers
+            currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers =
+            currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.filter((lvn, index, self) => self.indexOf(lvn) === index);
+        }
+    }
+}

+ 99 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionCalculator.ts

@@ -0,0 +1,99 @@
+import {SourceMeasure} from "../../VoiceData/SourceMeasure";
+import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {RepetitionInstructionComparer} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {ArgumentOutOfRangeException} from "../../Exceptions";
+import {MusicSheet} from "../../MusicSheet";
+
+export class RepetitionCalculator {
+  private musicSheet: MusicSheet;
+  private repetitionInstructions: RepetitionInstruction[] = [];
+  private currentMeasure: SourceMeasure;
+  private currentMeasureIndex: number;
+
+  /**
+   * Is called when all repetition symbols have been read from xml.
+   * Creates the repetition instructions and adds them to the corresponding measure.
+   * Creates the logical repetition objects for iteration and playback.
+   * @param musicSheet
+   * @param repetitionInstructions
+   */
+  public calculateRepetitions(musicSheet: MusicSheet, repetitionInstructions: RepetitionInstruction[]): void {
+    this.musicSheet = <MusicSheet>musicSheet;
+    this.repetitionInstructions = repetitionInstructions;
+    const sourceMeasures: SourceMeasure[] = this.musicSheet.SourceMeasures;
+    for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[idx];
+      this.currentMeasureIndex = instruction.measureIndex;
+      this.currentMeasure = sourceMeasures[this.currentMeasureIndex];
+      this.handleRepetitionInstructions(instruction);
+    }
+
+    // if there are more than one instruction at measure begin or end,
+    // sort them according to the nesting of the repetitions:
+    for (let idx: number = 0, len: number = this.musicSheet.SourceMeasures.length; idx < len; ++idx) {
+      const measure: SourceMeasure = this.musicSheet.SourceMeasures[idx];
+      if (measure.FirstRepetitionInstructions.length > 1) {
+        measure.FirstRepetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+      }
+      if (measure.LastRepetitionInstructions.length > 1) {
+        measure.LastRepetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+      }
+    }
+  }
+
+  private handleRepetitionInstructions(currentRepetitionInstruction: RepetitionInstruction): boolean {
+    switch (currentRepetitionInstruction.type) {
+      case RepetitionInstructionEnum.StartLine:
+        this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.BackJumpLine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Ending:
+        // set ending start or end
+        if (currentRepetitionInstruction.alignment === AlignmentType.Begin) {  // ending start
+          this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        } else { // ending end
+          for (let idx: number = 0, len: number = currentRepetitionInstruction.endingIndices.length; idx < len; ++idx) {
+            this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+          }
+        }
+        break;
+      case RepetitionInstructionEnum.Segno:
+        this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Fine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.ToCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Coda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapo:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegno:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegnoAlFine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapoAlFine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegnoAlCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapoAlCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.None:
+        break;
+      default:
+        throw new ArgumentOutOfRangeException("currentRepetitionInstruction");
+    }
+    return true;
+  }
+}

+ 380 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts

@@ -0,0 +1,380 @@
+import {MusicSheet} from "../../MusicSheet";
+import {IXmlElement} from "../../../Common/FileIO/Xml";
+import {SourceMeasure} from "../../VoiceData/SourceMeasure";
+import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {RepetitionInstructionComparer} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {StringUtil} from "../../../Common/Strings/StringUtil";
+export class RepetitionInstructionReader {
+  /**
+   * A global list of all repetition instructions in the musicsheet.
+   */
+  public repetitionInstructions: RepetitionInstruction[];
+  public xmlMeasureList: IXmlElement[][];
+  private musicSheet: MusicSheet;
+  private currentMeasureIndex: number;
+
+  public set MusicSheet(value: MusicSheet) {
+    this.musicSheet = value;
+    this.xmlMeasureList = new Array(this.musicSheet.Instruments.length);
+    this.repetitionInstructions = [];
+  }
+
+  /**
+   * is called when starting reading an xml measure
+   * @param measure
+   * @param currentMeasureIndex
+   */
+  public prepareReadingMeasure(measure: SourceMeasure, currentMeasureIndex: number): void {
+    this.currentMeasureIndex = currentMeasureIndex;
+  }
+
+  public handleLineRepetitionInstructions(barlineNode: IXmlElement, pieceEndingDetected: boolean): void {
+    pieceEndingDetected = false;
+    if (barlineNode.elements().length > 0) {
+      let location: string = "";
+      let hasRepeat: boolean = false;
+      let direction: string = "";
+      let type: string = "";
+      let style: string = "";
+      const endingIndices: number[] = [];
+
+      // read barline style
+      const styleNode: IXmlElement = barlineNode.element("bar-style");
+
+      // if location is ommited in Xml, right is implied (from documentation)
+      if (styleNode !== undefined) {
+        style = styleNode.value;
+      }
+      if (barlineNode.attributes().length > 0 && barlineNode.attribute("location") !== undefined) {
+        location = barlineNode.attribute("location").value;
+      } else {
+        location = "right";
+      }
+      const barlineNodeElements: IXmlElement[] = barlineNode.elements();
+
+      // read repeat- or ending line information
+      for (let idx: number = 0, len: number = barlineNodeElements.length; idx < len; ++idx) {
+        const childNode: IXmlElement = barlineNodeElements[idx];
+        if ("repeat" === childNode.name && childNode.hasAttributes) {
+          hasRepeat = true;
+          direction = childNode.attribute("direction").value;
+        } else if ( "ending" === childNode.name && childNode.hasAttributes &&
+                    childNode.attribute("type") !== undefined && childNode.attribute("number") !== undefined) {
+          type = childNode.attribute("type").value;
+          const num: string = childNode.attribute("number").value;
+
+          // Parse the given ending indices:
+          // handle cases like: "1, 2" or "1 + 2" or even "1 - 3, 6"
+          const separatedEndingIndices: string[] = num.split("[,+]");
+          for (let idx2: number = 0, len2: number = separatedEndingIndices.length; idx2 < len2; ++idx2) {
+            const separatedEndingIndex: string = separatedEndingIndices[idx2];
+            const indices: string[] = separatedEndingIndex.match("[0-9]");
+
+            // check if possibly something like "1-3" is given..
+            if (separatedEndingIndex.search("-") !== -1 && indices.length === 2) {
+              const startIndex: number = parseInt(indices[0], 10);
+              const endIndex: number = parseInt(indices[1], 10);
+              for (let index: number = startIndex; index <= endIndex; index++) {
+                endingIndices.push(index);
+              }
+            } else {
+              for (let idx3: number = 0, len3: number = indices.length; idx3 < len3; ++idx3) {
+                const index: string = indices[idx3];
+                endingIndices.push(parseInt(index, 10));
+              }
+            }
+          }
+        }
+      }
+
+      // reset measure counter if not lastMeasure
+      if (style === "light-heavy" && endingIndices.length === 0 && !hasRepeat) {
+        pieceEndingDetected = true;
+      }
+      if (hasRepeat || endingIndices.length > 0) {
+        if (location === "left") {
+          if (type === "start") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
+                                                                                    AlignmentType.Begin, undefined, endingIndices);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+          if (direction === "forward") {
+            // start new Repetition
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.StartLine);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+        } else { // location right
+          if (type === "stop" || type === "discontinue") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
+                                                                                    AlignmentType.End, undefined, endingIndices);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+          if (direction === "backward") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.BackJumpLine);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+        }
+      }
+    }
+  }
+
+  public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean {
+    const wordsNode: IXmlElement = directionTypeNode.element("words");
+    if (wordsNode !== undefined) {
+      // must Trim string and ToLower before compare
+      const innerText: string = wordsNode.value.trim().toLowerCase();
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al fine") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlFine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al fine") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlFine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dacapo") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "da capo")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapo);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dalsegno") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dal segno")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegno);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "tocoda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "to coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "a coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "a la coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.ToCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Fine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition > 0.5) {
+          measureIndex++;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "segno")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition > 0.5) {
+          measureIndex++;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+    } else if (directionTypeNode.element("segno") !== undefined) {
+      let measureIndex: number = this.currentMeasureIndex;
+      if (relativeMeasurePosition > 0.5) {
+        measureIndex++;
+      }
+      const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
+      this.addInstruction(this.repetitionInstructions, newInstruction);
+      return true;
+    } else if (directionTypeNode.element("coda") !== undefined) {
+      let measureIndex: number = this.currentMeasureIndex;
+      if (relativeMeasurePosition > 0.5) {
+        measureIndex++;
+      }
+      const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
+      this.addInstruction(this.repetitionInstructions, newInstruction);
+      return true;
+    }
+    return false;
+  }
+
+  public removeRedundantInstructions(): void {
+    let segnoCount: number = 0;
+    let codaCount: number = 0;
+    //const fineCount: number = 0;
+    let toCodaCount: number = 0;
+    let dalSegnaCount: number = 0;
+    for (let index: number = 0; index < this.repetitionInstructions.length; index++) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      switch (instruction.type) {
+        case RepetitionInstructionEnum.Coda:
+          if (toCodaCount > 0) {
+            if (this.findInstructionInPreviousMeasure(index, instruction.measureIndex, RepetitionInstructionEnum.ToCoda)) {
+              instruction.type = RepetitionInstructionEnum.None;
+            }
+          }
+          if (codaCount === 0 && toCodaCount === 0) {
+            instruction.type = RepetitionInstructionEnum.ToCoda;
+            instruction.alignment = AlignmentType.End;
+            instruction.measureIndex--;
+          }
+          break;
+        case RepetitionInstructionEnum.Segno:
+          if (segnoCount - dalSegnaCount > 0) { // two segnos in a row
+            let foundInstruction: boolean = false;
+            for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) {
+              const instr: RepetitionInstruction = this.repetitionInstructions[idx];
+              if (instruction.measureIndex - instr.measureIndex === 1) {
+                switch (instr.type) {
+                  case RepetitionInstructionEnum.BackJumpLine:
+                    if (toCodaCount - codaCount > 0) { // open toCoda existing
+                      instr.type = RepetitionInstructionEnum.DalSegnoAlCoda;
+                    } else {
+                      instr.type = RepetitionInstructionEnum.DalSegno;
+                    }
+                    instruction.type = RepetitionInstructionEnum.None;
+                    foundInstruction = true;
+                    break;
+                  case RepetitionInstructionEnum.DalSegno:
+                  case RepetitionInstructionEnum.DalSegnoAlFine:
+                  case RepetitionInstructionEnum.DalSegnoAlCoda:
+                    instruction.type = RepetitionInstructionEnum.None;
+                    foundInstruction = true;
+                    break;
+                  default:
+                    break;
+                }
+              }
+              if (foundInstruction) {
+                break;
+              }
+            }
+            if (foundInstruction) {
+              break;
+            }
+            // convert to dal segno instruction:
+            if (toCodaCount - codaCount > 0) { // open toCoda existing
+              instruction.type = RepetitionInstructionEnum.DalSegnoAlCoda;
+            } else {
+              instruction.type = RepetitionInstructionEnum.DalSegno;
+            }
+            instruction.alignment = AlignmentType.End;
+            instruction.measureIndex--;
+          }
+          break;
+        default:
+          break;
+      }
+
+      // check if this  instruction already exists or is otherwise redundant:
+      if (this.backwardSearchForPreviousIdenticalInstruction(index, instruction) || instruction.type === RepetitionInstructionEnum.None) {
+        this.repetitionInstructions.splice(index, 1);
+        index--;
+      } else {
+        switch (instruction.type) {
+          case RepetitionInstructionEnum.Fine:
+            //fineCount++;
+            break;
+          case RepetitionInstructionEnum.ToCoda:
+            toCodaCount++;
+            break;
+          case RepetitionInstructionEnum.Coda:
+            codaCount++;
+            break;
+          case RepetitionInstructionEnum.Segno:
+            segnoCount++;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlFine:
+          case RepetitionInstructionEnum.DalSegnoAlCoda:
+            dalSegnaCount++;
+            break;
+          default:
+            break;
+        }
+      }
+    }
+    this.repetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+  }
+
+  private findInstructionInPreviousMeasure(currentInstructionIndex: number, currentMeasureIndex: number, searchedType: RepetitionInstructionEnum): boolean {
+    for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      if (currentMeasureIndex - instruction.measureIndex === 1 && instruction.type === searchedType) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private backwardSearchForPreviousIdenticalInstruction(currentInstructionIndex: number, currentInstruction: RepetitionInstruction): boolean {
+    for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      if (instruction.equals(currentInstruction)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private addInstruction(currentRepetitionInstructions: RepetitionInstruction[], newInstruction: RepetitionInstruction): void {
+    let addInstruction: boolean = true;
+    for (let idx: number = 0, len: number = currentRepetitionInstructions.length; idx < len; ++idx) {
+      const repetitionInstruction: RepetitionInstruction = currentRepetitionInstructions[idx];
+      if (newInstruction.equals(repetitionInstruction)) {
+        addInstruction = false;
+        break;
+      }
+    }
+    if (addInstruction) {
+      currentRepetitionInstructions.push(newInstruction);
+    }
+  }
+}

File diff suppressed because it is too large
+ 576 - 566
src/MusicalScore/ScoreIO/VoiceGenerator.ts


+ 6 - 3
src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts

@@ -41,16 +41,19 @@ export class RepetitionInstruction /*implements IComparable*/ {
 
      }
      */
-    constructor(measureIndex: number, endingIndices: number[], type: RepetitionInstructionEnum, alignment: AlignmentType, parentRepetition: Repetition) {
+    constructor(measureIndex: number, type: RepetitionInstructionEnum, alignment: AlignmentType = AlignmentType.End,
+                parentRepetition: Repetition = undefined, endingIndices: number[] = undefined) {
         this.measureIndex = measureIndex;
-        this.endingIndices = endingIndices.slice();
+        if (endingIndices !== undefined) {
+            this.endingIndices = endingIndices.slice();
+        }
         this.type = type;
         this.alignment = alignment;
         this.parentRepetition = parentRepetition;
     }
 
     public measureIndex: number;
-    public endingIndices: number[];
+    public endingIndices: number[] = undefined;
     public type: RepetitionInstructionEnum;
     public alignment: AlignmentType;
     public parentRepetition: Repetition;

+ 8 - 1
src/MusicalScore/VoiceData/Lyrics/LyricsEntry.ts

@@ -2,14 +2,17 @@ import {LyricWord} from "./LyricsWord";
 import {VoiceEntry} from "../VoiceEntry";
 
 export class LyricsEntry {
-    constructor(text: string, word: LyricWord, parent: VoiceEntry) {
+    constructor(text: string, verseNumber: number, word: LyricWord, parent: VoiceEntry) {
         this.text = text;
         this.word = word;
         this.parent = parent;
+        this.verseNumber = verseNumber;
     }
     private text: string;
     private word: LyricWord;
     private parent: VoiceEntry;
+    private verseNumber: number;
+    public extend: boolean;
 
     public get Text(): string {
         return this.text;
@@ -26,4 +29,8 @@ export class LyricsEntry {
     public set Parent(value: VoiceEntry) {
         this.parent = value;
     }
+
+    public get VerseNumber(): number {
+        return this.verseNumber;
+    }
 }

+ 4 - 0
src/MusicalScore/VoiceData/Note.ts

@@ -103,6 +103,10 @@ export class Note {
         this.playbackInstrumentId = value;
     }
 
+    public isRest(): boolean {
+        return this.Pitch === undefined;
+    }
+
     public calculateNoteLengthWithoutTie(): Fraction {
         const withoutTieLength: Fraction = this.length.clone();
         if (this.tie !== undefined) {

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

@@ -1,7 +1,7 @@
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {VerticalSourceStaffEntryContainer} from "./VerticalSourceStaffEntryContainer";
 import {SourceStaffEntry} from "./SourceStaffEntry";
-import {RepetitionInstruction} from "./Instructions/RepetitionInstruction";
+import {RepetitionInstruction, RepetitionInstructionEnum} from "./Instructions/RepetitionInstruction";
 import {Staff} from "./Staff";
 import {VoiceEntry} from "./VoiceEntry";
 import {Voice} from "./Voice";
@@ -400,6 +400,9 @@ export class SourceMeasure extends BaseIdClass {
     public beginsWithLineRepetition(): boolean {
         for (let idx: number = 0, len: number = this.FirstRepetitionInstructions.length; idx < len; ++idx) {
             const instr: RepetitionInstruction = this.FirstRepetitionInstructions[idx];
+            if (instr.type === RepetitionInstructionEnum.StartLine) {
+                return true;
+            }
             if (instr.parentRepetition !== undefined && instr === instr.parentRepetition.startMarker && !instr.parentRepetition.FromWords) {
                 return true;
             }
@@ -414,6 +417,10 @@ export class SourceMeasure extends BaseIdClass {
     public endsWithLineRepetition(): boolean {
         for (let idx: number = 0, len: number = this.LastRepetitionInstructions.length; idx < len; ++idx) {
             const instruction: RepetitionInstruction = this.LastRepetitionInstructions[idx];
+            if (instruction.type === RepetitionInstructionEnum.BackJumpLine) {
+                return true;
+            }
+
             const rep: Repetition = instruction.parentRepetition;
             if (rep === undefined) {
                 continue;

+ 1 - 1
src/OSMD/OSMD.ts

@@ -20,7 +20,7 @@ export class OSMD {
      * @param container is either the ID, or the actual "div" element which will host the music sheet
      * @autoResize automatically resize the sheet to full page width on window resize
      */
-    constructor(container: string|HTMLElement, autoResize: boolean = false, backend: string = "canvas") {
+    constructor(container: string|HTMLElement, autoResize: boolean = false, backend: string = "svg") {
         // Store container element
         if (typeof container === "string") {
             // ID passed

+ 1 - 1
test/data/Beethoven_AnDieFerneGeliebte.xml

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <score-partwise version="2.0">
   <work>
     <work-number>Op. 98</work-number>

二進制
test/data/Cornelius_P_Christbaum_Opus_8_1_1865.mxl


+ 1 - 1
test/data/Mozart_DasVeilchen.xml

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <score-partwise version="2.0">
   <work>
     <work-number>K. 476</work-number>

Some files were not shown because too many files changed in this diff