Browse Source

merge osmd-public: render more tempo expressions, tab error fix, duplicate metronome mark fix

sschmidTU 1 year ago
parent
commit
faacf7cb65

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

@@ -540,6 +540,9 @@ export abstract class MusicSheetCalculator {
                 for (const groupBracket of musicSystem.GroupBrackets) {
                     minBracketTopBorder = Math.min(minBracketTopBorder, groupBracket.PositionAndShape.BorderTop);
                 }
+            } else if (measure.ParentStaff.ParentInstrument.Parent) { // Parent InstrumentalGroup
+                // note that GroupBracket creation is currently done after measure number creation, so we have to check it indirectly.
+                minBracketTopBorder = -1;
             }
             relativeY = Math.min(skyLineMinValue, minBracketTopBorder);
         } else {
@@ -1855,20 +1858,16 @@ export abstract class MusicSheetCalculator {
                         }
                     }
                 } else if (entry.Expression instanceof ContinuousTempoExpression) {
-                    // FIXME: Not yet implemented
-                    // let alreadyAdded: boolean = false;
-                    // for (const expr of staffLine.AbstractExpressions) {
-                    //     if (expr instanceof GraphicalContinuousTempoExpression &&
-                    //         expr.GetContinuousTempoExpression.Label === entry.Expression.Label) {
-                    //         alreadyAdded = true;
-                    //     }
-                    // }
-
-                    // if (alreadyAdded) {
-                    //     continue;
-                    // }
-
-                    // staffLine.AbstractExpressions.push(new GraphicalContinuousTempoExpression((ContinuousTempoExpression)(entry.Expression), graphLabel));
+                    for (const expr of staffLine.AbstractExpressions) {
+                        if (expr instanceof GraphicalInstantaneousTempoExpression &&
+                        (expr.SourceExpression as AbstractTempoExpression).Label === entry.Expression.Label) {
+                            continue; // already added
+                        }
+                    }
+                    // TODO maybe create GraphicalContinuousTempoExpression class,
+                    //   though the ContinuousTempoExpressions we have currently behave the same graphically (accelerando, ritardando, etc).
+                    //   The behavior difference rather affects playback (e.g. ritardando, which gradually changes tempo)
+                    staffLine.AbstractExpressions.push(new GraphicalInstantaneousTempoExpression(entry.Expression, graphLabel));
                 }
             }
         }

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

@@ -464,7 +464,7 @@ export class MusicSystemBuilder {
             );
             totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
         }
-        staves[0].formatBegModifiers(staves);
+        staves[0].formatBegModifiers(staves); // x-align notes / beginning modifiers like time signatures, e.g. for transposing instruments
         return totalBeginInstructionLengthX;
     }
 

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

@@ -864,7 +864,12 @@ export class VexFlowConverter {
         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.StringNumberTab, fret: tabNote.FretNumber};
