Repository: opensheetmusicdisplay/opensheetmusicdisplay (ts)

Issue:
Horizontal alignment issues with transposing parts
Here is another issue that should be fixed, and I am wondering if there is an easy fix for it.

When a score includes transposing parts (which results in parts having different key signatures), the music is not correctly aligned horizontally.

Have a look at the screenshot below, I have circled the bar where the problem is mostly shown, the notes of the first and second part are not aligned because of the different key signature:

![clarShot](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/10005316/ddb72bac-621e-433c-b16b-3c45c53cb23d)

Similarly, look at this shot of a score with multiple instruments; again, I have circled in red where the problem is mostly shown, the "Violin piccolo in Eb" part is even going out of the bar line on the right:

![brandShot](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/10005316/d1f207ba-c3c8-4691-92c7-e8b5390e91f0)

I have taken those shots from the demo page:

https://opensheetmusicdisplay.github.io/demo/

And you can try yourself with the same files from the link below:

[OSMD_transp_examples.zip](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/files/12043371/OSMD_transp_examples.zip)

All parts should be horizontally aligned from the time signature by starting from the most-far-right time signature (in the case of the last score above, aligned with the "Violin piccolo in Eb" part).

I am eager to know if there is a way to fix that or if you plan to fix that. I have spent hours trying to figure out and fix the code myself, but it looks like it'd be almost impossible for me to do it. I guess who wrote the code should be able to do it faster or point me in the right direction?

Thank you!

All the best,





Pull request description:
Fix note x-alignment for staves with different key signatures
Fixes #1425. #1315

before:
<img width="219" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/c4dac616-e3e9-490e-a669-fcdc78b5146d">

after:
<img width="249" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/8dfd6e84-9e64-4af9-885a-f9a2c4df5c21">

new sample: `test_noteStartX_key_signatures.musicxml`

<details><summary>before:</summary>

<img width="781" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/6703d7aa-5899-41ff-bc42-919548ca76bf">

</details>

after:
<img width="759" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/5e5006dc-c002-4b9b-883f-fe3b5e157c73">

`Mozart_Clarinet_Quintet_Excerpt.mxl`

<details><summary>before:</summary>

<img width="765" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/6ae61fc8-af2d-4d9a-ac5b-54ee9586a42c">

</details>

after:
<img width="758" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/2c12ab3c-fa3c-4403-8f7c-ff63dd6c9e74">

