瀏覽代碼

cherry-picked from Tab Branch (tabs are rendered)

Matthias Uiberacker 7 年之前
父節點
當前提交
9ce9a98acb

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

@@ -43,6 +43,7 @@ export abstract class GraphicalMeasure extends GraphicalObject {
     public parentSourceMeasure: SourceMeasure;
     public staffEntries: GraphicalStaffEntry[];
     public parentMusicSystem: MusicSystem;
+    public tabMeasure: GraphicalMeasure = undefined;
     /**
      * The x-width of possibly existing: repetition start line, clef, key, rhythm.
      */

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

@@ -49,6 +49,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     public graphicalVoiceEntries: GraphicalVoiceEntry[];
     public staffEntryParent: GraphicalStaffEntry;
     public parentVerticalContainer: VerticalGraphicalStaffEntryContainer;
+    public tabStaffEntry: GraphicalStaffEntry = undefined;
 
     private graphicalInstructions: AbstractGraphicalInstruction[] = [];
     private graphicalTies: GraphicalTie[] = [];

+ 69 - 7
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -7,7 +7,7 @@ import { Fraction } from "../../Common/DataObjects/Fraction";
 import { Note } from "../VoiceData/Note";
 import { MusicSheet } from "../MusicSheet";
 import { GraphicalMeasure } from "./GraphicalMeasure";
-import { ClefInstruction } from "../VoiceData/Instructions/ClefInstruction";
+import {ClefInstruction, ClefEnum} from "../VoiceData/Instructions/ClefInstruction";
 import { LyricWord } from "../VoiceData/Lyrics/LyricsWord";
 import { SourceMeasure } from "../VoiceData/SourceMeasure";
 import { GraphicalMusicPage } from "./GraphicalMusicPage";
@@ -66,6 +66,7 @@ import { AbstractTempoExpression } from "../VoiceData/Expressions/AbstractTempoE
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
 import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
+import { TabNote } from "../VoiceData/TabNote";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -685,6 +686,11 @@ export abstract class MusicSheetCalculator {
                 if (graphicalMeasure.isVisible()) {
                     visiblegraphicalMeasures.push(graphicalMeasure);
 
+                    // add Tab Measure if exists:
+                    if (graphicalMeasure.tabMeasure !== undefined) {
+                        visiblegraphicalMeasures.push(graphicalMeasure.tabMeasure);
+                    }
+
                     if (EngravingRules.Rules.ColoringEnabled) {
                         // (re-)color notes
                         for (const staffEntry of graphicalMeasure.staffEntries) {
@@ -692,6 +698,15 @@ export abstract class MusicSheetCalculator {
                                 gve.color();
                             }
                         }
+
+                        if (graphicalMeasure.tabMeasure !== undefined) {
+                            // (re-)color tab notes
+                            for (const staffEntry of graphicalMeasure.tabMeasure.staffEntries) {
+                                for (const gve of staffEntry.graphicalVoiceEntries) {
+                                    gve.color();
+                                }
+                            }
+                        }
                     }
                 }
             }
@@ -1543,8 +1558,16 @@ export abstract class MusicSheetCalculator {
         } else {
             this.calculateStemDirectionFromVoices(voiceEntry);
         }
+        // if GraphicalStaffEntry has been created earlier (because of Tie), then the GraphicalNotesLists have also been created
         const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
         gve.octaveShiftValue = octaveShiftValue;
+        // check for Tabs:
+        const tabStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.tabStaffEntry;
+        let graphicalTabVoiceEntry: GraphicalVoiceEntry;
+        if (tabStaffEntry !== undefined) {
+            graphicalTabVoiceEntry = tabStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
+        }
+
         for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
             const note: Note = voiceEntry.Notes[idx];
             if (note === undefined) {
@@ -1573,6 +1596,17 @@ export abstract class MusicSheetCalculator {
                     this.handleTuplet(graphicalNote, note.NoteTuplet, openTuplets);
                 }
             }
+
+            // handle TabNotes:
+            if (note instanceof TabNote) {
+                const graphicalTabNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(  note,
+                                                                                                        graphicalTabVoiceEntry,
+                                                                                                        activeClef,
+                                                                                                        octaveShiftValue,
+                                                                                                        undefined);
+                tabStaffEntry.addGraphicalNoteToListAtCorrectYPosition(graphicalTabVoiceEntry, graphicalTabNote);
+                graphicalTabNote.PositionAndShape.calculateBoundingBox();
+        }
         }
         if (voiceEntry.Articulations.length > 0) {
             this.handleVoiceEntryArticulations(voiceEntry.Articulations, voiceEntry, graphicalStaffEntry);
@@ -1979,7 +2013,11 @@ export abstract class MusicSheetCalculator {
                                    staffEntryLinks: StaffEntryLink[]): GraphicalMeasure {
         const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
         const measure: GraphicalMeasure = MusicSheetCalculator.symbolFactory.createGraphicalMeasure(sourceMeasure, staff);
+        if (activeClefs[staffIndex].ClefType === ClefEnum.TAB) {
+            measure.tabMeasure = MusicSheetCalculator.symbolFactory.createTabStaffMeasure(sourceMeasure, staff);
+        }
         measure.hasError = sourceMeasure.getErrorInMeasure(staffIndex);
+        // check for key instruction changes
         if (sourceMeasure.FirstInstructionsStaffEntries[staffIndex] !== undefined) {
             for (let idx: number = 0, len: number = sourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions.length; idx < len; ++idx) {
                 const instruction: AbstractNotationInstruction = sourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[idx];
@@ -1996,6 +2034,7 @@ export abstract class MusicSheetCalculator {
                 }
             }
         }
+        // check for octave shifts
         for (let idx: number = 0, len: number = sourceMeasure.StaffLinkedExpressions[staffIndex].length; idx < len; ++idx) {
             const multiExpression: MultiExpression = sourceMeasure.StaffLinkedExpressions[staffIndex][idx];
             if (multiExpression.OctaveShiftStart !== undefined) {
@@ -2007,36 +2046,57 @@ export abstract class MusicSheetCalculator {
                 );
             }
         }
+        // create GraphicalStaffEntries - always check for possible null Entry
         for (let entryIndex: number = 0; entryIndex < sourceMeasure.VerticalSourceStaffEntryContainers.length; entryIndex++) {
             const sourceStaffEntry: SourceStaffEntry = sourceMeasure.VerticalSourceStaffEntryContainers[entryIndex].StaffEntries[staffIndex];
+            // is there a SourceStaffEntry at this Index
             if (sourceStaffEntry !== undefined) {
+                // a SourceStaffEntry exists
+                // is there an inStaff ClefInstruction? -> update activeClef
                 for (let idx: number = 0, len: number = sourceStaffEntry.Instructions.length; idx < len; ++idx) {
                     const abstractNotationInstruction: AbstractNotationInstruction = sourceStaffEntry.Instructions[idx];
                     if (abstractNotationInstruction instanceof ClefInstruction) {
                         activeClefs[staffIndex] = <ClefInstruction>abstractNotationInstruction;
                     }
                 }
+                // create new GraphicalStaffEntry
                 const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
-                if (measure.staffEntries.length > entryIndex) {
+                if (entryIndex < measure.staffEntries.length) {
+                    // a GraphicalStaffEntry has been inserted already at this Index (from Tie)
                     measure.addGraphicalStaffEntryAtTimestamp(graphicalStaffEntry);
                 } else {
                     measure.addGraphicalStaffEntry(graphicalStaffEntry);
                 }
+                // if there is a Tab measure
+                if (measure.tabMeasure !== undefined) {
+                    // create new Tab-GraphicalStaffEntry in Tab-Measure
+                    const tabStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure.tabMeasure);
+                    graphicalStaffEntry.tabStaffEntry = tabStaffEntry;
+                    if (entryIndex < measure.tabMeasure.staffEntries.length) {
+                        // a GraphicalStaffEntry has been inserted already at this Index (from Tie)
+                        measure.tabMeasure.addGraphicalStaffEntryAtTimestamp(tabStaffEntry);
+                    } else {
+                        measure.tabMeasure.addGraphicalStaffEntry(tabStaffEntry);
+                    }
+                }
+
                 const linkedNotes: Note[] = [];
                 if (sourceStaffEntry.Link !== undefined) {
                     sourceStaffEntry.findLinkedNotes(linkedNotes);
                     this.handleStaffEntryLink(graphicalStaffEntry, staffEntryLinks);
                 }
+                // check for possible OctaveShift
                 let octaveShiftValue: OctaveEnum = OctaveEnum.NONE;
                 if (openOctaveShifts[staffIndex] !== undefined) {
-                    const octaveShiftParams: OctaveShiftParams = openOctaveShifts[staffIndex];
-                    if (octaveShiftParams.getAbsoluteStartTimestamp.lte(sourceStaffEntry.AbsoluteTimestamp) &&
-                        sourceStaffEntry.AbsoluteTimestamp.lte(octaveShiftParams.getAbsoluteEndTimestamp)) {
-                        octaveShiftValue = octaveShiftParams.getOpenOctaveShift.Type;
+                    if (openOctaveShifts[staffIndex].getAbsoluteStartTimestamp.lte(sourceStaffEntry.AbsoluteTimestamp) &&
+                        sourceStaffEntry.AbsoluteTimestamp.lte(openOctaveShifts[staffIndex].getAbsoluteEndTimestamp)) {
+                        octaveShiftValue = openOctaveShifts[staffIndex].getOpenOctaveShift.Type;
                     }
                 }
+                // for each visible Voice create the corresponding GraphicalNotes
                 for (let idx: number = 0, len: number = sourceStaffEntry.VoiceEntries.length; idx < len; ++idx) {
                     const voiceEntry: VoiceEntry = sourceStaffEntry.VoiceEntries[idx];
+                    // Normal Notes...
                     octaveShiftValue = this.handleVoiceEntry(
                         voiceEntry, graphicalStaffEntry,
                         accidentalCalculator, openLyricWords,
@@ -2045,6 +2105,7 @@ export abstract class MusicSheetCalculator {
                         sourceStaffEntry
                     );
                 }
+                // SourceStaffEntry has inStaff ClefInstruction -> create graphical clef
                 if (sourceStaffEntry.Instructions.length > 0) {
                     const clefInstruction: ClefInstruction = <ClefInstruction>sourceStaffEntry.Instructions[0];
                     MusicSheetCalculator.symbolFactory.createInStaffClef(graphicalStaffEntry, clefInstruction);
@@ -2060,6 +2121,7 @@ export abstract class MusicSheetCalculator {
         }
 
         accidentalCalculator.doCalculationsAtEndOfMeasure();
+        // update activeClef given at end of measure if needed
         if (sourceMeasure.LastInstructionsStaffEntries[staffIndex] !== undefined) {
             const lastStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
             for (let idx: number = 0, len: number = lastStaffEntry.Instructions.length; idx < len; ++idx) {
@@ -2073,7 +2135,7 @@ export abstract class MusicSheetCalculator {
             const multiExpression: MultiExpression = sourceMeasure.StaffLinkedExpressions[staffIndex][idx];
             if (multiExpression.OctaveShiftEnd !== undefined && openOctaveShifts[staffIndex] !== undefined &&
                 multiExpression.OctaveShiftEnd === openOctaveShifts[staffIndex].getOpenOctaveShift) {
-                openOctaveShifts[staffIndex] = undefined;
+                    openOctaveShifts[staffIndex] = undefined;
             }
         }
         // check wantedStemDirections of beam notes at end of measure (e.g. for beam with grace notes)

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

@@ -253,7 +253,7 @@ export class MusicSystemBuilder {
      * Initialize a new [[MusicSystem]].
      * @returns {MusicSystem}
      */
-    private initMusicSystem(measures: StaffMeasure[]): MusicSystem {
+    private initMusicSystem(measures: GraphicalMeasure[]): MusicSystem {
         this.currentSystemParams = new SystemBuildParameters();
         const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.currentMusicPage, this.globalSystemIndex++);
         this.currentSystemParams.currentSystem = musicSystem;
@@ -271,7 +271,7 @@ export class MusicSystemBuilder {
             - this.rules.PageRightMargin - this.rules.SystemLeftMargin - this.rules.SystemRightMargin;
     }
 
-    private layoutSystemStaves(measures: StaffMeasure[]): void {
+    private layoutSystemStaves(measures: GraphicalMeasure[]): void {
         const systemWidth: number = this.getFullPageSystemWidth();
         const musicSystem: MusicSystem = this.currentSystemParams.currentSystem;
         const boundingBox: BoundingBox = musicSystem.PositionAndShape;

+ 0 - 10
src/MusicalScore/Graphical/TabStaffLine.ts

@@ -1,10 +0,0 @@
-import { StaffLine } from "./StaffLine";
-import { MusicSystem } from "./MusicSystem";
-import { Staff } from "../VoiceData/Staff";
-
-export abstract class TabStaffLine extends StaffLine {
-
-    constructor(parentSystem: MusicSystem, parentStaff: Staff) {
-        super(parentSystem, parentStaff);
-    }
-}

+ 32 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -25,6 +25,7 @@ import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 import { EngravingRules } from "../EngravingRules";
 import { Note } from "../..";
 import StaveNote = Vex.Flow.StaveNote;
+import { TabNote } from "../../VoiceData/TabNote";
 
 /**
  * Helper class, which contains static methods which actually convert
@@ -465,6 +466,36 @@ export class VexFlowConverter {
     }
 
     /**
+     * Convert a set of GraphicalNotes to a VexFlow StaveNote
+     * @param notes form a chord on the staff
+     * @returns {Vex.Flow.StaveNote}
+     */
+    public static CreateTabNote(gve: GraphicalVoiceEntry): Vex.Flow.TabNote {
+        const tabPositions: {str: number, fret: number}[] = [];
+        const frac: Fraction = gve.notes[0].graphicalNoteLength;
+        const isTuplet: boolean = gve.notes[0].sourceNote.NoteTuplet !== undefined;
+        let duration: string = VexFlowConverter.duration(frac, isTuplet);
+        let numDots: number = 0;
+        for (const note of gve.notes) {
+            const tabNote: TabNote = note.sourceNote as TabNote;
+            const tabPosition: {str: number, fret: number} = {str: tabNote.StringNumber, fret: tabNote.FretNumber};
+            tabPositions.push(tabPosition);
+            if (numDots < note.numberOfDots) {
+                numDots = note.numberOfDots;
+            }
+        }
+        for (let i: number = 0, len: number = numDots; i < len; ++i) {
+            duration += "d";
+        }
+        const vfnote: Vex.Flow.TabNote = new Vex.Flow.TabNote({
+            duration: duration,
+            positions: tabPositions,
+        });
+
+        return vfnote;
+    }
+
+    /**
      * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
      *
      * @param clef The OSMD object to be converted representing the clef
@@ -551,7 +582,7 @@ export class VexFlowConverter {
 
             // TAB Clef
             case ClefEnum.TAB:
-                type = "tab";
+                type = "french";
                 break;
             default:
         }

+ 15 - 4
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -5,7 +5,6 @@ import {MusicSystem} from "../MusicSystem";
 import {VexFlowMusicSystem} from "./VexFlowMusicSystem";
 import {Staff} from "../../VoiceData/Staff";
 import {StaffLine} from "../StaffLine";
-import {VexFlowStaffLine} from "./VexFlowStaffLine";
 import {SourceMeasure} from "../../VoiceData/SourceMeasure";
 import {GraphicalMeasure} from "../GraphicalMeasure";
 import {VexFlowMeasure} from "./VexFlowMeasure";
@@ -27,6 +26,8 @@ import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { VoiceEntry } from "../../VoiceData/VoiceEntry";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 import { VexFlowConverter } from "./VexFlowConverter";
+import { VexFlowTabMeasure } from "./VexFlowTabMeasure";
+import { VexFlowStaffLine } from "./VexFlowStaffLine";
 
 export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
     /**
@@ -56,8 +57,18 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      * @param staff
      * @returns {VexFlowMeasure}
      */
-    public createGraphicalMeasure(sourceMeasure: SourceMeasure, staff: Staff): GraphicalMeasure {
-        return new VexFlowMeasure(staff, undefined, sourceMeasure);
+    public createGraphicalMeasure(sourceMeasure: SourceMeasure, staff: Staff, isTabMeasure: boolean = false): GraphicalMeasure {
+        return new VexFlowMeasure(staff, sourceMeasure, undefined);
+    }
+
+    /**
+     * Construct an empty Tab staffMeasure from the given source measure and staff.
+     * @param sourceMeasure
+     * @param staff
+     * @returns {VexFlowTabMeasure}
+     */
+    public createTabStaffMeasure(sourceMeasure: SourceMeasure, staff: Staff): GraphicalMeasure {
+        return new VexFlowTabMeasure(staff, sourceMeasure);
     }
 
     /**
@@ -66,7 +77,7 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      * @returns {VexFlowMeasure}
      */
     public createExtraGraphicalMeasure(staffLine: StaffLine): GraphicalMeasure {
-        return new VexFlowMeasure(staffLine.ParentStaff, staffLine);
+        return new VexFlowMeasure(staffLine.ParentStaff, undefined, staffLine);
     }
 
     /**

+ 10 - 10
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -36,7 +36,7 @@ import {AutoBeamOptions} from "../../../OpenSheetMusicDisplay/OSMDOptions";
 import {NoteType, Arpeggio} from "../../VoiceData";
 
 export class VexFlowMeasure extends GraphicalMeasure {
-    constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
+    constructor(staff: Staff, sourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) {
         super(staff, sourceMeasure, staffLine);
         this.minimumStaffEntriesWidth = -1;
         this.resetLayout();
@@ -53,9 +53,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
     /** The repetition instructions given as words or symbols (coda, dal segno..) */
     public vfRepetitionWords: Vex.Flow.Repetition[] = [];
     /** The VexFlow Stave (= one measure in a staffline) */
-    private stave: Vex.Flow.Stave;
+    protected stave: Vex.Flow.Stave;
     /** VexFlow StaveConnectors (vertical lines) */
-    private connectors: Vex.Flow.StaveConnector[] = [];
+    protected connectors: Vex.Flow.StaveConnector[] = [];
     /** Intermediate object to construct beams */
     private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]][]; } = {};
     /** Beams created by (optional) autoBeam function. */
@@ -65,9 +65,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
     /** VexFlow Beams */
     private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
     /** Intermediate object to construct tuplets */
-    private tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
+    protected tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
     /** VexFlow Tuplets */
-    private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
+    protected vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
 
     // Sets the absolute coordinates of the VFStave on the canvas
     public setAbsoluteCoordinates(x: number, y: number): void {
@@ -451,7 +451,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
      * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
      * @param voice the voice for which the ghost notes shall be searched.
      */
-    private getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
+    protected getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
         let latestVoiceTimestamp: Fraction = undefined;
         const gvEntries: GraphicalVoiceEntry[] = this.getGraphicalVoiceEntriesPerVoice(voice);
         for (let idx: number = 0, len: number = gvEntries.length; idx < len; ++idx) {
@@ -999,7 +999,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
     /**
      * Create the articulations for all notes of the current staff entry
      */
-    private createArticulations(): void {
+    protected createArticulations(): void {
         for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
             const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
 
@@ -1015,7 +1015,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
     /**
      * Create the ornaments for all notes of the current staff entry
      */
-    private createOrnaments(): void {
+    protected createOrnaments(): void {
         for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
             const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
             const gvoices: { [voiceID: number]: GraphicalVoiceEntry; } = graphicalStaffEntry.graphicalVoiceEntries;
@@ -1032,7 +1032,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         }
     }
 
-    private createFingerings(voiceEntry: GraphicalVoiceEntry): void {
+    protected createFingerings(voiceEntry: GraphicalVoiceEntry): void {
         const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
         const technicalInstructions: TechnicalInstruction[] = voiceEntry.parentVoiceEntry.TechnicalInstructions;
         const fingeringsCount: number = technicalInstructions.length;
@@ -1120,7 +1120,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
      * After re-running the formatting on the VexFlow Stave, update the
      * space needed by Instructions (in VexFlow: StaveModifiers)
      */
-    private updateInstructionWidth(): void {
+    protected updateInstructionWidth(): void {
         let vfBeginInstructionsWidth: number = 0;
         let vfEndInstructionsWidth: number = 0;
         const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();

+ 30 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -47,6 +47,7 @@ import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/Continu
 import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
 import { InstantaneousTempoExpression } from "../../VoiceData/Expressions";
 import { AlignRestOption } from "../../../OpenSheetMusicDisplay";
+import { VexFlowTabMeasure } from "./VexFlowTabMeasure";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
@@ -98,6 +99,16 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    * @returns the minimum required x width of the source measure (=list of staff measures)
    */
   protected calculateMeasureXLayout(measures: GraphicalMeasure[]): number {
+    // include tab measures:
+    const visibleMeasures: GraphicalMeasure[] = [];
+    for (const measure of measures) {
+      visibleMeasures.push(measure);
+      if (measure.tabMeasure) {
+        visibleMeasures.push(measure.tabMeasure);
+      }
+    }
+    measures = visibleMeasures;
+
     // Finalize beams
     /*for (let measure of measures) {
      (measure as VexFlowMeasure).finalizeBeams();
@@ -116,6 +127,22 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           allVoices.push(mvoices[voiceID]);
         }
       }
+      if (measure.tabMeasure !== undefined) {
+        const tabVoices: { [voiceID: number]: Vex.Flow.Voice; } = (measure.tabMeasure as VexFlowTabMeasure).vfVoices;
+        const tvoices: Vex.Flow.Voice[] = [];
+        for (const voiceID in tabVoices) {
+          if (tabVoices.hasOwnProperty(voiceID)) {
+            tvoices.push(tabVoices[voiceID]);
+            allVoices.push(tabVoices[voiceID]);
+          }
+        }
+        if (tvoices.length === 0) {
+          log.info("Found a tab measure with no voices. Continuing anyway.", tabVoices);
+          continue;
+        }
+        // all voices that belong to one stave are collectively added to create a common context in VexFlow.
+        formatter.joinVoices(tvoices);
+      }
       if (voices.length === 0) {
         log.info("Found a measure with no voices. Continuing anyway.", mvoices);
         continue;
@@ -367,6 +394,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
   protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
     (measure as VexFlowMeasure).graphicalMeasureCreatedCalculations();
+    if (measure.tabMeasure !== undefined) {
+      (measure.tabMeasure as VexFlowTabMeasure).graphicalMeasureCreatedCalculations();
+    }
   }
 
   /**

+ 112 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowTabMeasure.ts

@@ -0,0 +1,112 @@
+import Vex = require("vexflow");
+import { Staff } from "../../VoiceData/Staff";
+import { SourceMeasure } from "../../VoiceData/SourceMeasure";
+import { VexFlowMeasure } from "./VexFlowMeasure";
+import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
+import { VexFlowConverter } from "./VexFlowConverter";
+import { StaffLine } from "../StaffLine";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
+import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
+import { EngravingRules } from "../EngravingRules";
+import { Arpeggio } from "../../VoiceData/Arpeggio";
+import { Voice } from "../../VoiceData/Voice";
+import * as log from "loglevel";
+
+export class VexFlowTabMeasure extends VexFlowMeasure {
+    constructor(staff: Staff, sourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) {
+        super(staff, sourceMeasure, staffLine);
+    }
+
+    /**
+     * Reset all the geometric values and parameters of this measure and put it in an initialized state.
+     * This is needed to evaluate a measure a second time by system builder.
+     */
+    public resetLayout(): void {
+        // Take into account some space for the begin and end lines of the stave
+        // Will be changed when repetitions will be implemented
+        //this.beginInstructionsWidth = 20 / UnitInPixels;
+        //this.endInstructionsWidth = 20 / UnitInPixels;
+        this.stave = new Vex.Flow.TabStave(0, 0, 0, {
+            space_above_staff_ln: 0,
+            space_below_staff_ln: 0,
+        });
+        this.updateInstructionWidth();
+    }
+
+    public graphicalMeasureCreatedCalculations(): void {
+        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
+            const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
+
+            // create vex flow Notes:
+            for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
+                (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.CreateTabNote(gve);
+            }
+        }
+
+        this.finalizeTuplets();
+
+        const voices: Voice[] = this.getVoicesWithinMeasure();
+
+        for (const voice of voices) {
+            if (voice === undefined) {
+                continue;
+            }
+
+            // add a vexFlow voice for this voice:
+            this.vfVoices[voice.VoiceId] = new Vex.Flow.Voice({
+                        beat_value: this.parentSourceMeasure.Duration.Denominator,
+                        num_beats: this.parentSourceMeasure.Duration.Numerator,
+                        resolution: Vex.Flow.RESOLUTION,
+                    }).setMode(Vex.Flow.Voice.Mode.SOFT);
+
+            const restFilledEntries: GraphicalVoiceEntry[] =  this.getRestFilledVexFlowStaveNotesPerVoice(voice);
+            // create vex flow voices and add tickables to it:
+            for (const voiceEntry of restFilledEntries) {
+                if (voiceEntry.parentVoiceEntry) {
+                    if (voiceEntry.parentVoiceEntry.IsGrace && !voiceEntry.parentVoiceEntry.GraceAfterMainNote) {
+                        continue;
+                    }
+                }
+
+                const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
+                if (voiceEntry.notes.length === 0 || !voiceEntry.notes[0] || !voiceEntry.notes[0].sourceNote.PrintObject) {
+                    // GhostNote, don't add modifiers like in-measure clefs
+                    this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
+                    continue;
+                }
+
+                // add fingering
+                if (voiceEntry.parentVoiceEntry && EngravingRules.Rules.RenderFingerings) {
+                    this.createFingerings(voiceEntry);
+                }
+
+                // add Arpeggio
+                if (voiceEntry.parentVoiceEntry && voiceEntry.parentVoiceEntry.Arpeggio !== undefined) {
+                    const arpeggio: Arpeggio = voiceEntry.parentVoiceEntry.Arpeggio;
+                    // TODO right now our arpeggio object has all arpeggio notes from arpeggios across all voices.
+                    // see VoiceGenerator. Doesn't matter for Vexflow for now though
+                    if (voiceEntry.notes && voiceEntry.notes.length > 1) {
+                        const type: Vex.Flow.Stroke.Type = arpeggio.type;
+                        const stroke: Vex.Flow.Stroke = new Vex.Flow.Stroke(type, {
+                            all_voices: EngravingRules.Rules.ArpeggiosGoAcrossVoices
+                            // default: false. This causes arpeggios to always go across all voices, which is often unwanted.
+                            // also, this can cause infinite height of stroke, see #546
+                        });
+                        //if (arpeggio.notes.length === vexFlowVoiceEntry.notes.length) { // different workaround for endless y bug
+                        if (EngravingRules.Rules.RenderArpeggios) {
+                            vexFlowVoiceEntry.vfStaveNote.addStroke(0, stroke);
+                        }
+                    } else {
+                        log.debug(`[OSMD] arpeggio in measure ${this.MeasureNumber} could not be drawn.
+                        voice entry had less than two notes, arpeggio is likely between voice entries, not currently supported in Vexflow.`);
+                        // TODO: create new arpeggio with all the arpeggio's notes (arpeggio.notes), perhaps with GhostNotes in a new vfStaveNote. not easy.
+                    }
+                }
+
+                this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
+            }
+        }
+        //this.createArticulations();
+        //this.createOrnaments();
+    }
+}

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

@@ -24,6 +24,8 @@ export interface IGraphicalSymbolFactory {
 
     createGraphicalMeasure(sourceMeasure: SourceMeasure, staff: Staff): GraphicalMeasure;
 
+    createTabStaffMeasure(sourceMeasure: SourceMeasure, staff: Staff): GraphicalMeasure;
+
     createExtraGraphicalMeasure(staffLine: StaffLine): GraphicalMeasure;
 
     createStaffEntry(sourceStaffEntry: SourceStaffEntry, measure: GraphicalMeasure): GraphicalStaffEntry;

+ 5 - 0
src/MusicalScore/VoiceData/Instructions/ClefInstruction.ts

@@ -66,6 +66,7 @@ export class ClefInstruction extends AbstractNotationInstruction {
             case ClefEnum.F:
             case ClefEnum.C:
             case ClefEnum.percussion:
+            case ClefEnum.TAB:
                 return true;
             default:
                 return false;
@@ -148,6 +149,10 @@ export class ClefInstruction extends AbstractNotationInstruction {
                 this.clefPitch = new Pitch(NoteEnum.C, 2, AccidentalEnum.NONE);
                 this.referenceCyPosition = 2;
                 break;
+            case ClefEnum.TAB:
+                this.clefPitch = new Pitch(NoteEnum.G, 2, AccidentalEnum.NONE);
+                this.referenceCyPosition = -1;
+                break;
             default:
                 throw new ArgumentOutOfRangeException("clefType");
         }