Explorar o código

Merge branch 'develop' into release/0.6.5

sschmidTU %!s(int64=6) %!d(string=hai) anos
pai
achega
86fbe15e7d

+ 1 - 0
demo/index.js

@@ -153,6 +153,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             fingeringPosition: "auto", // left is default. try right. experimental: auto, above, below.
             // fingeringInsideStafflines: "true", // default: false. true draws fingerings directly above/below notes
             setWantedStemDirectionByXml: true, // try false, which was previously the default behavior
+            // drawUpToMeasureNumber: 3, // draws only up to measure 3, meaning it draws measure 1 to 3 of the piece.
 
             // coloring options
             coloringEnabled: true,

+ 8 - 0
external/vexflow/vexflow.d.ts

@@ -212,6 +212,14 @@ declare namespace Vex {
             public addTimeSignature(sig: string): void;
 
             public setVoltaType(type: number, number_t: number, y: number): void;
+
+            public setTempo(tempo: Object, y: number): Stave;
+
+            public setShiftX(x: number): Stave;
+        }
+
+        export class StaveTempo extends StaveModifier { // needs Vexflow PR to be exported/usable
+            constructor(tempo: Object, x: number, shift_y: number);
         }
 
         export class Volta extends StaveModifier {

+ 1 - 0
src/Common/index.ts

@@ -1,3 +1,4 @@
 export * from "./DataObjects/Fraction";
 export * from "./DataObjects/Pitch";
 export * from "./Enums";
+export * from "./FileIO";

+ 1 - 1
src/MusicalScore/Graphical/BoundingBox.ts

@@ -38,7 +38,7 @@ export class BoundingBox {
      * Create a bounding box
      * @param dataObject Graphical object where the bounding box will be attached
      * @param parent Parent bounding box of an object in a higher hierarchy position
-     * @param isSymbol Defines the bounding box to be symbol thus not calculating it's boundaries by itself. NOTE: Borders need to be set!
+     * @param isSymbol Defines the bounding box to be symbol thus not calculating its boundaries by itself. NOTE: Borders need to be set!
      */
     constructor(dataObject: Object = undefined, parent: BoundingBox = undefined, isSymbol: boolean = false) {
         this.parent = parent;

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

@@ -168,6 +168,8 @@ export class EngravingRules {
     private subMeasureXSpacingThreshold: number;
     private measureDynamicsMaxScalingFactor: number;
     private wholeRestXShiftVexflow: number;
+    private metronomeMarkXShift: number;
+    private metronomeMarkYShift: number;
     private maxInstructionsConstValue: number;
     private noteDistances: number[] = [1.0, 1.0, 1.3, 1.6, 2.0, 2.5, 3.0, 4.0];
     private noteDistancesScalingFactors: number[] = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0];
@@ -182,6 +184,7 @@ export class EngravingRules {
     private defaultColorStem: string;
     private defaultColorLabel: string;
     private defaultColorTitle: string;
+    private maxMeasureToDrawIndex: number;
     /** Whether to render a label for the composer of the piece at the top of the sheet. */
     private renderComposer: boolean;
     private renderTitle: boolean;
@@ -383,6 +386,8 @@ export class EngravingRules {
         this.subMeasureXSpacingThreshold = 35;
         this.measureDynamicsMaxScalingFactor = 2.5;
         this.wholeRestXShiftVexflow = -2.5; // VexFlow draws rest notes too far to the right
+        this.metronomeMarkXShift = -6; // our unit, is taken * unitInPixels
+        this.metronomeMarkYShift = -0.5;
 
         // Render options (whether to render specific or invisible elements)
         this.coloringEnabled = true;
@@ -393,6 +398,7 @@ export class EngravingRules {
         this.defaultColorStem = undefined;
         this.defaultColorLabel = undefined;
         this.defaultColorTitle = undefined;
+        this.maxMeasureToDrawIndex = Number.MAX_VALUE;
         this.renderComposer = true;
         this.renderTitle = true;
         this.renderSubtitle = true;
@@ -1288,6 +1294,18 @@ export class EngravingRules {
     public set WholeRestXShiftVexflow(value: number) {
         this.wholeRestXShiftVexflow = value;
     }
+    public get MetronomeMarkXShift(): number {
+        return this.metronomeMarkXShift;
+    }
+    public set MetronomeMarkXShift(value: number) {
+        this.metronomeMarkXShift = value;
+    }
+    public get MetronomeMarkYShift(): number {
+        return this.metronomeMarkYShift;
+    }
+    public set MetronomeMarkYShift(value: number) {
+        this.metronomeMarkYShift = value;
+    }
     public get MaxInstructionsConstValue(): number {
         return this.maxInstructionsConstValue;
     }
@@ -1360,6 +1378,12 @@ export class EngravingRules {
     public set DefaultColorTitle(value: string) {
         this.defaultColorTitle = value;
     }
+    public get MaxMeasureToDrawIndex(): number {
+        return this.maxMeasureToDrawIndex;
+    }
+    public set MaxMeasureToDrawIndex(value: number) {
+        this.maxMeasureToDrawIndex = value;
+    }
     public get RenderComposer(): boolean {
         return this.renderComposer;
     }

+ 38 - 15
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -671,7 +671,7 @@ export abstract class MusicSheetCalculator {
 
         // visible 2D-MeasureList
         const visibleMeasureList: GraphicalMeasure[][] = [];
-        for (let idx: number = 0, len: number = allMeasures.length; idx < len; ++idx) {
+        for (let idx: number = 0, len: number = allMeasures.length; idx < len && idx < EngravingRules.Rules.MaxMeasureToDrawIndex; ++idx) {
             const graphicalMeasures: GraphicalMeasure[] = allMeasures[idx];
             const visiblegraphicalMeasures: GraphicalMeasure[] = [];
             for (let idx2: number = 0, len2: number = graphicalMeasures.length; idx2 < len2; ++idx2) {
@@ -847,7 +847,25 @@ export abstract class MusicSheetCalculator {
     }
 
     protected calculateChordSymbols(): void {
-        return;
+        for (const musicPage of this.graphicalMusicSheet.MusicPages) {
+            for (const musicSystem of musicPage.MusicSystems) {
+                for (const staffLine of musicSystem.StaffLines) {
+                    const sbc: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
+                    for (const measure of staffLine.Measures) {
+                        for (const staffEntry of measure.staffEntries) {
+                            if (!staffEntry.graphicalChordContainer) {
+                                continue;
+                            }
+                            const sps: BoundingBox = staffEntry.PositionAndShape;
+                            const gps: BoundingBox = staffEntry.graphicalChordContainer.PositionAndShape;
+                            const start: number = gps.BorderMarginLeft + sps.AbsolutePosition.x;
+                            const end: number = gps.BorderMarginRight + sps.AbsolutePosition.x;
+                            sbc.updateSkyLineInRange(start, end, sps.BorderMarginTop);
+                        }
+                    }
+                }
+            }
+        }
     }
 
     /**
@@ -1401,29 +1419,26 @@ export abstract class MusicSheetCalculator {
                                                                        textAlignment);
 
                 if (entry.Expression instanceof InstantaneousTempoExpression) {
-                    let alreadyAdded: boolean = false;
+                    //already added?
                     for (const expr of staffLine.AbstractExpressions) {
                         if (expr instanceof GraphicalInstantaneousTempoExpression &&
                             (expr.SourceExpression as AbstractTempoExpression).Label === entry.Expression.Label) {
-                            alreadyAdded = true;
+                            //already added
+                            continue;
                         }
                     }
 
-                    if (alreadyAdded) {
-                        continue;
-                    }
-
                     const graphicalTempoExpr: GraphicalInstantaneousTempoExpression = new GraphicalInstantaneousTempoExpression(entry.Expression, graphLabel);
                     if (graphicalTempoExpr.ParentStaffLine === undefined) {
                         log.warn("Adding staffline didn't work");
-                        // I am actually fooling the linter her and use the created object. This method needs refactoring,
-                        // all graphical expression creations should be in one place and ahve basic stuff like labels, lines, ...
+                        // I am actually fooling the linter here and use the created object. This method needs refactoring,
+                        // all graphical expression creations should be in one place and have basic stuff like labels, lines, ...
                         // in their constructor
                     }
                     // in case of metronome mark:
                     if ((entry.Expression as InstantaneousTempoExpression).Enum === TempoEnum.metronomeMark) {
-                        // use smaller font:
-                        graphLabel.Label.fontHeight = 1.2;
+                        this.createMetronomeMark((entry.Expression as InstantaneousTempoExpression));
+                        continue;
                     }
                 } else if (entry.Expression instanceof ContinuousTempoExpression) {
                     // FIXME: Not yet implemented
@@ -1445,6 +1460,10 @@ export abstract class MusicSheetCalculator {
         }
     }
 
+    protected createMetronomeMark(metronomeExpression: InstantaneousTempoExpression): void {
+        throw new Error("abstract, not implemented");
+    }
+
     protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
         return;
     }
@@ -2380,7 +2399,9 @@ export abstract class MusicSheetCalculator {
             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] &&
+            if (nextStaffLine &&
+                endStaffentry.parentMeasure.ParentStaffLine &&
+                !(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 +
@@ -2568,7 +2589,8 @@ export abstract class MusicSheetCalculator {
     }
 
     private calculateDynamicExpressions(): void {
-        for (let i: number = 0; i < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; i++) {
+        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length, EngravingRules.Rules.MaxMeasureToDrawIndex);
+        for (let i: number = 0; i < maxIndex; i++) {
             const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
             for (let j: number = 0; j < sourceMeasure.StaffLinkedExpressions.length; j++) {
                 if (this.graphicalMusicSheet.MeasureList[i][j].ParentStaff.ParentInstrument.Visible) {
@@ -2648,7 +2670,8 @@ export abstract class MusicSheetCalculator {
     }
 
     private calculateTempoExpressions(): void {
-        for (let i: number = 0; i < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; i++) {
+        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length, EngravingRules.Rules.MaxMeasureToDrawIndex);
+        for (let i: number = 0; i < maxIndex; i++) {
             const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
             for (let j: number = 0; j < sourceMeasure.TempoExpressions.length; j++) {
                 this.calculateTempoExpressionsForMultiTempoExpression(sourceMeasure, sourceMeasure.TempoExpressions[j], i);

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

@@ -734,7 +734,8 @@ export class MusicSystemBuilder {
      */
     private nextMeasureBeginsLineRepetition(): boolean {
         const nextMeasureIndex: number = this.measureListIndex + 1;
-        if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length) {
+        if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length
+            || !this.measureList[nextMeasureIndex]) {
             return false;
         }
         for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {

+ 1 - 1
src/MusicalScore/Graphical/SkyBottomLineCalculator.ts

@@ -228,7 +228,7 @@ export class SkyBottomLineCalculator {
 
     /**
      * This method updates the SkyLine for a given range with a given value
-     * @param  to update the SkyLine for
+     * //param  to update the SkyLine for
      * @param start Start index of the range
      * @param end End index of the range
      * @param value ??

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

@@ -45,6 +45,7 @@ import { GraphicalSlur } from "../GraphicalSlur";
 import { BoundingBox } from "../BoundingBox";
 import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
+import { InstantaneousTempoExpression } from "../../VoiceData/Expressions";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
@@ -470,6 +471,29 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     }
   }
 
+  protected createMetronomeMark(metronomeExpression: InstantaneousTempoExpression): void {
+    const vfStave: Vex.Flow.Stave = (this.graphicalMusicSheet.MeasureList[0][0] as VexFlowMeasure).getVFStave();
+    //vfStave.addModifier(new Vex.Flow.StaveTempo( // needs Vexflow PR
+    vfStave.setTempo(
+      {
+          bpm: metronomeExpression.TempoInBpm,
+          dots: metronomeExpression.dotted,
+          //duration: metronomeExpression.beatUnit
+          duration: "q"
+      },
+      EngravingRules.Rules.MetronomeMarkYShift * unitInPixels);
+       // -50, -30), 0); //needs Vexflow PR
+       //.setShiftX(-50);
+
+    (<any>vfStave.getModifiers()[vfStave.getModifiers().length - 1]).setShiftX(
+      EngravingRules.Rules.MetronomeMarkXShift * unitInPixels
+    );
+    // TODO calculate bounding box of metronome mark instead of hacking skyline to fix lyricist collision
+    const skyline: number[] = this.graphicalMusicSheet.MeasureList[0][0].ParentStaffLine.SkyLine;
+    skyline[0] = Math.min(skyline[0], -4.5 + EngravingRules.Rules.MetronomeMarkYShift);
+    // somehow this is called repeatedly in Clementi, so skyline[0] = Math.min instead of -=
+  }
+
   /**
    * Calculate a single OctaveShift for a [[MultiExpression]].
    * @param sourceMeasure

+ 6 - 7
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -157,28 +157,27 @@ export class ExpressionReader {
         let dirContentNode: IXmlElement = dirNode.element("metronome");
         if (dirContentNode !== undefined) {
             const beatUnit: IXmlElement = dirContentNode.element("beat-unit");
-            const hasDot: boolean = dirContentNode.element("beat-unit-dot") !== undefined;
+            const dotted: boolean = dirContentNode.element("beat-unit-dot") !== undefined;
             const bpm: IXmlElement = dirContentNode.element("per-minute");
+            // TODO check print-object = false -> don't render invisible metronome mark
             if (beatUnit !== undefined && bpm !== undefined) {
                 const useCurrentFractionForPositioning: boolean = (dirContentNode.hasAttributes && dirContentNode.attribute("default-x") !== undefined);
                 if (useCurrentFractionForPositioning) {
                     this.directionTimestamp = Fraction.createFromFraction(inSourceMeasureCurrentFraction);
                 }
-                let text: string = beatUnit.value + " = " + bpm.value;
-                if (hasDot) {
-                    text = "dotted " + text;
-                }
                 const bpmNumber: number = parseInt(bpm.value, 10);
                 this.createNewTempoExpressionIfNeeded(currentMeasure);
                 const instantaneousTempoExpression: InstantaneousTempoExpression =
-                    new InstantaneousTempoExpression(text,
+                    new InstantaneousTempoExpression(undefined,
                                                      this.placement,
                                                      this.staffNumber,
                                                      bpmNumber,
                                                      this.currentMultiTempoExpression,
                                                      true);
+                instantaneousTempoExpression.dotted = dotted;
+                instantaneousTempoExpression.beatUnit = beatUnit.value;
                 this.currentMultiTempoExpression.addExpression(instantaneousTempoExpression, "");
-                this.currentMultiTempoExpression.CombinedExpressionsText = text;
+                this.currentMultiTempoExpression.CombinedExpressionsText = "test";
             }
             return;
         }

+ 1 - 0
src/MusicalScore/ScoreIO/index.ts

@@ -4,3 +4,4 @@ export * from "./InstrumentReader";
 export * from "./MusicSheetReader";
 export * from "./MusicSymbolModuleFactory";
 export * from "./VoiceGenerator";
+export * from "./MusicSymbolModules";

+ 11 - 0
src/MusicalScore/VoiceData/Expressions/InstantaneousTempoExpression.ts

@@ -7,9 +7,15 @@ import {MultiTempoExpression} from "./MultiTempoExpression";
 export class InstantaneousTempoExpression extends AbstractTempoExpression {
     constructor(label: string, placement: PlacementEnum, staffNumber: number,
                 soundTempo: number, parentMultiTempoExpression: MultiTempoExpression, isMetronomeMark: boolean = false) {
+        /*if (isMetronomeMark) {
+            label = " = " + soundTempo;
+        }*/
         super(label, placement, staffNumber, parentMultiTempoExpression);
         this.setTempoAndTempoType(soundTempo);
     }
+
+    public dotted: boolean;
+    public beatUnit: string;
     private static listInstantaneousTempoLarghissimo: string[] = ["Larghissimo", "Sehr breit", "very, very slow"]; // }), TempoEnum.larghissimo);
     private static listInstantaneousTempoGrave: string[] = ["Grave", "Schwer", "slow and solemn"]; //  }), TempoEnum.grave);
     private static listInstantaneousTempoLento: string[] = ["Lento", "Lent", "Langsam", "slowly"]; //  }), TempoEnum.lento);
@@ -261,6 +267,11 @@ export class InstantaneousTempoExpression extends AbstractTempoExpression {
         return Fraction.plus(this.ParentMultiTempoExpression.SourceMeasureParent.AbsoluteTimestamp, this.ParentMultiTempoExpression.Timestamp).RealValue;
     }
     private setTempoAndTempoType(soundTempo: number): void {
+        if (!this.label) {
+            this.tempoInBpm = soundTempo;
+            this.tempoEnum = TempoEnum.metronomeMark;
+            return;
+        }
         if (InstantaneousTempoExpression.isStringInStringList(InstantaneousTempoExpression.listInstantaneousTempoLarghissimo, this.label)) {
             if (soundTempo === 0) {
                 soundTempo = InstantaneousTempoExpression.getDefaultValueForTempoType(TempoEnum.larghissimo);

+ 2 - 0
src/MusicalScore/index.ts

@@ -7,3 +7,5 @@ export * from "./Label";
 export * from "./MusicSheet";
 export * from "./SubInstrument";
 export * from "./VoiceData";
+export * from "./MusicSource";
+export * from "./ScoreIO";

+ 3 - 2
src/OpenSheetMusicDisplay/Cursor.ts

@@ -58,13 +58,14 @@ export class Cursor {
     }
     this.graphic.Cursors.length = 0;
     const iterator: MusicPartManagerIterator = this.iterator;
-    if (iterator.EndReached || iterator.CurrentVoiceEntries === undefined || iterator.CurrentVoiceEntries.length === 0) {
+    const voiceEntries: VoiceEntry[] = iterator.CurrentVisibleVoiceEntries();
+    if (iterator.EndReached || iterator.CurrentVoiceEntries === undefined || voiceEntries.length === 0) {
       return;
     }
     let x: number = 0, y: number = 0, height: number = 0;
 
     // get all staff entries inside the current voice entry
-    const gseArr: VexFlowStaffEntry[] = iterator.CurrentVoiceEntries.map(ve => this.getStaffEntriesFromVoiceEntry(ve));
+    const gseArr: VexFlowStaffEntry[] = voiceEntries.map(ve => this.getStaffEntriesFromVoiceEntry(ve));
     // sort them by x position and take the leftmost entry
     const gse: VexFlowStaffEntry =
           gseArr.sort((a, b) => a.PositionAndShape.AbsolutePosition.x <= b.PositionAndShape.AbsolutePosition.x ? -1 : 1 )[0];

+ 2 - 0
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -44,6 +44,8 @@ export interface IOSMDOptions {
     fingeringPosition?: string;
     /** For above/below fingerings, whether to draw them directly above/below notes (default), or above/below staffline. */
     fingeringInsideStafflines?: boolean;
+    /** Only draw measure 1 to n, where n is the number you specify. */
+    drawUpToMeasureNumber?: number;
     /** Whether to set the wanted stem direction by xml (default) or automatically. */
     setWantedStemDirectionByXml?: boolean;
     /** Whether tuplets are labeled with ratio (e.g. 5:2 instead of 5 for quintuplets). Default false. */

+ 3 - 0
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -294,6 +294,9 @@ export class OpenSheetMusicDisplay {
         if (options.defaultColorTitle) {
             EngravingRules.Rules.DefaultColorTitle = options.defaultColorTitle;
         }
+        if (options.drawUpToMeasureNumber) {
+            EngravingRules.Rules.MaxMeasureToDrawIndex = options.drawUpToMeasureNumber;
+        }
         if (options.tupletsRatioed) {
             EngravingRules.Rules.TupletsRatioed = true;
         }

+ 23 - 0
test/Common/OSMD/OSMD_Test.ts

@@ -192,4 +192,27 @@ describe("OpenSheetMusicDisplay Main Export", () => {
             done
         ).catch(done);
     });
+
+    describe("cursor with hidden instrument", () => {
+        let osmd: OpenSheetMusicDisplay;
+        beforeEach(() => {
+            const div: HTMLElement = TestUtils.getDivElement(document);
+            osmd = TestUtils.createOpenSheetMusicDisplay(div);
+            const score: Document =
+                TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
+            return osmd.load(score)
+            .then(() => {
+                osmd.render();
+            });
+        });
+
+        it("should move cursor after instrument is hidden", () => {
+            osmd.Sheet.Instruments[1].Visible = false;
+            osmd.render();
+            osmd.cursor.show();
+            for (let i: number = 0; i < 100; i++) {
+                osmd.cursor.next();
+            }
+        });
+    });
 });