(sample from #1425)
The Brandenburg concerto, a very big score, is now also correctly aligned, very pleased!

<details><summary>before:</summary>

<img width="767" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/b011758d-c5ec-4d7e-8898-2a104f6e3c7d">

</details>

after:

<img width="757" alt="image" src="https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/assets/33069673/c6bb9f53-9c31-40b4-96d2-147c9adb11a7">

edit: This fix is an extended version of the one Vexflow did in their repository:
https://github.com/0xfe/vexflow/pull/1390

In our case, this fix alone was not enough, because of how we use `formatToStave()`, which overwrote the new `x` and `start_x` values after they had been set correctly. (which was quite tricky to debug)

Patch:
diff --git a/src/MusicalScore/Graphical/MusicSystemBuilder.ts b/src/MusicalScore/Graphical/MusicSystemBuilder.ts
index 1a028631..fda90cbc 100644
--- a/src/MusicalScore/Graphical/MusicSystemBuilder.ts
+++ b/src/MusicalScore/Graphical/MusicSystemBuilder.ts
@@ -449,8 +449,12 @@ export class MusicSystemBuilder {
         }
         let totalBeginInstructionLengthX: number = 0.0;
         const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
+        const staves: any[] = []; // VF.Stave. generally don't want to reference Vexflow classes here.
         for (let idx: number = 0; idx < measureCount; ++idx) {
             const measure: GraphicalMeasure = measures[idx];
+            if (measure) { // MultiRestMeasure may be undefined
+                staves.push((measure as any).getVFStave()); // as VexFlowMeasure
+            }
             const staffIndex: number = this.visibleStaffIndices[idx];
             const beginInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[staffIndex];
             const beginInstructionLengthX: number = this.AddInstructionsAtMeasureBegin(
@@ -460,6 +464,7 @@ export class MusicSystemBuilder {
             );
             totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
         }
+        staves[0].formatBegModifiers(staves);
         return totalBeginInstructionLengthX;
     }
 
diff --git a/src/VexFlowPatch/src/stave.js b/src/VexFlowPatch/src/stave.js
index e6f46488..4bff0037 100644
--- a/src/VexFlowPatch/src/stave.js
+++ b/src/VexFlowPatch/src/stave.js
@@ -72,14 +72,72 @@ export class Stave extends Element {
     this.options.bottom_text_position = this.options.num_lines;
   }
 
+  /** align modifiers like time signature for vertical staves with different key signatures / time signature xs
+   * This method should be static, but that makes using it with `any` usage more difficult.
+   */
+  formatBegModifiers(staves) {
+    let maxX = 0;
+    // align note start
+    staves.forEach((stave) => {
+      if (stave.getNoteStartX() > maxX) maxX = stave.getNoteStartX();
+    });
+    staves.forEach((stave) => {
+      stave.setNoteStartX(maxX);
+    });
+
+    maxX = 0;
+    // align REPEAT_BEGIN
+    staves.forEach((stave) => {
+      const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, Barline.CATEGORY);
+      modifiers.forEach((modifier) => {
+        if (modifier.getType() == Barline.type.REPEAT_BEGIN)
+          if (modifier.getX() > maxX) maxX = modifier.getX();
+      });
+    });
+    staves.forEach((stave) => {
+      const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, Barline.CATEGORY);
+      modifiers.forEach((modifier) => {
+        if (modifier.getType() == Barline.type.REPEAT_BEGIN) modifier.setX(maxX);
+      });
+    });
+
+    maxX = 0;
+    // Align time signatures
+    staves.forEach((stave) => {
+      const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, TimeSignature.CATEGORY);
+      modifiers.forEach((modifier) => {
+        if (modifier.getX() > maxX) maxX = modifier.getX();
+      });
+    });
+    staves.forEach((stave) => {
+      const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, TimeSignature.CATEGORY);
+      modifiers.forEach((modifier) => {
+        modifier.setX(maxX);
+      });
+    });
+    // Align key signatures
+    // staves.forEach((stave) => {
+    //   const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, KeySignature.CATEGORY);
+    //   modifiers.forEach((modifier) => {
+    //     if (modifier.getX() > maxX) maxX = modifier.getX();
+    //   });
+    // });
+    // staves.forEach((stave) => {
+    //   const modifiers = stave.getModifiers(StaveModifier.Position.BEGIN, KeySignature.CATEGORY);
+    //   modifiers.forEach((modifier) => {
+    //     modifier.setX(maxX);
+    //   });
+    // });
+  }
+
   getOptions() { return this.options; }
 
   setNoteStartX(x) {
     if (!this.formatted) this.format();
 
     this.setStartX(x);
-    const begBarline = this.modifiers[0];
-    begBarline.setX(this.start_x - begBarline.getWidth());
+    // const begBarline = this.modifiers[0];
+    // begBarline.setX(this.start_x - begBarline.getWidth());
     return this;
   }
   setStartX(x) {
@@ -493,14 +551,23 @@ export class Stave extends Element {
       }
 
       x += padding;
-      modifier.setX(x);
+      const modifierX = modifier.getX();
+      if (modifierX > x) { // e.g. when beginning modifiers already x-aligned, different key signatures
+        x = modifierX;
+        // VexFlowPatch: don't overwrite modifier.x when it was already shifted further, e.g. to align time signatures.
+        //   see formatBegModifiers().
+      } else {
+        modifier.setX(x);
+      }
       x += width;
 
       if (padding + width === 0) offset--;
     }
 
-    this.setStartX(x);
-    // this.start_x = x;
+    if (x > this.start_x) {
+      this.setStartX(x);
+      // VexFlowPatch: don't overwrite start_x if it's already bigger (alignment, see formatBegModifiers())
+    }
     x = this.x + this.width;
 
     const widths = {


Test patch:
diff --git a/test/data/test_noteStartX_key_signatures.musicxml b/test/data/test_noteStartX_key_signatures.musicxml
new file mode 100644
index 00000000..c673406a
--- /dev/null
+++ b/test/data/test_noteStartX_key_signatures.musicxml
@@ -0,0 +1,157 @@
+<?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>test_noteStartX_key_signatures</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-07-13</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>6.99911</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1696.94</page-height>
+      <page-width>1200.48</page-width>
+      <page-margins type="even">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600.242" default-y="1611.21" justify="center" valign="top" font-size="22">test_noteStartX_key_signatures</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>B♭ Clarinet</part-name>
+      <part-abbreviation>B♭ Cl.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>B♭ Clarinet</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>72</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P2">
+      <part-name>Violin</part-name>
+      <part-abbreviation>Vln.</part-abbreviation>
+      <score-instrument id="P2-I1">
+        <instrument-name>Violin</instrument-name>
+        </score-instrument>
+      <midi-device id="P2-I1" port="1"></midi-device>
+      <midi-instrument id="P2-I1">
+        <midi-channel>2</midi-channel>
+        <midi-program>41</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="419.15">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>114.98</left-margin>
+            <right-margin>494.57</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>2</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        <transpose>
+          <diatonic>-1</diatonic>
+          <chromatic>-2</chromatic>
+          </transpose>
+        </attributes>
+      <note default-x="110.64" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="419.15">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>96.19</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>-2</fifths>
+          </key>
+        <time>
+          <beats>2</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="110.64" default-y="-156.19">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>