Pārlūkot izejas kodu

feat(Measure Numbers): Display measure numbers (labels) as given in XML (#541)

polish of PR #879

renders the measure numbers as given in the XML, which can be completely arbitrary.
Doesn't affect SourceMeasure.MeasureNumber, which is now the logical nth measure printed in the sheet.

This will cause `SourceMeasure.getPrintedMeasureNumber()` to return what was given in XML,
while `SourceMeasure.MeasureNumber` is unchanged and always starting at 1.
It makes sense to have one variable for measure numbers starting at 1 (or 0) representing the logical measure number, that is, "this measure is the n-th printed on screen", for array indexes etc. So the first measure printed on screen still always has `MeasureNumber` 1, while `MeasureNumberXML` and `MeasureNumberPrinted` (set in `getPrintedMeasureNumber()`) can be different.
sschmid 4 gadi atpakaļ
vecāks
revīzija
6f5d77a9ef

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

@@ -234,8 +234,8 @@ export class EngravingRules {
     public RenderPartAbbreviations: boolean;
     public RenderFingerings: boolean;
     public RenderMeasureNumbers: boolean;
-    public ReadXMLMeasureNumbers: boolean;
     public RenderMeasureNumbersOnlyAtSystemStart: boolean;
+    public UseXMLMeasureNumbers: boolean;
     public RenderLyrics: boolean;
     public RenderMultipleRestMeasures: boolean;
     public AutoGenerateMutipleRestMeasuresFromRestMeasures: boolean;
@@ -506,6 +506,7 @@ export class EngravingRules {
         this.RenderFingerings = true;
         this.RenderMeasureNumbers = true;
         this.RenderMeasureNumbersOnlyAtSystemStart = false;
+        this.UseXMLMeasureNumbers = true;
         this.RenderLyrics = true;
         this.RenderMultipleRestMeasures = true;
         this.AutoGenerateMutipleRestMeasuresFromRestMeasures = true;

+ 13 - 8
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -415,7 +415,7 @@ export abstract class MusicSheetCalculator {
             log.warn("calculateMeasureNumberPlacement: measure undefined for system.Id " + musicSystem.Id);
             return; // TODO apparently happens in script sometimes (mp #70)
         }
-        let previousLabelMeasureNumber: number = staffLine.Measures[0].MeasureNumber;
+        let previousMeasureNumber: number = staffLine.Measures[0].MeasureNumber;
         let labelOffsetX: number = 0;
         for (let i: number = 0; i < staffLine.Measures.length; i++) {
             if (this.rules.RenderMeasureNumbersOnlyAtSystemStart && i > 0) {
@@ -423,7 +423,7 @@ export abstract class MusicSheetCalculator {
             }
             const measure: GraphicalMeasure = staffLine.Measures[i];
             if (measure.MeasureNumber === 0 || measure.MeasureNumber === 1) {
-                previousLabelMeasureNumber = measure.MeasureNumber;
+                previousMeasureNumber = measure.MeasureNumber;
                 // for the first measure, this label still needs to be created. Afterwards, this variable will hold the previous label's measure number.
             }
             if (measure !== staffLine.Measures[0] && this.rules.MeasureNumberLabelXOffset) {
@@ -432,14 +432,19 @@ export abstract class MusicSheetCalculator {
                 labelOffsetX = 0; // don't offset label for first measure in staffline
             }
 
-            if ((measure.MeasureNumber === previousLabelMeasureNumber ||
-                measure.MeasureNumber >= previousLabelMeasureNumber + this.rules.MeasureNumberLabelOffset) &&
-                !measure.parentSourceMeasure.ImplicitMeasure) {
+            const isFirstMeasureAndNotPrintedOne: boolean = this.rules.UseXMLMeasureNumbers &&
+                measure.MeasureNumber === 1 && measure.parentSourceMeasure.getPrintedMeasureNumber() !== 1;
+            if ((measure.MeasureNumber === previousMeasureNumber ||
+                measure.MeasureNumber >= previousMeasureNumber + this.rules.MeasureNumberLabelOffset) &&
+                !measure.parentSourceMeasure.ImplicitMeasure ||
+                isFirstMeasureAndNotPrintedOne) {
                 if (measure.MeasureNumber !== 1 ||
-                    (measure.MeasureNumber === 1 && measure !== staffLine.Measures[0])) {
+                    (measure.MeasureNumber === 1 && measure !== staffLine.Measures[0]) ||
+                    isFirstMeasureAndNotPrintedOne
+                    ) {
                     this.calculateSingleMeasureNumberPlacement(measure, staffLine, musicSystem, labelOffsetX);
                 }
-                previousLabelMeasureNumber = measure.MeasureNumber;
+                previousMeasureNumber = measure.MeasureNumber;
             }
         }
     }
@@ -452,7 +457,7 @@ export abstract class MusicSheetCalculator {
     /// <param name="musicSystem"></param>
     private calculateSingleMeasureNumberPlacement(measure: GraphicalMeasure, staffLine: StaffLine, musicSystem: MusicSystem,
                                                   labelOffsetX: number = 0): void {
-        const labelNumber: string = measure.MeasureNumber.toString();
+        const labelNumber: string = measure.parentSourceMeasure.getPrintedMeasureNumber().toString();
         const label: Label = new Label(labelNumber);
         // maybe give rules as argument instead of just setting fontStyle and maybe other settings manually afterwards
         const graphicalLabel: GraphicalLabel = new GraphicalLabel(label, this.rules.MeasureNumberLabelHeight,

+ 8 - 1
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -133,7 +133,14 @@ export class InstrumentReader {
     this.maxTieNoteFraction = new Fraction(0, 1);
     let lastNoteWasGrace: boolean = false;
     try {
-      const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[this.currentXmlMeasureIndex].elements();
+      const measureNode: IXmlElement = this.xmlMeasureList[this.currentXmlMeasureIndex];
+      const xmlMeasureListArr: IXmlElement[] = measureNode.elements();
+      if (currentMeasure.Rules.UseXMLMeasureNumbers && !Number.isInteger(currentMeasure.MeasureNumberXML)) {
+        const measureNumberXml: number = parseInt(measureNode.attribute("number")?.value, 10);
+        if (Number.isInteger(measureNumberXml)) {
+            currentMeasure.MeasureNumberXML = measureNumberXml;
+        }
+      }
       for (let xmlNodeIndex: number = 0; xmlNodeIndex < xmlMeasureListArr.length; xmlNodeIndex++) {
         const xmlNode: IXmlElement = xmlMeasureListArr[xmlNodeIndex];
         if (xmlNode.name === "print") {

+ 0 - 8
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -120,14 +120,6 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         this.musicSheet = new MusicSheet();
         this.musicSheet.Path = path;
         this.musicSheet.Rules = this.rules;
-        if (this.rules.ReadXMLMeasureNumbers) {
-            const measureNum: number = parseInt(root.element("part").element("measure").attribute("number").value, 10) - 1;
-            if (isNaN(measureNum)) {
-                throw new MusicSheetReadingException("Unable to read first measure number");
-            } else {
-                sourceMeasureCounter = measureNum;
-            }
-        }
         if (!root) {
             throw new MusicSheetReadingException("Undefined root element");
         }

+ 13 - 0
src/MusicalScore/VoiceData/SourceMeasure.ts

@@ -59,6 +59,8 @@ export class SourceMeasure {
     public printNewPageXml: boolean = false;
 
     private measureNumber: number;
+    public MeasureNumberXML: number;
+    public MeasureNumberPrinted: number; // measureNumber if MeasureNumberXML undefined or NaN. Set in getPrintedMeasureNumber()
     public multipleRestMeasures: number; // usually undefined (0), unless "multiple-rest" given in XML (e.g. 4 measure rest)
     // public multipleRestMeasuresPerStaff: Dictionary<number, number>; // key: staffId. value: how many rest measures
     private absoluteTimestamp: Fraction;
@@ -96,6 +98,17 @@ export class SourceMeasure {
         this.measureNumber = value;
     }
 
+    public getPrintedMeasureNumber(): number {
+        if (this.rules.UseXMLMeasureNumbers) {
+            if (Number.isInteger(this.MeasureNumberXML)) { // false for NaN, undefined, null, "5" (string)
+                this.MeasureNumberPrinted = this.MeasureNumberXML;
+                return this.MeasureNumberPrinted;
+            }
+        }
+        this.MeasureNumberPrinted = this.MeasureNumber;
+        return this.MeasureNumberPrinted;
+    }
+
     public get AbsoluteTimestamp(): Fraction {
         return this.absoluteTimestamp;
     }

+ 1 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -448,7 +448,7 @@ export class OpenSheetMusicDisplay {
             this.rules.MeasureNumberLabelOffset = options.measureNumberInterval;
         }
         if (options.useXMLMeasureNumbers !== undefined) {
-            this.rules.ReadXMLMeasureNumbers = options.useXMLMeasureNumbers;
+            this.rules.UseXMLMeasureNumbers = options.useXMLMeasureNumbers;
         }
         if (options.fingeringPosition !== undefined) {
             this.rules.FingeringPosition = AbstractExpression.PlacementEnumFromString(options.fingeringPosition);

+ 154 - 0
test/data/measure_numbers_xml_starting_at_3_with_multirest.musicxml

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>Title</work-title>
+    </work>
+  <identification>
+    <creator type="composer">Composer</creator>
+    <encoding>
+      <software>MuseScore 3.5.0</software>
+      <encoding-date>2020-09-04</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7.05556</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1683.36</page-height>
+      <page-width>1190.88</page-width>
+      <page-margins type="even">
+        <left-margin>56.6929</left-margin>
+        <right-margin>56.6929</right-margin>
+        <top-margin>56.6929</top-margin>
+        <bottom-margin>113.386</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>56.6929</left-margin>
+        <right-margin>56.6929</right-margin>
+        <top-margin>56.6929</top-margin>
+        <bottom-margin>113.386</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="FreeSerif" font-size="10"/>
+    <lyric-font font-family="FreeSerif" font-size="11"/>
+    </defaults>
+  <credit page="1">
+    <credit-words default-x="595.44" default-y="1626.67" justify="center" valign="top" font-size="24">Title</credit-words>
+    </credit>
+  <credit page="1">
+    <credit-words default-x="1134.19" default-y="1526.67" justify="right" valign="bottom" font-size="12">Composer</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="3" width="208.89">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>-0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="79.27" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="4" width="143.94">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="5" width="144.90">
+      <note default-x="10.00" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="6" width="143.94">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="7" width="143.94">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="8" width="143.94">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="9" width="147.96">
+      <note default-x="13.32" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>