+            let tabPosition: {str: number, fret: number} = {str: tabNote.StringNumberTab, fret: tabNote.FretNumber};
+            if (!(note.sourceNote instanceof TabNote)) {
+                log.info(`invalid tab note: ${note.sourceNote.Pitch.ToString()} in measure ${gve.parentStaffEntry.parentMeasure.MeasureNumber}` +
+                    ", likely missing XML string+fret number.");
+                tabPosition = {str: 1, fret: 0}; // random safe values, otherwise it's both undefined for invalid notes
+            }
             tabPositions.push(tabPosition);
             if (tabNote.BendArray) {
                 tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {

+ 2 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -72,6 +72,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
     public vfTies: VF.StaveTie[] = [];
     /** The repetition instructions given as words or symbols (coda, dal segno..) */
     public vfRepetitionWords: VF.Repetition[] = [];
+    public hasMetronomeMark: boolean = false;
     /** The VexFlow Stave (= one measure in a staffline) */
     protected stave!: VF.Stave;
     /** VexFlow StaveConnectors (vertical lines) */
@@ -119,6 +120,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         }
         (this.stave as any).MeasureNumber = this.MeasureNumber; // for debug info. vexflow automatically uses stave.measure for rendering measure numbers
         // also see VexFlowMusicSheetDrawer.drawSheet() for some other vexflow default value settings (like default font scale)
+        this.hasMetronomeMark = false;
 
         if (this.ParentStaff) {
             this.setLineNumber(this.ParentStaff.StafflineCount);

+ 8 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -795,7 +795,13 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     const measureNumber: number = Math.max(metronomeExpression.ParentMultiTempoExpression.SourceMeasureParent.MeasureNumber - 1, 0);
     const staffNumber: number = Math.max(metronomeExpression.StaffNumber - 1, 0);
     const firstMetronomeMark: boolean = measureNumber === 0 && staffNumber === 0;
-    const vfStave: VF.Stave = (this.graphicalMusicSheet.MeasureList[measureNumber][staffNumber] as VexFlowMeasure).getVFStave();
+    const vfMeasure: VexFlowMeasure = (this.graphicalMusicSheet.MeasureList[measureNumber][staffNumber] as VexFlowMeasure);
+    if (vfMeasure.hasMetronomeMark) {
+      return; // don't create more than one metronome mark per measure;
+      // TODO some measures still seem to have two metronome marks, one less bold than the other (or not bold),
+      //   might be because of both <sound> node and <per-minute> node (within <metronome>) creating metronome marks
+    }
+    const vfStave: VF.Stave = vfMeasure.getVFStave();
     //vfStave.addModifier(new VF.StaveTempo( // needs Vexflow PR
     let vexflowDuration: string = "q";
     if (metronomeExpression.beatUnit) {
@@ -836,6 +842,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     (<any>vfStave.getModifiers()[vfStave.getModifiers().length - 1]).setShiftX(
       xShift
     );
+    vfMeasure.hasMetronomeMark = true;
     if (skyline) {
       // TODO calculate bounding box of metronome mark instead of hacking skyline to fix lyricist collision
       skyline[0] = Math.min(skyline[0], -4.5 + yShift);

+ 2 - 2
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -497,8 +497,8 @@ export class VoiceGenerator {
     const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAccidental, accidentalValue);
     const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
     let note: Note = undefined;
-    let stringNumber: number = -1;
-    let fretNumber: number = -1;
+    let stringNumber: number = -1; //1 to always recognize as valid tab note
+    let fretNumber: number = -1; //0 to always recognize as valid tab note
     const bends: {bendalter: number, direction: string}[] = [];
     // check for guitar tabs:
     const notationNode: IXmlElement = node.element("notations");

+ 3 - 0
src/MusicalScore/VoiceData/Expressions/ContinuousExpressions/ContinuousTempoExpression.ts

@@ -3,6 +3,9 @@ import {PlacementEnum} from "../AbstractExpression";
 import {MultiTempoExpression} from "../MultiTempoExpression";
 import {AbstractTempoExpression} from "../AbstractTempoExpression";
 
+/** Tempo expressions that usually have a continuous or gradual effect playback-wise (e.g. accelerando),
+ * or describe shorter sections (e.g. meno mosso).
+ */
 export class ContinuousTempoExpression extends AbstractTempoExpression {
     constructor(label: string, placement: PlacementEnum, staffNumber: number, parentMultiTempoExpression: MultiTempoExpression) {
         super(label, placement, staffNumber, parentMultiTempoExpression);

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

@@ -4,6 +4,9 @@ import {ArgumentOutOfRangeException} from "../../Exceptions";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {MultiTempoExpression} from "./MultiTempoExpression";
 
+/** Tempo expressions that usually have an instantaneous and non-gradual effect on playback speed (e.g. Allegro),
+ * or at least cover large sections, compared to the usually gradual effects or shorter sections of ContinuousExpressions.
+ */
 export class InstantaneousTempoExpression extends AbstractTempoExpression {
     constructor(label: string, placement: PlacementEnum, staffNumber: number,
                 soundTempo: number, parentMultiTempoExpression: MultiTempoExpression, isMetronomeMark: boolean = false) {
@@ -11,11 +14,13 @@ export class InstantaneousTempoExpression extends AbstractTempoExpression {
             label = " = " + soundTempo;
         }*/
         super(label, placement, staffNumber, parentMultiTempoExpression);
+        this.isMetronomeMark = isMetronomeMark;
         this.setTempoAndTempoType(soundTempo);
     }
 
     public dotted: boolean;
     public beatUnit: string;
+    public isMetronomeMark: boolean;
     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);

+ 4 - 2
src/VexFlowPatch/readme.txt

@@ -21,9 +21,11 @@ able to add svg node id+class to beam (not yet in vexflow 4)
 clef.js (merged vexflow 4):
 open group to get SVG group+class for clef
 
-formatter.js (custom addition, unnecessary in vexflow 4):
+formatter.js:
 comment out unnecessary error thrown, which prevents the fix to
-layouting improvements with whole measure rests and e.g. 12/8 rhythm in #1187.
+  layouting improvements with whole measure rests and e.g. 12/8 rhythm in #1187.
+  (custom addition, unnecessary in vexflow 4)
+fix x set to NaN when totalTicks = 0 (bugfix for some tab scores, not sure if fixed in vexflow 4)
 
 gracenotegroup.js (custom addition, needs check if necessary in vexflow 4):
 check for gracenotegroup.spacing set, to allow e.g. spacing = 0 by default.

+ 7 - 1
src/VexFlowPatch/src/formatter.js

@@ -481,7 +481,13 @@ export class Formatter {
     // Pass 2: Take leftover width, and distribute it to proportionately to
     // all notes.
     const remainingX = justifyWidth - this.minTotalWidth;
-    const leftoverPxPerTick = remainingX / (this.totalTicks.value() * resolutionMultiplier);
+    let totalTicks = this.totalTicks.value();
+    if (totalTicks === 0) {
+      totalTicks = 1;
+      // VexFlowPatch: this is not supposed to happen, but does for faulty tab scores, needs fixing,
+      //   so that leftoverPxPerTick doesn't become Infinity, and then x is set to NaN (via setX())
+    }
+    const leftoverPxPerTick = remainingX / (totalTicks * resolutionMultiplier);
     let spaceAccum = 0;
 
     contextList.forEach((tick, index) => {

+ 305 - 0
test/data/test_tempo_expression_poco_meno_continuoustempoexpression.musicxml

@@ -0,0 +1,305 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!DOCTYPE score-partwise PUBLIC '-//Recordare//DTD MusicXML 4.0 Partwise//EN' 'http://www.musicxml.org/dtds/partwise.dtd'>
+<score-partwise version='4.0'>
+	<work>
+		<work-title>test_tempo_expression_poco_meno_continuoustempoexpression</work-title>
+	</work>
+	<identification>
+		<encoding>
+			<software>Sibelius 2023.6</software>
+			<software>Dolet 8.1 for Sibelius</software>
+			<encoding-date>2023-07-26</encoding-date>
+			<supports element='accidental' type='yes'/>
+			<supports element='transpose' type='yes'/>
+			<supports attribute='new-page' element='print' type='yes' value='yes'/>
+			<supports attribute='new-system' element='print' type='yes' value='yes'/>
+		</encoding>
+	</identification>
+	<defaults>
+		<scaling>
+			<millimeters>7</millimeters>
+			<tenths>40</tenths>
+		</scaling>
+		<concert-score/>
+		<page-layout>
+			<page-height>1597</page-height>
+			<page-width>1234</page-width>
+			<page-margins type='both'>
+				<left-margin>72.5714</left-margin>
+				<right-margin>72.5714</right-margin>
+				<top-margin>72.5714</top-margin>
+				<bottom-margin>72.5714</bottom-margin>
+			</page-margins>
+		</page-layout>
+		<system-layout>
+			<system-margins>
+				<left-margin>46.25</left-margin>
+				<right-margin>0</right-margin>
+			</system-margins>
+			<system-distance>120</system-distance>
+			<top-system-distance>108.75</top-system-distance>
+		</system-layout>
+		<?DoletSibelius StaffJustificationPercentage=65?>
+		<appearance>
+			<line-width type='beam'>5</line-width>
+			<line-width type='heavy barline'>5</line-width>
+			<line-width type='leger'>1.5625</line-width>
+			<line-width type='light barline'>1.5625</line-width>
+			<line-width type='slur middle'>2.1875</line-width>
+			<line-width type='slur tip'>0.625</line-width>
+			<line-width type='staff'>1.25</line-width>
+			<line-width type='stem'>1.25</line-width>
+			<line-width type='tie middle'>2.1875</line-width>
+			<line-width type='tie tip'>0.625</line-width>
+			<note-size type='grace'>60</note-size>
+			<note-size type='cue'>75</note-size>
+		</appearance>
+		<music-font font-family='Helsinki Std,engraved'/>
+		<word-font font-family='Palatino,serif'/>
+	</defaults>
+	<part-list>
+		<score-part id='P1'>
+			<part-name>Flute</part-name>
+			<part-abbreviation>Fl.</part-abbreviation>
+			<group>score</group>
+			<score-instrument id='P1-I1'>
+				<instrument-name>Flute</instrument-name>
+				<instrument-abbreviation>Fl.</instrument-abbreviation>
+				<instrument-sound>wind.flutes.flute</instrument-sound>
+			</score-instrument>
+			<midi-instrument id='P1-I1'>
+				<volume>75</volume>
+				<pan>9</pan>
+			</midi-instrument>
+		</score-part>
+	</part-list>
+<!--=========================================================-->
+	<part id='P1'>
+		<measure number='1'>
+			<print>
+				<system-layout>
+					<system-margins>
+						<left-margin>75</left-margin>
+						<right-margin>0</right-margin>
+					</system-margins>
+					<top-system-distance>181.5625</top-system-distance>
+				</system-layout>
+				<staff-layout>
+					<?DoletSibelius ExtraSpacesAbove=3?>
+				</staff-layout>
+			</print>
+			<attributes>
+				<divisions>768</divisions>
+				<key>
+					<fifths>0</fifths>
+					<mode>major</mode>
+				</key>
+				<time>
+					<beats>4</beats>
+					<beat-type>4</beat-type>
+				</time>
+				<clef>
+					<sign>G</sign>
+					<line>2</line>
+				</clef>
+			</attributes>
+			<direction placement='above' directive='yes' system='only-top'>
+				<direction-type>
+					<words font-weight='bold' font-size='12.56'>Andante</words>
+				</direction-type>
+			</direction>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>E</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>F</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+		</measure>
+<!--=========================================================-->
+		<measure number='2'>
+			<note>
+				<pitch>
+					<step>E</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<direction placement='above'>
+				<direction-type>
+					<words default-y='20' relative-x='-6' font-size='11'>poco meno</words>
+				</direction-type>
+				<offset>-345</offset>
+				<voice>1</voice>
+			</direction>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+		</measure>
+<!--=========================================================-->
+		<measure number='3'>
+			<note>
+				<pitch>
+					<step>A</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>up</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+		</measure>
+<!--=========================================================-->
+		<measure number='4'>
+			<note>
+				<pitch>
+					<step>E</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>D</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<pitch>
+					<step>B</step>
+					<octave>4</octave>
+				</pitch>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+		</measure>
+<!--=========================================================-->
+		<measure number='5'>
+			<note>
+				<pitch>
+					<step>C</step>
+					<octave>5</octave>
+				</pitch>
+				<duration>3072</duration>
+				<voice>1</voice>
+				<type>whole</type>
+			</note>
+			<barline location='right'>
+				<bar-style>light-heavy</bar-style>
+			</barline>
+		</measure>
+	</part>
+<!--=========================================================-->
+</score-partwise>